ReactNative 導航

rgav7825 8年前發布 | 10K 次閱讀 ReactNative 移動開發 React Native

導航是 RN 中比較重要的一個模塊,官方最開始推出 Navigator,因性能問題社區開發了 NavigatorIOS,但不靈活,而且沒有 Android 的實現,RN 團隊一直在醞釀一個 NavigationExperimental,最近基于此推出了 React Navigation,可同時驅動 Native 和 Web。React Navigation 是目前一個比較成熟的實現,在性能與靈活性上有很大改進。這篇博客主要講 React Navigation 的棧結構和轉場動畫的實現。

視圖棧

RN 頁面只有一個視圖,View 套 View 一層層套下去,App 應用要求分頁展示,RN 需要一種方式在一個 View 內產生多個頁面,如下圖示:

最頂層的 View 用于統籌應用,我們的業務都寫在下層子 view 上,對這種模式再抽象一下,得到一種棧結構,如下圖示:

index 表示當前激活的頁面, routes 內存子視圖

  • 打開一個新的頁面, routes.push(NewView); index++ 。

  • 關閉當前頁, routes.pop(); index-- 。

操作視圖棧即操作數組,對數組的任何操作,都能體現在導航視圖上。

動畫

React Navigation 的轉場動畫依賴內建動畫模塊 Animated ,如下圖示動畫:

實現以上動畫效果并不需要多少代碼,如下示例完整的代碼實現:

import React from 'react';  
import {  
    AppRegistry,
    View,
    Text,
    StyleSheet,
    Animated,
    Dimensions,
} from 'react-native';

const {width} = Dimensions.get('window');

class SimpleApp extends React.Component {

    animatedValue = new Animated.Value(width);

    onPress = () => {
        Animated.timing(this.animatedValue, {
            toValue: 0,
            duration: 1000,
            useNativeDriver: true
        }).start();
    };

    render() {
        return (
            <View style={styles.container}>
                <View style={styles.scene}>
                    <Text style={styles.text} onPress={this.onPress}>
                        Start
                    </Text>
                </View>
                <Animated.View style={[styles.scene, styles.animated, {transform: [{
                    translateX: this.animatedValue
                }]}]} />
            </View>
        );
    }
}

const styles = StyleSheet.create({  
    container: {
        flex: 1
    },
    scene: {
        position: 'absolute',
        top: 0,
        right: 0,
        bottom: 0,
        left: 0,
        flexDirection: 'row'
    },
    animated: {
        backgroundColor: '#f5f5f5',
        shadowColor: 'black',
        shadowOffset: { width: 0, height: 0 },
        shadowOpacity: 0.4,
        shadowRadius: 10,
    },
    text: {
        flex: 1,
        height: 44,
        lineHeight: 44,
        backgroundColor: 'red',
        color: '#fff',
        textAlign: 'center',
        alignSelf: 'center'
    }
});

AppRegistry.registerComponent('SimpleApp', () => SimpleApp);

導航棧與動畫結合

導航棧與轉場動畫的結合是 Navigation 的關鍵,我們通過一個簡單的示例來了解其實現原理:

核心代碼如下:

class SimpleApp extends React.Component {  
    state = {
        index: 0,
        routes: [{
            scene: Card
        }]
    };

    animateValue = new Animated.Value(0);

    push = () => {
        this.setState({
            index: this.state.index + 1,
            routes: this.state.routes.concat({
                scene: Card
            })
        }, () => {
            Animated.timing(this.animateValue, {
                toValue: this.state.index,
                duration: 500
            }).start();
        });
    };

    render() {
        return (
            <View style={styles.container}>
                {this.state.routes.map((v, i) => 
                    <Animated.View key={i} style={[styles.card,
                        {transform: [{
                            'translateX': this.animateValue.interpolate({
                                inputRange: [i - 1, i, i + 1],
                                outputRange: [width, 0, -width]
                            })
                        }]}]}>
                        <v.scene push={this.push} index={i}/>
                    </Animated.View>
                )}
            </View>
        );
    }
}

最重要的是 this.animatedValue.interpolate , 插值函數非常巧妙的為視圖棧內的不同視圖分配不同的插值

  • 棧內第一個視圖插值 inputRange:[-1, 0, 1] outputRange[width, 0, -width]
  • 棧內第二個視圖插值 inputRange:[0, 1, 2] outputRange[width, 0, -width]

當 index 為 0 的時候

  • 棧內第一個視圖輸出值 translateX: 0 【視口】
  • 棧內第二個視圖輸出值 translateX: width 【視口右邊不可見】

當 index 為 1 的時候

  • 棧內第一個視圖輸出值 translateX: -width 【視口左邊不可見】
  • 棧內第二個視圖輸出值 translateX: 0 【視口】

以此類推......

如下示例完整的代碼實現

import React from 'react';  
import {  
    AppRegistry,
    View,
    Text,
    StyleSheet,
    Dimensions,
    Animated
} from 'react-native';

const width = Dimensions.get('window').width;

const Card = (props) => {  
    return (
        <View style={styles.cardContainer}>
            <Text style={styles.text}>
                {props.index}
            </Text>
            <Text style={styles.text} onPress={props.push}>
                push
            </Text>
        </View>
    );
};

class SimpleApp extends React.Component {  
    state = {
        index: 0,
        routes: [{
            scene: Card
        }]
    };

    animateValue = new Animated.Value(0);

    push = () => {
        this.setState({
            index: this.state.index + 1,
            routes: this.state.routes.concat({
                scene: Card
            })
        }, () => {
            Animated.timing(this.animateValue, {
                toValue: this.state.index,
                duration: 500
            }).start();
        });
    };

    render() {
        return (
            <View style={styles.container}>
                {this.state.routes.map((v, i) => 
                    <Animated.View key={i} style={[styles.card,
                        {transform: [{
                            'translateX': this.animateValue.interpolate({
                                inputRange: [i - 1, i, i + 1],
                                outputRange: [width, 0, -width]
                            })
                        }]}]}>
                        <v.scene push={this.push} index={i}/>
                    </Animated.View>
                )}
            </View>
        );
    }
}

const styles = StyleSheet.create({  
    container: {
        flex: 1,
    },
    cardContainer: {
        flex: 1,
        alignSelf: 'center'
    },
    card: {
        position: 'absolute',
        top: 0,
        right: 0,
        bottom: 0,
        left: 0,
        flexDirection: 'row',
        shadowColor: 'black',
        shadowOffset: { width: 0, height: 0 },
        shadowOpacity: 0.4,
        shadowRadius: 10,
    },
    text: {
        height: 44,
        lineHeight: 44,
        backgroundColor: 'red',
        color: '#fff',
        textAlign: 'center',
        marginTop: 32
    }
});

AppRegistry.registerComponent('SimpleApp', () => SimpleApp);

 

來自:https://blog.souche.com/reactnative-dao-hang/

 

 本文由用戶 rgav7825 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!