前言
SectionList 是最为常用的控件。此Demo包含数据分组展示、编辑全选、单选,上拉刷新、下拉加载更多等,如下效果。
效果演示
代码
import React from 'react';
import {
Dimensions,
Image, Platform, SectionList, StatusBar, Text, TouchableOpacity, View
} from 'react-native';
import LinearGradient from "react-native-linear-gradient";
import ImageButton from "miot/ui/ImageButton";
const tag = "SectionListDemo";
let screenHeight = Dimensions.get('window').height;
let screenWidth = Dimensions.get('window').width;
let containerHeight = (StatusBar.currentHeight || 0) + 65;
export default class SectionListDemo extends React.Component{
constructor(props) {
super(props);
let sections = [
{title: "09:00-10:00", data: [{dateTime: "09:01"}]},
{title: "10:00-11:00", data: [{dateTime: "10:01"}, {dateTime: "10:02"}]},
{title: "11:00-12:00", data: [{dateTime: "11:01"}, {dateTime: "11:02"}, {dateTime: "11:03"}]},
{title: "12:00-13:00", data: [{dateTime: "12:01"}, {dateTime: "12:02"}, {dateTime: "12:03"}, {dateTime: "12:04"}]},
{title: "13:00-14:00", data: [{dateTime: "13:01"}, {dateTime: "13:02"}, {dateTime: "13:03"}, {dateTime: "13:04"}]},
]
this.state = {
sections: sections,
isRefreshing: false,
showFoot: 0,
currentItemTag:undefined,
isSelectMode:false,
}
this.requestData = false;
this.hasMoreData = true;
this.requestMoreTimes = 0;//用来模拟器请求更多
//this.dataToGroup();
}
static navigationOptions = ({ navigation }) => {
return {
headerTransparent: true,
header: null
}
};
_renderTitleView() {
StatusBar.setBarStyle('dark-content');
if (Platform.OS == 'android') {
StatusBar.setTranslucent(true);
}
const containerStyle = {
position: "absolute",
top: 0,
backgroundColor: "#00000000",
height: containerHeight,
width: "100%",
display: "flex",
flexDirection: "row",
alignItems: "center",//垂直居中
paddingLeft: 9,
paddingRight: 9,
}
const textContainerStyle = {
flexGrow: 1,
alignSelf: 'stretch',//控制自己填充满父类的高度
display: "flex",
flexDirection: "column",
justifyContent: 'center',
alignItems: 'stretch',//控制子类填充满本身的宽度
marginHorizontal: 5,
}
let screenWidth = Dimensions.get('window').width;
const titleTextStyle = {
fontSize: 16,
width: screenWidth - 110,
fontFamily: 'D-DINCondensed-Bold',
textAlignVertical: 'center',
textAlign: 'center'
}
const darkTitleColor = '#000000'; //标题颜色
const titleColor = {color: darkTitleColor};
let iconSize = 40;
let isSelectMode = this.state.isSelectMode;
let title;
let isSelectAll = false;
if(isSelectMode){
let selectedCount = 0;
let notSelectedCount = 0;
let sections = this.state.sections;
for (let section of sections) {
for (let item of section.data) {
if(item.isSelected){
selectedCount++;
}else {
notSelectedCount++;
}
}
}
isSelectAll = (notSelectedCount === 0);
title = "选择了" + selectedCount + "个";
}else {
title = "SectionListDemo演示"
}
const iconBack = require("../../resources/Images/icon_back_black.png");
const iconCancel = require("../../resources/Images/icon_cancle_black.png")
const iconSelect = require("../../resources/Images/icon_select_black.png")
const icon_select_active = require("../../resources/Images/icon_select_active.png")
const iconEdit = require("../../resources/Images/icon_edit_black.png")
const leftIcon = isSelectMode ? iconCancel : iconBack;
const rightIcon = isSelectMode ? (isSelectAll ? icon_select_active : iconSelect) : iconEdit;
const leftIconData = {
source: leftIcon,
onPress: (() => {
if(isSelectMode){
//退出编辑
this.setState({isSelectMode:false})
this._doSectionSelectAll(false);
}else {
//退出页面
this.props.navigation.goBack();
}
})
}
const rightIconData = {
source: rightIcon,
onPress: (()=>{
if(isSelectMode){
//全选处理
this._doSectionSelectAll(!isSelectAll);
}else {
//进入编辑
this.setState({isSelectMode:true,currentItemTag:undefined});
}
})
}
return (
<View style={{width: "100%", height: 75}}>
<StatusBar backgroundColor={'transparent'}/>
<LinearGradient
colors={['#00000000', '#00000000']}
style={containerStyle}>
<View
style={{width: iconSize, height: iconSize, position: "relative"}}>
<ImageButton
style={{width: iconSize, height: iconSize, position: "absolute", marginLeft: 0}}
source={leftIconData.source}
onPress={leftIconData.onPress}
/>
</View>
<View style={textContainerStyle}>
<Text
numberOfLines={1}
style={[titleTextStyle, titleColor]}
>
{title}
</Text>
</View>
<View
style={{width: iconSize, height: iconSize, position: "relative"}}>
<ImageButton
style={{width: iconSize, height: iconSize, position: "absolute"}}
source={rightIconData.source}
onPress={rightIconData.onPress}
/>
</View>
</LinearGradient></View>
);
}
render() {
return (
<View style={{
backgroundColor: "white",
width: '100%',
height: '100%',
display: "flex"
}}>
{this._renderTitleView()}
{this._renderSectionList()}
{this._renderBottomDeleteView()}
</View>
)
}
_renderSectionList(){
return (
<View style={{height:(screenHeight-containerHeight)}}>
{/*height指定解决onEndReached多次触发问题*/}
<SectionList
style={{marginHorizontal:5}}
renderSectionHeader={({ section: section }) => this._renderSectionHeader(section)}
renderItem={({ item, index ,section}) => this._renderItem(item, index,section)}
sections={this.state.sections}
extraData={this.state}
keyExtractor={(item, index) => index}
stickySectionHeadersEnabled={false}
scrollIndicatorInsets={{right: 1}/*ios 滚动条异常滚动条居中解决方案*/}
contentContainerStyle={{flexDirection: 'row',flexWrap: 'wrap'}}
onRefresh={this._onRefresh.bind(this)}
refreshing={this.state.isRefreshing}
onEndReached={this._onEndReached.bind(this)}
onEndReachedThreshold={0.1}
ListFooterComponent={this._renderFooter.bind(this)}
/>
</View>
)
}
_renderSectionHeader(section) {
//console.log(tag,"_renderSectionHeader",section)
let title = section.title;
let isSelectMode = this.state.isSelectMode;
let isSelectAll = true;
let items = section.data;
for (let item of items) {
if(!item.isSelected){
isSelectAll = false;
break;
}
}
let selectAllTxt = isSelectAll?"全不选":"全选";
return (
<View style={{flexDirection: 'row', height:30,width: Dimensions.get('window').width,marginVertical:5,paddingLeft:2,alignItems:"center"}}>
<Text style={{color:"#7F7F7F",fontSize:15}}>
{title}
</Text>
{
isSelectMode ?
<TouchableOpacity style={{position: "absolute", right: 20,backgroundColor:'#E5E5E5',borderRadius:10,paddingVertical:5,paddingHorizontal:10}}
onPress={()=>{
this._doSectionHeaderSelectAll(title,!isSelectAll);
}}>
<Text style={{color:"#000000",fontSize:15,fontWeight: 'bold'}}>
{selectAllTxt}
</Text>
</TouchableOpacity>:null
}
</View>
)
}
_renderItem(item, index,section) {
//console.log(tag,"_renderItem",item,index,section);
let marginHorizontal = 3.5;
let screenWidth = Dimensions.get('window').width;
let containerWidth = (screenWidth - 11 -marginHorizontal * 6) / 3;
let dateTimeTxt = item.dateTime;
let itemImgSource = require("../../resources/Images/item_icon.png") ;
let currentItemTag = item.dateTime;
let isSelectMode = this.state.isSelectMode;
let isSelected = item.isSelected;
let isSelectedImg = isSelected ? require("../../resources/Images/icon_selected.png") : require("../../resources/Images/icon_unselected.png");
let curItemBackground = {
borderColor: "#32BAC0",
borderWidth: 1.5,
borderRadius:6
}
return (
<TouchableOpacity
style={[{ width: containerWidth, height: 85, marginBottom: 10, marginLeft: marginHorizontal, marginRight: marginHorizontal }, this.state.currentItemTag == item.dateTime ? curItemBackground : null]}
onPress={()=>{
if(!isSelectMode){
this.setState({currentItemTag:currentItemTag})
}else {
let sections = this.state.sections;
for (let section of sections) {
for (let item of section.data) {
if(item.dateTime === currentItemTag){
item.isSelected = !item.isSelected;
}
}
}
this.setState({sections:sections})
}
}}
>
<View style={{ width: "100%", height: 60, marginBottom: 3, position: "relative" }} >
<Image style={{ width: "100%", height: "100%", borderRadius: 5 }}
source={itemImgSource}
/>
{
isSelectMode ?
<Image style={{ width: 18, height: 18, position: "absolute", bottom: 4, right: 4 }}
source={isSelectedImg}
/> :null
}
</View>
<View style={{ display: "flex", flexDirection: "row", alignItems: "center" }}>
<Text style={{ marginLeft: 4, marginRight: 4, fontSize: 11, color: "#000000" }}>
{dateTimeTxt}
</Text>
</View>
</TouchableOpacity>
);
}
// 头部刷新
_onRefresh = () => {
if(this.state.isSelectMode){
return;
}
this.setState({isRefreshing:true})
//模拟去服务器拉取数据
setTimeout(()=>{
this.setState({isRefreshing:false})
},5000)
};
_onEndReached = () => {
if(this.state.isSelectMode){
return;
}
console.log(tag,"_onEndReached","showFoot=",this.state.showFoot,"hasMoreData=",this.hasMoreData,"requestData=",this.requestData);
// 如果是正在加载中或没有更多数据了,正在请求数据,则返回
if (this.state.showFoot != 0 || this.requestData) {
this.onEndReachedFlag = false;
return;
}
if (this.hasMoreData) {
this.setState({showFoot: 2});
//模拟去服务器拉取更多数据
this.requestData = true;
this.requestMoreTimes++;
setTimeout(()=>{
this.requestData = false;
console.log(tag,"_onEndReached","requestData=",this.requestData);
let sections = this.state.sections;
sections.push({title:"x"+this.requestMoreTimes+":xx",data:[{dateTime: "x"+this.requestMoreTimes+":01"}, {dateTime: "x"+this.requestMoreTimes+":02"}]})
this.setState({sections:sections});
if(this.requestMoreTimes>=5){
this.hasMoreData = false;
}else {
this.hasMoreData = true;
}
this.setState({showFoot: this.hasMoreData ? 0 : 1});
},3000)
} else {
this.setState({showFoot: 1});
}
this.onEndReachedFlag = false;
}
_renderFooter() {
//console.log(tag,"_renderFooter","showFoot=",this.state.showFoot)
if (this.state.showFoot === 1) {
return (
<View style={{height: 30,width:screenWidth, justifyContent: 'center',alignItems: 'center',}}>
<Text style={{flex:1,textAlignVertical: 'center',textAlign: 'center',color: '#999999', fontSize: 14}}>
没有可加载的数据
</Text>
</View>
)
} else if (this.state.showFoot === 2) {
return (
<View style={{
width:screenWidth,
flexDirection: 'row',
height: 30,
justifyContent: 'center',
alignItems: 'center'
}}>
<Text style={{flex:1,textAlignVertical: 'center',textAlign: 'center'}}>正在加载数据...</Text>
</View>
);
} else if (this.state.showFoot === 0) {
return (
<View style={{height: 0,width:screenWidth}}>
<Text>{/* 正常情况:不显示加载中 或 没有可加载的数据 */}</Text>
</View>
);
}
}
_doSectionSelectAll(isSelectAll){
let sections = this.state.sections;
for (let section of sections) {
for (let item of section.data) {
item.isSelected = isSelectAll;
}
}
this.setState({sections:sections})
}
_doSectionHeaderSelectAll(sectionTitle,isSelectAll){
let sections = this.state.sections;
for (let section of sections) {
if(sectionTitle === section.title){
for (let item of section.data) {
item.isSelected = isSelectAll;
}
}
}
this.setState({sections:sections})
}
_renderBottomDeleteView() {
if (!this.state.isSelectMode) {
return;
}
return (
<View style={{ width: "100%",height: 70,display: "flex",justifyContent: "center",alignItems: "center" }}>
<View style={{width:"100%",height:1,backgroundColor: "#e5e5e5"}}></View>
<TouchableOpacity
style={{ width: 50, display: "flex", alignItems: "center" }}
onPress={() => {
let delItems = [];
let sections = this.state.sections;
for (let section of sections) {
for (let item of section.data) {
if(item.isSelected){
delItems = delItems.concat(item);
}
}
}
console.log(tag,"delItems=",delItems)
}}
>
<Image
style={{ width: 35,height: 35 }}
source={require("../../resources/Images/icon_delete_photo_black.png")} />
<Text style={{ color: "#000000",fontSize: 11 }} >
{"删除"}
</Text>
</TouchableOpacity>
</View>
);
}
dataToGroup(){
let data1 = [
{dateTime: "09:01"},
{dateTime: "09:02"},
{dateTime: "10:01"},
{dateTime: "10:02"},
{dateTime: "11:01"},
{dateTime: "11:02"},
{dateTime: "11:03"},
{dateTime: "12:01"},
{dateTime: "12:02"},
{dateTime: "12:03"}]
//倒序
let data2 = data1.sort(function(a,b){
return parseInt(b.dateTime.replace(/:/g,''))
-parseInt(a.dateTime.replace(/:/g,''));
})
console.log("data1=",data1,"data2=",data2);
let groupData=[]
for (let i = 0; i < data2.length; i++) {
let hour = (data2[i].dateTime.split(":"))[0];
let title= hour;
let data = data2[i];
//遍历是否存在title
if(groupData.length===0){
groupData.push({title:title,data:[data]})
}else {
let isExist = false;
for (let i = 0; i < groupData.length; i++) {
if(title === groupData[i].title){
groupData[i].data.push(data);
isExist = true;
break;
}
}
if(!isExist){
groupData.push({title:title,data:[data]})
}
}
}
console.log("groupData=",groupData);
}
}
SectionList【sections】数据格式组装
dataToGroup(){
let data1 = [
{dateTime: "09:01"},
{dateTime: "09:02"},
{dateTime: "10:01"},
{dateTime: "10:02"},
{dateTime: "11:01"},
{dateTime: "11:02"},
{dateTime: "11:03"},
{dateTime: "12:01"},
{dateTime: "12:02"},
{dateTime: "12:03"}]
//倒序
let data2 = data1.sort(function(a,b){
return parseInt(b.dateTime.replace(/:/g,''))
-parseInt(a.dateTime.replace(/:/g,''));
})
console.log("data1=",data1,"data2=",data2);
let groupData=[]
for (let i = 0; i < data2.length; i++) {
let hour = (data2[i].dateTime.split(":"))[0];
let title= hour;
let data = data2[i];
//遍历是否存在title
if(groupData.length===0){
groupData.push({title:title,data:[data]})
}else {
let isExist = false;
for (let i = 0; i < groupData.length; i++) {
if(title === groupData[i].title){
groupData[i].data.push(data);
isExist = true;
break;
}
}
if(!isExist){
groupData.push({title:title,data:[data]})
}
}
}
console.log("groupData=",groupData);
}
总结
工作中常用,做个云备份,方便查看、总结、拓展。