ساخت لیست Expanable در React Native


یک ساختار متعارف از چندین لیست است که در صدها اپلیکیشن موبایل استفاده می شود.Expandable ListView یک لیست قابل گسترش یا باز شدن است که برای نمایش چندین دسته به همراه زیردسته ها در یک فضای کم استفاده می شود.تفاوتی که Expandable ListView با ListView معمولی یا Flatlist داره در اینکه ما آیتم ها رو فقط میتونیم در یک level نمایش بدیم اما در Expandable ما میتونیم آیتم ها رو در چنیدن level با قابلیت باز و بسته شدن زیردسته ها نمایش بدیم.در حال حاظر pure component برای ایجاد Expandable ListView وجود ندارد اما با طراحی یک کامپوننت سفارشی Expandable ListView سفارشی خودمون رو در React Native ایجاد میکنیم.

1.ایمپورت کردن کامپوننت های Alert, LayoutAnimation, StyleSheet, View, Text, ScrollView, UIManager, TouchableOpacity, Platform, Image در فایل App.js


import React, { Component } from 'react';
import { Alert, LayoutAnimation, StyleSheet, View, Text, ScrollView, UIManager, TouchableOpacity, Platform, Image } from 'react-native';

2.ایجاد یک کلاس به نام Expandable_ListView

class Expandable_ListView extends Component {


}


3.ایجاد ()constructor در کلاس Expandable_ListView . درون سازنده یک state به نام layout_Height تعریف میکنیم و مقدار اون رو 0 میزاریم.

 constructor() {
 super();
 this.state = {
 layout_Height: 0
 }
 }

4.تعریف () componentWillReceiveProps با پارامتر nextProps در کلاس Expandable_ListView .در این method ما شرایطی رو برای هر آیتم مشخص می کنیم و بر اساس اون مقدار layout_height state رو تنظیم میکنیم.componentWillReceiveProps برای مقایسه مجدد داده ها زمانی که یک مقدار به عنوان prop تغییر می کند، استفاده می شود.


componentWillReceiveProps(nextProps) {
 if (nextProps.item.expanded) {
 this.setState(() => {
 return {
 layout_Height: null
 }
 });
 }
 else {
 this.setState(() => {
 return {
 layout_Height: 0
 }
 });
 }

5.تعریف() shouldComponentUpdate با پارامترهای nextProps و nextState در کلاس Expandable_ListView . متد shouldComponentUpdate برای افزایش عملکرد اپلیکیشن از طریق غیرفعال کردن re-rendering زمانی که کاربر یر روی یک data کلیک میکند.با اینکار مطمئن می شویم که فقط آیتم انتخاب شدهload شود نه کل لیست.

shouldComponentUpdate(nextProps, nextState) {
if (this.state.layout_Height !== nextState.layout_Height) {
return true;
}
return false;
}

6.ایجاد یک تابع به نام ()show_Selected_Category با پارامتر item در کلاس Expandable_ListView .این تابع برای نمایش آیتم انتخاب شده در لیست استفاده می شود.

show_Selected_Category = (item) => {
 // Write your code here which you want to execute on sub category selection.
 Alert.alert(item);
 }

7.سرانجام ما آیتم ها و عنوان Expandable ListView رو در بلاک render’s return کلاس Expandable_ListView ایجاد می کنیم.

render() {
return (
<View style={styles.Panel_Holder}>
<TouchableOpacity activeOpacity={0.8} onPress={this.props.Function} style={styles.category_View}>
<Text style={styles.category_Text}>{this.props.item.category_Name} </Text>
<Image
source={{ uri: 'https://reactnativecode.com/wp-content/uploads/2019/02/arrow_right_icon.png' }}
style={styles.iconStyle} />
</TouchableOpacity>
<View style={{ height: this.state.layout_Height, overflow: 'hidden' }}>
{
this.props.item.sub_Category.map((item, key) => (
<TouchableOpacity key={key} style={styles.sub_Category_Text} onPress={this.show_Selected_Category.bind(this, item.name)}>
<Text> {item.name} </Text>
<View style={{ width: '100%', height: 1, backgroundColor: '#000' }} />
</TouchableOpacity>
))
}
</View>
</View>
);
}

8.سورس کد کامل کلاس Expandable_ListView .

class Expandable_ListView extends Component {
constructor() {
super();
this.state = {
layout_Height: 0
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.item.expanded) {
this.setState(() => {
return {
layout_Height: null
}
});
}
else {
this.setState(() => {
return {
layout_Height: 0
}
});
}
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.layout_Height !== nextState.layout_Height) {
return true;
}
return false;
}
show_Selected_Category = (item) => {
// Write your code here which you want to execute on sub category selection.
Alert.alert(item);
}
render() {
return (
<View style={styles.Panel_Holder}>
<TouchableOpacity activeOpacity={0.8} onPress={this.props.Function} style={styles.category_View}>
<Text style={styles.category_Text}>{this.props.item.category_Name} </Text>
<Image
source={{ uri: 'https://reactnativecode.com/wp-content/uploads/2019/02/arrow_right_icon.png' }}
style={styles.iconStyle} />
</TouchableOpacity>
<View style={{ height: this.state.layout_Height, overflow: 'hidden' }}>
{
this.props.item.sub_Category.map((item, key) => (
<TouchableOpacity key={key} style={styles.sub_Category_Text} onPress={this.show_Selected_Category.bind(this, item.name)}>
<Text> {item.name} </Text>
<View style={{ width: '100%', height: 1, backgroundColor: '#000' }} />
</TouchableOpacity>
))
}
</View>
</View>
);
}
}

9. تعریف ()constructor در کلاس App .ما layout animation رو درون سازنده فعال می کنیم.

constructor() {
super();
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true)
}

10.تعریف یک آرایه از نوع const در کلاس App. این آرایه شامل آیتم های Expandable ListView است.

const array = [
 
 {
 expanded: false, category_Name: "Mobiles", sub_Category: [{ id: 1, name: 'Mi' }, { id: 2, name: 'RealMe' }, { id: 3, name: 'Samsung' },
 { id: 4, name: 'Infinix' }, { id: 5, name: 'Oppo' }, { id: 6, name: 'Apple' }, { id: 7, name: 'Honor' }]
 },
 
 {
 expanded: false, category_Name: "Laptops", sub_Category: [{ id: 8, name: 'Dell' }, { id: 9, name: 'MAC' }, { id: 10, name: 'HP' },
 { id: 11, name: 'ASUS' }]
 },
 {
 expanded: false, category_Name: "Computer Accessories", sub_Category: [{ id: 12, name: 'Pendrive' }, { id: 13, name: 'Bag' },
 { id: 14, name: 'Mouse' }, { id: 15, name: 'Keyboard' }]
 },
 {
 expanded: false, category_Name: "Home Entertainment", sub_Category: [{ id: 16, name: 'Home Audio Speakers' },
 { id: 17, name: 'Home Theatres' }, { id: 18, name: 'Bluetooth Speakers' }, { id: 19, name: 'DTH Set Top Box' }]
 },
 {
 expanded: false, category_Name: "TVs by brand", sub_Category: [{ id: 20, name: 'Mi' },
 { id: 21, name: 'Thomson' }, { id: 22, name: 'LG' }, { id: 23, name: 'SONY' }]
 },
 {
 expanded: false, category_Name: "Kitchen Appliances", sub_Category: [{ id: 24, name: 'Microwave Ovens' },
 { id: 25, name: 'Oven Toaster Grills (OTG)' }, { id: 26, name: 'Juicer/Mixer/Grinder' }, { id: 27, name: 'Electric Kettle' }]
 }
 ];
 this.state = { AccordionData: [...array] }
 }
 11.ایجاد یک تابع به نام ()update_Layout با پارامتر index در کلاس App. در ابتدا ما layout animation رو فعال می کنیم و سپس layout رو با آیتم های جدید بروزرسانی می کنیم. 
 update_Layout = (index) => {
 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
 const array = [...this.state.AccordionData];
 array[index]['expanded'] = !array[index]['expanded'];
 this.setState(() => {
 return {
 AccordionData: array
 }
 });
 }

12.سرانجام ما کامپوننت Expandable_ListView در بلاک render’s return فراخوانی میکنیم.

render() {
return (
<View style={styles.MainContainer}>
<ScrollView contentContainerStyle={{ paddingHorizontal: 10, paddingVertical: 5 }}>
{
this.state.AccordionData.map((item, key) =>
(
<Expandable_ListView key={item.category_Name} Function={this.update_Layout.bind(this, key)} item={item} />
))
}
</ScrollView>
</View>
);
}

13.ایجاد استایل.

const styles = StyleSheet.create({
 MainContainer: {
 flex: 1,
 justifyContent: 'center',
 paddingTop: (Platform.OS === 'ios') ? 20 : 0,
 backgroundColor: '#F5FCFF',
 },
 
 iconStyle: {
 
 width: 30,
 height: 30,
 justifyContent: 'flex-end',
 alignItems: 'center',
 tintColor: '#fff'
 },
 sub_Category_Text: {
 fontSize: 18,
 color: '#000',
 padding: 10
 },
 category_Text: {
 textAlign: 'left',
 color: '#fff',
 fontSize: 21,
 padding: 10
 },
 category_View: {
 marginVertical: 5,
 flexDirection: 'row',
 justifyContent: 'space-between',
 alignItems: 'center',
 backgroundColor: '#0091EA'
 },
 Btn: {
 padding: 10,
 backgroundColor: '#FF6F00'
 }
});

14.کد کامل فایل App.js



import React, { Component } from 'react';
 
import { Alert, LayoutAnimation, StyleSheet, View, Text, ScrollView, UIManager, TouchableOpacity, Platform, Image } from 'react-native';
 
class Expandable_ListView extends Component {
 
 constructor() {
 
 super();
 
 this.state = {
 
 layout_Height: 0
 
 }
 }
 
 componentWillReceiveProps(nextProps) {
 if (nextProps.item.expanded) {
 this.setState(() => {
 return {
 layout_Height: null
 }
 });
 }
 else {
 this.setState(() => {
 return {
 layout_Height: 0
 }
 });
 }
 }
 
 shouldComponentUpdate(nextProps, nextState) {
 if (this.state.layout_Height !== nextState.layout_Height) {
 return true;
 }
 return false;
 }
 
 show_Selected_Category = (item) => {
 
 // Write your code here which you want to execute on sub category selection.
 Alert.alert(item);
 
 }
 
 render() {
 return (
 <View style={styles.Panel_Holder}>
 
 <TouchableOpacity activeOpacity={0.8} onPress={this.props.Function} style={styles.category_View}>
 
 <Text style={styles.category_Text}>{this.props.item.category_Name} </Text>
 
 <Image
 source={{ uri: 'https://reactnativecode.com/wp-content/uploads/2019/02/arrow_right_icon.png' }}
 style={styles.iconStyle} />
 
 </TouchableOpacity>
 
 <View style={{ height: this.state.layout_Height, overflow: 'hidden' }}>
 
 {
 this.props.item.sub_Category.map((item, key) => (
 
 <TouchableOpacity key={key} style={styles.sub_Category_Text} onPress={this.show_Selected_Category.bind(this, item.name)}>
 
 <Text> {item.name} </Text>
 
 <View style={{ width: '100%', height: 1, backgroundColor: '#000' }} />
 
 </TouchableOpacity>
 
 ))
 }
 
 </View>
 
 </View>
 
 );
 }
}
 
export default class App extends Component {
 
 constructor() {
 super();
 
 if (Platform.OS === 'android') {
 
 UIManager.setLayoutAnimationEnabledExperimental(true)
 
 }
 
 const array = [
 
 {
 expanded: false, category_Name: "Mobiles", sub_Category: [{ id: 1, name: 'Mi' }, { id: 2, name: 'RealMe' }, { id: 3, name: 'Samsung' },
 { id: 4, name: 'Infinix' }, { id: 5, name: 'Oppo' }, { id: 6, name: 'Apple' }, { id: 7, name: 'Honor' }]
 },
 
 {
 expanded: false, category_Name: "Laptops", sub_Category: [{ id: 8, name: 'Dell' }, { id: 9, name: 'MAC' }, { id: 10, name: 'HP' },
 { id: 11, name: 'ASUS' }]
 },

 {
 expanded: false, category_Name: "Computer Accessories", sub_Category: [{ id: 12, name: 'Pendrive' }, { id: 13, name: 'Bag' },
 { id: 14, name: 'Mouse' }, { id: 15, name: 'Keyboard' }]
 },
 
 {
 expanded: false, category_Name: "Home Entertainment", sub_Category: [{ id: 16, name: 'Home Audio Speakers' },
 { id: 17, name: 'Home Theatres' }, { id: 18, name: 'Bluetooth Speakers' }, { id: 19, name: 'DTH Set Top Box' }]
 },
 
 {
 expanded: false, category_Name: "TVs by brand", sub_Category: [{ id: 20, name: 'Mi' },
 { id: 21, name: 'Thomson' }, { id: 22, name: 'LG' }, { id: 23, name: 'SONY' }]
 },
 
 {
 expanded: false, category_Name: "Kitchen Appliances", sub_Category: [{ id: 24, name: 'Microwave Ovens' },
 { id: 25, name: 'Oven Toaster Grills (OTG)' }, { id: 26, name: 'Juicer/Mixer/Grinder' }, { id: 27, name: 'Electric Kettle' }]
 }
 ];
 
 this.state = { AccordionData: [...array] }
 }
 
 update_Layout = (index) => {
 
 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
 
 const array = [...this.state.AccordionData];
 
 array[index]['expanded'] = !array[index]['expanded'];
 
 this.setState(() => {
 return {
 AccordionData: array
 }
 });
 }
 
 render() {
 return (
 <View style={styles.MainContainer}>
 
 <ScrollView contentContainerStyle={{ paddingHorizontal: 10, paddingVertical: 5 }}>
 {
 this.state.AccordionData.map((item, key) =>
 (
 <Expandable_ListView key={item.category_Name} Function={this.update_Layout.bind(this, key)} item={item} />
 ))
 }
 </ScrollView>
 
 </View>
 );
 }
}
 
const styles = StyleSheet.create({
 
 MainContainer: {
 flex: 1,
 justifyContent: 'center',
 paddingTop: (Platform.OS === 'ios') ? 20 : 0,
 backgroundColor: '#F5FCFF',
 },
 iconStyle: {
 width: 30,
 height: 30,
 justifyContent: 'flex-end',
 alignItems: 'center',
 tintColor: '#fff'
 
 },

 sub_Category_Text: {
 fontSize: 18,
 color: '#000',
 padding: 10
 },

 category_Text: {
 textAlign: 'left',
 color: '#fff',
 fontSize: 21,
 padding: 10
 }, 
 category_View: {
 marginVertical: 5,
 flexDirection: 'row',
 justifyContent: 'space-between',
 alignItems: 'center',
 backgroundColor: '#0091EA'
 },

 Btn: {
 padding: 10,
 backgroundColor: '#FF6F00'
 }
 
});