React Native带你实现scrollable-tab-view(四)
来源:互联网 发布:ipsec dh算法 编辑:程序博客网 时间:2024/06/02 03:11
上一节React Native带你实现scrollable-tab-view(三)中我们最后实现了我们scrollable-tab-view的效果为:
还记得我们上一节最后留下的问题吗?比如我们有很多个页面,然后一出来就加载那么多页面的话,再牛掰的手机都扛不住,我们想做的是:
1、第一次加载页面的时候,假设有三个页面,第一次就只加载第一个页面,(用户也可以选择预加载出第二个和第三个页面)。
2、每次滑动的时候,滑动到某个页面,只渲染加载过的页面跟需要预加载的页面,其它的页面先用一个空白页面替代。
哈哈~ 说了那么多文字性的东西,小伙伴是否已经疲惫了呢?下面我们直接撸代码了。
我们把每一个加载过的和需要加载的页面用一个对应的集合sceneKeys对应起来:
export default class ScrollableTab extends Component { static propTypes = { prerenderingSiblingsNumber: PropTypes.number,//预加载的页面 } static defaultProps = { prerenderingSiblingsNumber: 0,//不需要预加载 } // 构造 constructor(props) { super(props); // 初始状态 this.state = { containerWidth: screenW, currentPage: 0,//当前页面 scrollXAnim: new Animated.Value(0), scrollValue: new Animated.Value(0), sceneKeys: this._newSceneKeys({currentPage: 0}), }; } ...... /** * 生成需要渲染的页面跟渲染过的页面的集合 * @param previousKeys 之前的集合 * @param currentPage 当前页面 * @param children 子控件 * @private */ _newSceneKeys({previousKeys = [], currentPage = 0, children = this.props.children,}) { let newKeys = []; this._children().forEach((child, index)=> { const key = this._makeSceneKey(child, index); //页面是否渲染过||是否需要预加载 if (this._keyExists(previousKeys, key) || this._shouldSceneRender(index, currentPage)) { newKeys.push(key); } }); return newKeys; } /** * 生成唯一key * @param child 子控件 * @param index 下标 * @private */ _makeSceneKey(child, index) { return (child.props.tabLabel + '_' + index); } /** * 判断key是否存在 * @param previousKeys key集合 * @param key 当前key * @private */ _keyExists(previousKeys, key) { return (previousKeys.find((sceneKey)=>sceneKey === key)); } /** * 是否需要预加载 * @private */ _shouldSceneRender(index, currentPage) { const siblingsNumber = this.props.prerenderingSiblingsNumber; //比如当前页面为1,预加载1个,也就是我们需要显示0、1、2三个页面,所[-1<x<3] return (index < (currentPage + siblingsNumber + 1) && index > (currentPage - siblingsNumber - 1)); }
然后我们渲染子控件的时候,在我们集合中的控件我们就渲染出来,不在集合中的控件我们直接用一个空view替代(因为我们需要滑动操作)。
/** * 渲染主体内容 * @private */ _renderScrollableContent() { return ( <Animated.ScrollView ref={(ref) => { this._scrollView = ref; }} .... > {this._renderContentView()} </Animated.ScrollView> ); }
/** * 渲染子view * @private */ _renderContentView() { let scenes = []; this._children().forEach((child, index)=> { const sceneKey = this._makeSceneKey(child, index); let scene = null; if (this._keyExists(this.state.sceneKeys, sceneKey)) { scene = (child); } else { scene = (<View tabLabel={child.tabLabel}/>); } scenes.push( <View key={child.key} style={{width: this.state.containerWidth}} > {scene} </View> ); }); return scenes; }
然后我们在滑动开始和结束的时候,需要重新更新下ke yScenes,也就是重新计算我们需要渲染哪些页面:
/** * scrollview开始跟结束滑动回调 * @param e * @private */ _onMomentumScrollBeginAndEnd = (e) => { let offsetX = e.nativeEvent.contentOffset.x; let page = Math.round(offsetX / this.state.containerWidth); if (this.state.currentPage !== page) { this._updateKeyScenes(page); } } /** * 更新sceneskey和当前页面 * @param nextPage * @private */ _updateKeyScenes(nextPage) { let sceneKeys = this._newSceneKeys({previousKeys: this.state.sceneKeys, currentPage: nextPage}) this.setState({ currentPage: nextPage, sceneKeys: sceneKeys, });
好啦~~ 我们走一遍代码:
可以看到,我们页面刚渲染的时候然后滑动到第二页的时候会闪烁一下,那是因为我们加载第一个页面的时候,第二页并没有加载,而是在滑动到第二页的时候加载完毕的,所以当我们再滑回到第一第二页的时候,页面里面出来了,(这也是我们一开始说的“滑到哪加载到哪,页面加载完毕后就不需要重新加载了”)那么问题来了,说好的预加载呢? 哈哈~~ 有的!! 我们把prerenderingSiblingsNumber改为1,也就是说一进来就加载第一页跟第二页,我们再看看效果:
可以看到,当我们滑动到第二页的时候页面立马出来了,然后第二页滑动到第三页的时候页面也是立马出来了。
好啦~~ 当我们页面滑动完毕后我们有改变我们的ke y Scenes集合,但是我们点击tabview的时候是不是也得改变下我们的ke y Scenes集合呢? 我们没改变,所以我们点击一下tab切换一下:
可以看到,我们点击第一页跟第二页都有页面,然后点到第三页就空白了,这是为什么呢?? 哈哈~ 因为我们前面有设置一个预加载,所以第二页跟第一页一开始就渲染出来了,但是当我们切换到第三页的时候我们的ke yScenes还是之前的集合,所以出来的还是两个页面,所以我们在点击tab回调的地方也得重新计算下ke yScenes集合:
/** * 渲染tabview * @private */ _renderTabView() { let tabParams = { tabs: this._children().map((child)=>child.props.tabLabel), activeTab: this.state.currentPage, scrollValue: this.state.scrollValue, containerWidth: this.state.containerWidth, }; return ( <DefaultTabBar {...tabParams} style={[{width: this.state.containerWidth}]} onTabClick={(page)=>this.goToPage(page)} /> ); }
/** * 滑动到指定位置 * @param pageNum page下标 * @param scrollAnimation 是否需要动画 */ goToPage(pageNum, scrollAnimation = true) { if (this._scrollView && this._scrollView._component && this._scrollView._component.scrollTo) { this._scrollView._component.scrollTo({x: pageNum * this.state.containerWidth, scrollAnimation}); this._updateKeyScenes(pageNum); } }
然后我们把预加载去掉:
static propTypes = { prerenderingSiblingsNumber: PropTypes.number,//预加载的页面 } static defaultProps = { prerenderingSiblingsNumber: 0,//不需要预加载 }
我们再次运行代码:
可以看到,我们完美的实现了我们的需求!
最后附上本节代码:
DefaultTabBar.js:
/** * @author YASIN * @version [React-Native Pactera V01, 2017/9/5] * @date 17/2/23 * @description DefaultTabBar */import React, { Component, PropTypes,} from 'react';import { View, Text, StyleSheet, TouchableOpacity, Dimensions, Animated,} from 'react-native';const screenW = Dimensions.get('window').width;const screenH = Dimensions.get('window').height;export default class DefaultTabBar extends Component { static propTypes = { tabs: PropTypes.array, activeTab: PropTypes.number,//当前选中的tab style: View.propTypes.style, onTabClick: PropTypes.func, containerWidth: PropTypes.number, } // 构造 constructor(props) { super(props); // 初始状态 this.state = {}; } render() { let {containerWidth, tabs, scrollValue}=this.props; //给传过来的动画一个插值器 const left = scrollValue.interpolate({ inputRange: [0, 1,], outputRange: [0, containerWidth / tabs.length,], }); let tabStyle = { width: containerWidth / tabs.length, position: 'absolute', bottom: 0, left, } return ( <View style={[styles.container, this.props.style]}> {this.props.tabs.map((name, page) => { const isTabActive = this.props.activeTab === page; return this._renderTab(name, page, isTabActive); })} <Animated.View style={[styles.tabLineStyle, tabStyle]} /> </View> ); } /** * 渲染tab * @param name 名字 * @param page 下标 * @param isTabActive 是否是选中的tab * @private */ _renderTab(name, page, isTabActive) { let tabTextStyle = null; //如果被选中的style if (isTabActive) { tabTextStyle = { color: 'green' }; } else { tabTextStyle = { color: 'red' }; } let self = this; return ( <TouchableOpacity key={name + page} style={[styles.tabStyle]} onPress={()=>this.props.onTabClick(page)} > <Text style={[tabTextStyle]}>{name}</Text> </TouchableOpacity> ); }}const styles = StyleSheet.create({ container: { width: screenW, flexDirection: 'row', alignItems: 'center', height: 50, }, tabStyle: { flex: 1, alignItems: 'center', justifyContent: 'center', }, tabLineStyle: { height: 2, backgroundColor: 'navy', }});
ScrollableTab.js:
/** * @author YASIN * @version [React-Native Pactera V01, 2017/9/5] * @date 2017/9/5 * @description index */import React, { Component, PropTypes,} from 'react';import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, Animated,} from 'react-native';const screenW = Dimensions.get('window').width;const screenH = Dimensions.get('window').height;import DefaultTabBar from './DefaultTabBar';export default class ScrollableTab extends Component { static propTypes = { prerenderingSiblingsNumber: PropTypes.number,//预加载的页面 } static defaultProps = { prerenderingSiblingsNumber: 0,//不需要预加载 } // 构造 constructor(props) { super(props); // 初始状态 this.state = { containerWidth: screenW, currentPage: 0,//当前页面 scrollXAnim: new Animated.Value(0), scrollValue: new Animated.Value(0), sceneKeys: this._newSceneKeys({currentPage: 0}), }; } render() { return ( <View style={styles.container} onLayout={this._onLayout} > {/*渲染tabview*/} {this._renderTabView()} {/*渲染主体内容*/} {this._renderScrollableContent()} </View> ); } componentDidMount() { //设置scroll动画监听 this.state.scrollXAnim.addListener(({value})=> { let offset = value / this.state.containerWidth; this.state.scrollValue.setValue(offset); }); } componentWillUnMount() { //移除动画监听 this.state.scrollXAnim.removeAllListeners(); this.state.scrollValue.removeAllListeners(); } /** * 渲染tabview * @private */ _renderTabView() { let tabParams = { tabs: this._children().map((child)=>child.props.tabLabel), activeTab: this.state.currentPage, scrollValue: this.state.scrollValue, containerWidth: this.state.containerWidth, }; return ( <DefaultTabBar {...tabParams} style={[{width: this.state.containerWidth}]} onTabClick={(page)=>this.goToPage(page)} /> ); } /** * 渲染主体内容 * @private */ _renderScrollableContent() { return ( <Animated.ScrollView ref={(ref) => { this._scrollView = ref; }} style={{width: this.state.containerWidth}} pagingEnabled={true} horizontal={true} onMomentumScrollBegin={this._onMomentumScrollBeginAndEnd} onMomentumScrollEnd={this._onMomentumScrollBeginAndEnd} scrollEventThrottle={15} onScroll={Animated.event([{ nativeEvent: {contentOffset: {x: this.state.scrollXAnim}} }], { useNativeDriver: true, })} bounces={false} scrollsToTop={false} > {this._renderContentView()} </Animated.ScrollView> ); } /** * 渲染子view * @private */ _renderContentView() { let scenes = []; this._children().forEach((child, index)=> { const sceneKey = this._makeSceneKey(child, index); let scene = null; if (this._keyExists(this.state.sceneKeys, sceneKey)) { scene = (child); } else { scene = (<View tabLabel={child.tabLabel}/>); } scenes.push( <View key={child.key} style={{width: this.state.containerWidth}} > {scene} </View> ); }); return scenes; } /** * 获取子控件数组集合 * @param children * @returns {*} * @private */ _children(children = this.props.children) { return React.Children.map(children, (child)=>child); } /** * 获取控件宽度 * @param e * @private */ _onLayout = (e)=> { let {width}=e.nativeEvent.layout; if (this.state.containerWidth !== width) { this.setState({ containerWidth: width, }); } } /** * scrollview开始跟结束滑动回调 * @param e * @private */ _onMomentumScrollBeginAndEnd = (e) => { let offsetX = e.nativeEvent.contentOffset.x; let page = Math.round(offsetX / this.state.containerWidth); if (this.state.currentPage !== page) { this._updateKeyScenes(page); } } /** * 更新sceneskey和当前页面 * @param nextPage * @private */ _updateKeyScenes(nextPage) { let sceneKeys = this._newSceneKeys({previousKeys: this.state.sceneKeys, currentPage: nextPage}) this.setState({ currentPage: nextPage, sceneKeys: sceneKeys, }); } /** * 滑动到指定位置 * @param pageNum page下标 * @param scrollAnimation 是否需要动画 */ goToPage(pageNum, scrollAnimation = true) { if (this._scrollView && this._scrollView._component && this._scrollView._component.scrollTo) { this._scrollView._component.scrollTo({x: pageNum * this.state.containerWidth, scrollAnimation}); this._updateKeyScenes(pageNum); } } /** * 生成需要渲染的页面跟渲染过的页面的集合 * @param previousKeys 之前的集合 * @param currentPage 当前页面 * @param children 子控件 * @private */ _newSceneKeys({previousKeys = [], currentPage = 0, children = this.props.children,}) { let newKeys = []; this._children().forEach((child, index)=> { const key = this._makeSceneKey(child, index); //页面是否渲染过||是否需要预加载 if (this._keyExists(previousKeys, key) || this._shouldSceneRender(index, currentPage)) { newKeys.push(key); } }); return newKeys; } /** * 生成唯一key * @param child 子控件 * @param index 下标 * @private */ _makeSceneKey(child, index) { return (child.props.tabLabel + '_' + index); } /** * 判断key是否存在 * @param previousKeys key集合 * @param key 当前key * @private */ _keyExists(previousKeys, key) { return (previousKeys.find((sceneKey)=>sceneKey === key)); } /** * 是否需要预加载 * @private */ _shouldSceneRender(index, currentPage) { const siblingsNumber = this.props.prerenderingSiblingsNumber; //比如当前页面为1,预加载1个,也就是我们需要显示0、1、2三个页面,所[-1<x<3] return (index < (currentPage + siblingsNumber + 1) && index > (currentPage - siblingsNumber - 1)); }}const styles = StyleSheet.create({ container: { width: screenW, flex: 1, marginTop: 22, },});
下一节我们去实现一下ScrollableTabBar.js,也就是说上面的tabview需要映射底部的scrollview滑动,让tabview跟随底部滑动而滑动,小伙伴可以先思考思考哦。
欢迎入群,欢迎交流,大牛勿喷,下一节见!!
- React Native带你实现scrollable-tab-view(四)
- React Native带你实现scrollable-tab-view(一)
- React Native带你实现scrollable-tab-view(二)
- React Native带你实现scrollable-tab-view(三)
- React Native带你实现scrollable-tab-view(五)
- React Native带你实现scrollable-tab-view(完结)
- React:react-native-scrollable-tab-view
- ReactNative组件-react-native-scrollable-tab-view
- react-native-scrollable-tab-view 使用总结
- react-native-scrollable-tab-view详解
- 选项卡react-native-scrollable-tab-view
- [React Native]react-native-scrollable-tab-view(入门篇)
- [React Native]react-native-scrollable-tab-view(进阶篇)
- React Native之react-native-scrollable-tab-view详解
- [React Native]react-native-scrollable-tab-view(入门篇)
- [React Native]react-native-scrollable-tab-view(入门篇)
- 选项卡react-native-scrollable-tab-view(入门篇)
- 选项卡react-native-scrollable-tab-view(进阶篇)
- Android图片压缩利器——Luban(鲁班),智商二百五……
- Java计算矩形的面积和周长
- 字典
- python之单例模式
- UnityShader从入门到放弃(一)UnityShader的结构
- React Native带你实现scrollable-tab-view(四)
- 《笨办法学python》加分习题36——我的答案
- 数据结构与算法:STL容器
- 系统大文件上传控制
- python字符串编码测试
- Educational Codeforces Round 28 C 降低复杂度的方式
- jackson工具类json转list、json转map、json转bean、bean转json
- UVA.580 Critical Mass (递推)
- 121. 单次购买股票问题