native react 可伸缩背景_React Native可伸缩列表封装与快速实现方案

ReactNative.png

类似QQ好友列表的可伸缩分组列表,相信对于一部分的开发者来说还是有需要用到的,本文将会结合自己的开发理解和已发布在github和npm上的实例,结合讲解如何使用React Native快速封装此类组件。你也可以查看已经封装并发布在Github上的组件react-native-expandable-section-list查看效果,当然在你的项目中,你也可以直接使用该组件,如发现任何bug请及时提出issue给我,我会认真处理,如果你觉得组件做的还不错,请给我一个star哦!

封装思路解析

原生ios开发阶段时,UITableView功能强大,实现类似QQ列表这样的功能,你只需要在UITableView的delegate方法中作相关控制即可实现,但是最开始转到react native的时候,由于当时刚参加工作,没什么思路,后经过一些尝试和封装,才渐渐有了现在的实现方法

思路一: 通过View的onLayout回调记录分组布局大小,控制打开组高度和关闭组高度实现类似开关分组效果(这个思路和代码是一个同事的方法,代码稍微有点绕,但是可以控制打开关闭的动画,我暂时放弃了使用,但是在写下一篇文章会来讲解这个方法的思路和实现)

思路二: 控制数据,改变开关分组的标记值,并结合react native的state渲染实现类似开关分组效果(这是当前用在实际项目中的一个组件,下面会具体介绍如何实现它的代码和思路)

代码解析

首先渲染一个全屏的ListView,ListView中的row当作分组,row渲染成下列组件:

section.png

分组渲染:

_renderRow = (rowData, sectionId, rowId) => { // eslint-disable-line

const { renderRow, renderSectionHeaderX, renderSectionFooterX, headerKey, memberKey } = this.props;

let memberArr = rowData[memberKey];

if (!this.state.memberOpened.get(rowId) || !memberArr) {

memberArr = [];

}

return (

this._onPress(rowId)}>

{ renderSectionHeaderX ? renderSectionHeaderX(rowData[headerKey], rowId) : null}

{

memberArr.map((rowItem, index) => {

return (

{renderRow ? renderRow(rowItem, index, sectionId) : null}

);

})

}

{ memberArr.length > 0 && renderSectionFooterX ? renderSectionFooterX(rowData, sectionId) : null }

);

}

说明:如代码,每个Row中都由renderSectionHeaderX、renderRow、renderSectionFooterX组成,renderSectionHeaderX通过Touchable组件的点击事件,可以控制下面的ScrollView包着的一个个组成员renderRow,及sectionFooter,每次渲染这样一个分组的时候通过当前组的开关状态this.state.memberOpened来获得当前memberArr数组是空数组或是你的传入数据rowData[memberKey],当然也可以控制你的关闭状态不一定是空数组,这里的开关状态寄存器是一个Map对象,或者你也可以自己构造合适的键值对对象

开关对象构造:

constructor(props) {

super(props);

this.ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });

let map = new Map();

if (props.dataSource && props.isOpen) {

props.dataSource.map((item, i) => map.set(i.toString(), true))

}

if (props.openOptions) {

props.openOptions.map((item) => map.set(item.toString(), true))

}

this.state = {

memberOpened: map

}

}

说明:构造状态寄存器,通过属性设置默认的分组开关状态,isOpen是bool值,记录是否全部打开分组, openOptions是一个数组,里面记录你选择打开哪些分组,如打开0, 2分组,即为[0,2]

点击开关方法:

_onPress = (i) => {

this.setState((state) => {

const memberOpened = new Map(state.memberOpened);

memberOpened.set(i, !memberOpened.get(i)); // toggle

return { memberOpened };

});

if (this.props.headerOnPress) {

this.prop.headerOnPress(i, this.state.memberOpened.get(i) || false);

}

LayoutAnimation.easeInEaseOut();

};

说明:每次点击组头,执行该方法,改变当前组在状态寄存器中设置的状态,最开始思路甚至把这状态寄存设置成为每个分组数据的某个特定属性,后来发现Map拿来这里当作一种寄存器用真的很完美。

数据源

const MockData = [

...

{

header: 'sectionHeader',

member: [

...

{

title: 'memberTitle',

content: 'content',

},

...

]

},

...

]

特定组件是拿来做特定用途的,react-native-expandable-section-list只适用于本文说明的情况,当然,也对数据源做一定的限制,从而快速封装出来QQ列表的可伸缩分组效果。

FlatList扩展封装

react native在版本0.43后提出使用FlatList,在使用FlatList的时候,通过同样的思路,也对FlatList做了类似的扩展,组件可见react-native-expandable-section-flatlist,0.43版本后的FlatList确实是对列表组件性能做了极大的提升,数据源data属性,加上扩展数据属性extraData的使用让每次的state组件渲染不会再是渲染当前列表,而是直接定位到你作出改变的分组从而改变分组的开关状态。keyExtractor属性也更加利于定位每一个分组的位置和设定。在数据测试时,暂只做了100个分组的测试,FlatList的性能原理是只会显示你看到的这部分分组,做的还是相当好的,暂未发现性能影响。我尽量减少了对原组件FlatList的属性影响,其他类似上拉刷新,下拉加载等属性也全部兼容,接下来直接展示精简后的代码:

class ExpanableList extends Component {

constructor(props) {

super(props);

let map = new Map();

if (props.dataSource && props.isOpen) {

props.dataSource.map((item, i) => map.set(i, true))

}

if (props.openOptions) {

props.openOptions.map((item) => map.set(item, true))

}

this.state = {

memberOpened: map

}

}

static propTypes = {

dataSource: PropTypes.array.isRequired,

headerKey: PropTypes.string,

memberKey: PropTypes.string,

renderRow: PropTypes.func,

renderSectionHeaderX: PropTypes.func,

renderSectionFooterX: PropTypes.func,

headerOnPress: PropTypes.func,

isOpen: PropTypes.bool,

openOptions: PropTypes.array,

};

static defaultProps = {

headerKey: 'header',

memberKey: 'member',

isOpen: false,

};

_keyExtractor = (item, index) => index;

_onPress = (i) => {

this.setState((state) => {

const memberOpened = new Map(state.memberOpened);

memberOpened.set(i, !memberOpened.get(i)); // toggle

return { memberOpened };

});

if (this.props.headerOnPress) {

this.prop.headerOnPress(i, this.state.memberOpened.get(i) || false);

}

LayoutAnimation.easeInEaseOut();

};

_renderItem = ({ item, index }) => { // eslint-disable-line

const { renderRow, renderSectionHeaderX, renderSectionFooterX, headerKey, memberKey } = this.props;

const sectionId = index;

let memberArr = item[memberKey];

if (!this.state.memberOpened.get(sectionId) || !memberArr) {

memberArr = [];

}

return (

this._onPress(sectionId)}>

{ renderSectionHeaderX ? renderSectionHeaderX(item[headerKey], sectionId) : null}

{

memberArr.map((rowItem, rowId) => {

return (

{renderRow ? renderRow(rowItem, rowId, index) : null}

);

})

}

{ memberArr.length > 0 && renderSectionFooterX ? renderSectionFooterX(item, sectionId) : null }

);

};

render() {

const { dataSource } = this.props;

return (

{...this.props}

data={dataSource}

extraData={this.state}

keyExtractor={this._keyExtractor}

renderItem={this._renderItem}

/>

);

}

}

写在最后

写总结和分享文章确实是一件快乐的事情,第一篇文章React Native路由理解和react-navigation库封装学习受到了一些人的肯定,真的很开心,谢谢各位,一起进步!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值