React Native带你实现scrollable-tab-view(五)

来源:互联网 发布:excel密码破解软件 编辑:程序博客网 时间:2024/06/08 18:00

上一节React Native带你实现scrollable-tab-view(三)中我们最后实现了我们scrollable-tab-view的效果为:
这里写图片描述

比如有很多页面,我们的tabview需要跟随scrollview的滑动而滑动:

这里写图片描述

我们现在用的还是默认的tabview,比如我们多加一些页面,
这里写图片描述
这样肯定不行,我们需要的是上面的那种效果,所以每个view的flex=1这种模式肯定不适合了,我们需要用一个scrollview去包涵tab,使它也能滑动,所以也算是scrollable-tab-view的一个难点了,好啦~ 我们来一步一步实现一下scrollable-tab-view中的ScrollableTabBar.js。

我们先创建一个view叫ScrollableTabBar.js:

/** * @author YASIN * @version [React-Native Pactera V01, 2017/9/7] * @date 17/2/23 * @description ScrollableTabBar */import React, {    Component} from 'react';import {    View,    Text,    StyleSheet,} from 'react-native';export default class ScrollableTabBar extends Component {    // 构造    constructor(props) {        super(props);        // 初始状态        this.state = {};    }    render() {        return (            <View>                <Text>ScrollableTabBar</Text>            </View>        );    }}const styles = StyleSheet.create({    container: {}});

然后替换掉index文件中的DefaultTabBar组件:

  /**     * 渲染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 (            <ScrollableTabBar                {...tabParams}                style={[{width: this.state.containerWidth}]}                onTabClick={(page)=>this.goToPage(page)}            />        );    }

这里写图片描述

然后我们开始实现ScrollableTabBar组件:

我们用一个scrollview去包裹tab,然后其它的东西直接copy一份DefaultTabBar的内容:

/** * @author YASIN * @version [React-Native Pactera V01, 2017/9/7] * @date 17/2/23 * @description ScrollableTabBar */import React, {    Component} from 'react';import {    View,    Text,    StyleSheet,    Dimensions,    TouchableOpacity,    ScrollView,    Animated,} from 'react-native';const screenW = Dimensions.get('window').width;const screenH = Dimensions.get('window').height;export default class ScrollableTabBar extends Component {    // 构造    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: 50,            position: 'absolute',            bottom: 0,            left,        };        return (            <View style={[styles.container, this.props.style]}>                <ScrollView                    automaticallyAdjustContentInsets={false}                    ref={(scrollView) => {                        this._scrollView = scrollView;                    }}                    horizontal={true}                    showsHorizontalScrollIndicator={false}                    showsVerticalScrollIndicator={false}                    directionalLockEnabled={true}                    bounces={false}                    scrollsToTop={false}                >                    <View                        style={styles.tabContainer}                    >                        {tabs.map((tab, index)=> {                            return this._renderTab(tab, index, index === this.props.activeTab);                        })}                        <Animated.View                            style={[styles.tabLineStyle, tabStyle]}                        />                    </View>                </ScrollView>            </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,        height: 50,    },    tabContainer: {        flexDirection: 'row',        alignItems: 'center',    },    tabLineStyle: {        height: 1,        backgroundColor: 'navy',    },    tabStyle: {        height: 49,        alignItems: 'center',        justifyContent: 'center',        paddingHorizontal: 20,    },});

然后我们运行代码:
这里写图片描述

可以看到,有点那种感觉了,但是我们目前看到的东西需要解决的就是:
1、底部那个线条长度计算(目前是定死了一个值)

 render() {       ....        let tabStyle = {            width: 50,            position: 'absolute',            bottom: 0,            left,        };        ...

我们线条的长度应该等于当前tab的宽度。

2、我们页面滑动的时候,线条指向有问题。

好了~ 我们一个一个的解决,我们把下标线view的left跟宽度用两个单独的动画控制,然后监听内容scrollview的scrollValue动画滑动改变控制left和控制width的动画值:

export default class ScrollableTabBar extends Component {    // 构造    constructor(props) {        super(props);        // 初始状态        this.state = {            _leftTabUnderline: new Animated.Value(0),            _widthTabUnderline: new Animated.Value(0),        };    }    render() {        let {containerWidth, tabs, scrollValue}=this.props;        //给传过来的动画一个插值器        let tabStyle = {            width: this.state._widthTabUnderline,            position: 'absolute',            bottom: 0,            left: this.state._leftTabUnderline,        };        return (            <View style={[styles.container, this.props.style]}>                <ScrollView                    ....                >                    <View                        style={styles.tabContainer}                    >                        ...                        <Animated.View                            style={[styles.tabLineStyle, tabStyle]}                        />                    </View>                </ScrollView>            </View>        );    }

然后跟前一节我们实现的DefaultTabBar.js一样,监听scrollValue动画:

componentDidMount() {        this.props.scrollValue.addListener(this._updateView);    }
 /**     * 根据scrollview的滑动改变底部线条的宽度跟left     * @param value     * @private     */    _updateView = ({value = 0}) => {        //因为value 的值是[0-1-2-3-4]变换        const position = Math.floor(value);        //取小数部分        const offset = value % 1;        const tabCount = this.props.tabs.length;        const lastTabPosition = tabCount - 1;        //如果没有tab||(有bounce效果)直接return        if (tabCount === 0 || value < 0 || value > lastTabPosition) {            return;        }        console.log('position==>' + position + ' offset==>' + offset);    }

我们运行然后从第一页滑动到第二页:
这里写图片描述

可以看到,我们position是从(0-1)而offset为0–>1的一个小数,所以我们:
1、底部线条view的宽度应该为(第position页tab宽度)*(1-offset)+(第positon+1页tab的宽度)*offset。
2、底部线条view的left应该为(第position页tab的x轴)+(第position+1页tab的宽度)*offset。

所以我们接下来要做的就是通过o nLayout方法计算出每个tab的x轴跟宽度然后保存起来。

我们在渲染tabbar的时候提供view o nLayout方法去测量view:

 /**     * 渲染tab     * @param name 名字     * @param page 下标     * @param isTabActive 是否是选中的tab     * @private     */    _renderTab(name, page, isTabActive) {        ....        let self = this;        return (            <TouchableOpacity               .....                onLayout={(event)=>this._onMeasureTab(page, event)}            >                <Text style={[tabTextStyle]}>{name}</Text>            </TouchableOpacity>        );    }

每次测量完毕后去更新tab底部线view的宽度:

/**     * 测量tabview     * @param page 页面下标     * @param event 事件     * @private     */    _onMeasureTab(page, event) {        let {nativeEvent:{layout:{x, width}}}=event;        this._tabsMeasurements[page] = {left: x, right: width + x, width: width};        this._updateView({value: this.props.scrollValue._value})    }

按照我们前面所说的“1、底部线条view的宽度应该为(第position页tab宽度)*(1-offset)+(第positon+1页tab的宽度)*offset。”所以我们必须要拿到tab的测量值,并且有下一个tab,或者特殊情况就是到最后一个tab了,最后才去做改变width的操作。

 /**     * 根据scrollview的滑动改变底部线条的宽度跟left     * @param value     * @private     */    _updateView = ({value = 0}) => {        //因为value 的值是[0-1-2-3-4]变换        const position = Math.floor(value);        //取小数部分        const offset = value % 1;        const tabCount = this.props.tabs.length;        const lastTabPosition = tabCount - 1;        //如果没有tab||(有bounce效果)直接return        if (tabCount === 0 || value < 0 || value > lastTabPosition) {            return;        }        if (this._necessarilyMeasurementsCompleted(position, position === tabCount - 1)) {            this._updateTabLine(position, offset);        }    }
  /**     * 判断是否需要跟新的条件是否初始化     * @param position     * @param isLast 是否是最后一个     * @private     */    _necessarilyMeasurementsCompleted(position, isLast) {        return (            this._tabsMeasurements[position] &&            (isLast || this._tabsMeasurements[position + 1])        );    }

然后套去我们前面说的公式:

(第position页tab宽度)*(1-offset)+(第positon+1页tab的宽度)*offset
但是有一种特殊情况,就是position+1不存在,所以我们要做好判断:

 /**     * 更新底部线view的left跟width     * @param position     * @param offset     * @private     */    _updateTabLine(position, offset) {        //当前tab的测量值        const currMeasure = this._tabsMeasurements[position];        //position+1的tab的测量值        const nextMeasure = this._tabsMeasurements[position + 1];        let width = currMeasure.width * (1 - offset);        if (nextMeasure) {            width += nextMeasure.width * offset;        }        this.state._widthTabUnderline.setValue(width);    }

然后我们运行代码:
这里写图片描述

可以看到,我们滑动的时候tab底部线条的宽度再改变,宽度=我们的tab的宽度,好啦!! 我们接着只要使线条view的left跟着scrollview滑动而改变到对应的tab位置就可以了。

我们继续套入公式:

(第position页tab的x轴)+(第position+1页tab的宽度)*offset

/**     * 更新底部线view的left跟width     * @param position     * @param offset     * @private     */    _updateTabLine(position, offset) {        //当前tab的测量值        const currMeasure = this._tabsMeasurements[position];        //position+1的tab的测量值        const nextMeasure = this._tabsMeasurements[position + 1];        let width = currMeasure.width * (1 - offset);        let left= currMeasure.left;        if (nextMeasure) {            width += nextMeasure.width * offset;            left+=nextMeasure.width*offset;        }        this.state._leftTabUnderline.setValue(left);        this.state._widthTabUnderline.setValue(width);    }

然后运行代码:

这里写图片描述

好啦! 可以看到,我们底部线条view可以跟随了,到这我们还差最后一步,那就是当我们点击tab的时候如果后面的tab被遮住了就得向后移动。

有点晚了,该洗洗睡啦~~

欢迎入群,欢迎交流,大牛勿喷,下一节见!

原创粉丝点击