【React Native】React Native項目設計與知識點分享

  閒暇之餘,寫了一個React Native的demo,能夠做爲你們的入門學習參考。html

  GitHub:https://github.com/xujianfu/ElmApp.git前端

  GitHub:https://github.com/xujianfu/React-Native-CarProject.gitreact

  項目截圖以下:git

 

 1、項目界面設計

  一、React Navigation的應用

   React Navigation 源於 React Native 社區對一個可擴展且易於使用的導航解決方案的需求,它徹底使用 JavaScript 編寫(所以你能夠閱讀並理解全部源碼)。支持iOS/Android.github

   一、如何在項目進行使用?

yarn add react-navigation
# or with npm
# npm install --save react-navigation

  而後,安裝 react-native-gesture-handler。 若是你正在使用 Expo managed workflow,那麼你什麼都不須要作, SDK 中已經包含了這些. 不然:  npm

yarn add react-native-gesture-handler
# or with npm
# npm install --save react-native-gesture-handler

  最後進行Link 全部的原生依賴react-native

react-native link react-native-gesture-handler

  二、路由配置

  爲某個模塊建立StackNavigator導航前端框架

const HomeStack = createStackNavigator(
    {
        Home:{
            screen:HomeScreen,
            navigationOptions:()=>({
                headerBackTitle: null,
            })
        },
         //添加多個路由
        CarLoans:CarLoansScreen,
        CheckRules:CheckRulesScreen,
    },
)

......    

  將多個模塊添加到TabNavigator上app

const TabNavigator = createBottomTabNavigator(
    {
        Home:{
            screen:HomeStack,
            navigationOptions:({navigation}) => ({
                tabBarLabel:'首頁',
                tabBarIcon:({focused}) => (
                    <Image source={{uri:focused ? 'ic_tab_home_h':'ic_tab_home_n.png'}} style={styles.iconStyle}/>
                ),
            }),
        },
        Mall:{
            screen:MallStack,
            navigationOptions:({navigation}) => ({
                tabBarLabel:'商城',
                tabBarIcon:({focused}) => (
                    <Image source={{uri:focused ? 'ic_tab_mall_h':'ic_tab_mall_n.png'}} style={styles.iconStyle}/>
                )
            }),
        },
        Publish:{
            screen:PublishStack,
            navigationOptions:({navigation}) => ({
                tabBarLabel:'發佈',
                tabBarIcon:({focused}) => (
                    <Image source={{uri:focused ? 'ic_tab_release_h':'ic_tab_release_n.png'}} style={styles.iconStyle}/>
                )
            }),
        },
        Discover:{
            screen:DiscoverStack,
            navigationOptions:({navigation}) => ({
                tabBarLabel:'發現',
                tabBarIcon:({focused}) => (
                    <Image source={{uri:focused ? 'ic_tab_find_h':'ic_tab_find_n.png'}} style={styles.iconStyle}/>
                )
            }),
        },
        Mine:{
            screen:MineStack,
            navigationOptions:({navigation}) => ({
                tabBarLabel:'個人',
                tabBarIcon:({focused}) => (
                    <Image source={{uri:focused ? 'ic_tab_my_h':'ic_tab_my_n.png'}} style={styles.iconStyle}/>
                )
            }),
        },
    },
    {
        defaultNavigationOptions: ({ navigation }) => {
            let tabBarVisible = true;
            if (navigation.state.index > 0) {
                tabBarVisible = false;
            }
            return {
                tabBarVisible,
            };
        },
        tabBarPosition:'bottom',
        tabBarOptions: {
            activeTintColor: 'blue', //選中tabbar的文字顏色
            inactiveTintColor: 'gray',
            showIcon:true,

        },

    }
);

export default  createAppContainer(TabNavigator);

  二、選擇相冊照片或視頻,或進行拍照

  (1)引入react-native-image-picker框架

yarn add react-native-image-picker
react-native link react-native-image-picker

  (2)在項目中使用react-native-image-picker

import ImagePicker from 'react-native-image-picker';

//選擇圖片
    selectPhotoTapped() {
        const options = {
            // 彈窗標題
            title: '選擇圖片',
            cancelButtonTitle: '取消',
            takePhotoButtonTitle: '拍照',
            chooseFromLibraryButtonTitle: '選擇照片',
            // 自定義按鈕
            customButtons: [
                {name: 'fb', title: 'Choose Photo from Facebook'},
            ],
            // 相機類型'front' 或者 'back'
            cameraType: 'back',
            // 圖片或視頻:'photo','video'
            mediaType: 'photo',
            // 視頻質量
            videoQuality: 'high',
            //最大視頻錄製時間
            durationLimit: 10,
            //最長寬
            maxWidth: 300,
            //最長高,
            maxHeight: 300,
            //圖片質量
            quality: 0.8,
            angle: 0,
            //是否能夠編輯
            allowsEditing: false,
            //若是爲真,則禁用data生成的base64字段
            noData: false,
            // 若是提供此密鑰,該圖像將被保存在Documents iOS 應用程序的目錄中,或者保存在PicturesAndroid上的應用程序目錄(而不是臨時目錄)
            storageOptions: {
                skipBackup: true
            }
        };

        ImagePicker.showImagePicker(options, (response) => {
            console.log('Response = ', response);

            if (response.didCancel) {
                console.log('User cancelled photo picker');
            }
            else if (response.error) {
                console.log('ImagePicker Error: ', response.error);
            }
            else if (response.customButton) {
                console.log('User tapped custom button: ', response.customButton);
            }
            else {
                let source = { uri: response.uri };

                // You can also display the image using data:
                // let source = { uri: 'data:image/jpeg;base64,' + response.data };

                this.setState({
                    avatarSource: source
                });
            }
        });
    }

    //選擇視頻
    selectVideoTapped() {
        const options = {

            title: '選擇視頻',
            cancelButtonTitle: '取消',
            takePhotoButtonTitle: '錄製視頻',
            chooseFromLibraryButtonTitle: '選擇視頻',
            mediaType: 'video',
            videoQuality: 'medium'
        };

        ImagePicker.showImagePicker(options, (response) => {
            console.log('Response = ', response);

            if (response.didCancel) {
                console.log('User cancelled video picker');
            }
            else if (response.error) {
                console.log('ImagePicker Error: ', response.error);
            }
            else if (response.customButton) {
                console.log('User tapped custom button: ', response.customButton);
            }
            else {
                this.setState({
                    videoSource: response.uri
                });
            }
        });
    }

 三、建立切換選項卡

  導入react-native-scrollable-tab-view

npm install react-native-scrollable-tab-view --save

  項目中引入

//引用插件
import ScrollableTabView, { ScrollableTabBar, DefaultTabBar } from 'react-native-scrollable-tab-view';

<ScrollableTabView
                    initialPage={0}
                    renderTabBar={() => <ScrollableTabBar style={{borderBottomWidth: 0,height: 44}}/>}
                    tabBarTextStyle={{fontSize:16}}
                    tabBarActiveTextColor={'#fdd000'}
                    tabBarInactiveTextColor={'#999999'}
                    tabBarUnderlineStyle={{backgroundColor:'#fdd000'}}
                >
                    {
                        label.map((item,index) =>{
                            if (index === 0) {
                                return <AllBusinessScreen tabLabel={item} key={index}/>
                            } else {
                                return <NearByBusinessScreen tabLabel={item} key={index}/>
                            }
                        })
                    }
                </ScrollableTabView>

  四、使用Modal組件

  Modal組件能夠用來覆蓋包含React Native根視圖的原生視圖(如UIViewController,Activity)。在嵌入React Native的混合應用中可使用Modal。Modal可使你應用中RN編寫的那部份內容覆蓋在原生視圖上顯示。  

<Modal
                    animationType={"slide"}
                    transparent={true}
                    visible={this.state.modalVisible}
                    onRequestClose={()=>{alert('modal has been closed')}}
                >
                        <View style={styles.modalStyle}>
                            <View style={styles.coverStyle}>
                                {this.renderItem()}
                            </View>
                        </View>


                </Modal>


......

renderItem(){
        let itemTitleArr = ['京','滬','浙','蘇','粵','魯','晉','冀',
            '豫','川','渝','遼','吉','黑','皖','鄂',
            '湘','贛','閩','陝','甘','寧','蒙','津',
            '貴','雲','桂','瓊','青','新','藏'];;
        var itemArr = [];
        for (var i = 0; i < itemTitleArr.length; i++) {
            itemArr.push(
                <TouchableHighlight onPress={this.callBack.bind(this,itemTitleArr[i])} key={i}>
                    <View style={styles.chooseItemStyle} >
                        <Text style={styles.chooseTitleStyle}>{itemTitleArr[i]}</Text>
                    </View>
                </TouchableHighlight>

            )
        }
        return itemArr;
    }

  五、下拉列表實現

import React, {Component} from 'react';
import {View, Text, Image, TouchableOpacity, ScrollView, Animated, Easing, StyleSheet} from 'react-native';
import PropTypes from 'prop-types';

class DropdownMenu extends Component {

    constructor(props, context) {
        super(props, context);

        var selectIndex = new Array(this.props.data.length);
        for (var i = 0; i < selectIndex.length; i++) {
            selectIndex[i] = 0;
        }
        this.state = {
            activityIndex: -1,
            selectIndex: selectIndex,
            rotationAnims: props.data.map(() => new Animated.Value(0))
        };

        this.defaultConfig = {
            bgColor: '#f5f5f5',
            tintColor: '#fdd000',
            activityTintColor: "red",
            arrowImg: 'ic_nav_down',
            checkImage: 'ic_nav_down'
        };

    }

    renderChcek(index, title) {
        var activityIndex = this.state.activityIndex;
        if (this.state.selectIndex[activityIndex] === index) {
            var checkImage = this.props.checkImage ? this.props.checkImage : this.defaultConfig.checkImage;
            return (
                <View style={{flex: 1, justifyContent: 'space-between', alignItems: "center", paddingHorizontal: 15, flexDirection: 'row'}} >
                    <Text
                        style={[
                            styles.item_text_style,
                            this.props.optionTextStyle,
                            {color: this.props.activityTintColor ? this.props.activityTintColor : this.defaultConfig.activityTintColor}
                        ]} >
                        {title}
                    </Text>
                    <Image
                        source={checkImage}
                        style={{tintColor: this.props.activityTintColor ? this.props.activityTintColor : this.defaultConfig.activityTintColor}} />
                </View>
            );
        } else {
            return (
                <View style={{flex: 1, justifyContent: 'space-between', alignItems: "center", paddingHorizontal: 15, flexDirection: 'row'}} >
                    <Text style={[
                        styles.item_text_style,
                        this.props.optionTextStyle,
                        {color: this.props.tintColor ? this.props.tintColor : this.defaultConfig.tintColor}
                    ]} >{title}</Text>
                </View>
            );
        }
    }

    renderActivityPanel() {
        if (this.state.activityIndex >= 0) {

            var currentTitles = this.props.data[this.state.activityIndex];

            var heightStyle = {};
            if (this.props.maxHeight && this.props.maxHeight < currentTitles.length * 44) {
                heightStyle.height = this.props.maxHeight;
            }

            return (
                <View style={{position: 'absolute', left: 0, right: 0, top: 40, bottom: 0}}>
                    <TouchableOpacity onPress={() => this.openOrClosePanel(this.state.activityIndex)} activeOpacity={1} style={{position: 'absolute', left: 0, right: 0, top: 0, bottom: 0}}>
                        <View style={{opacity: 0.4, backgroundColor: 'black', flex: 1 }} />
                    </TouchableOpacity>

                    <ScrollView style={[{position: 'absolute', top: 0, left: 0, right: 0, backgroundColor: 'white'}, heightStyle]} >
                        {
                            currentTitles.map((title, index) =>
                                <TouchableOpacity key={index} activeOpacity={1} style={{flex: 1, height: 44}} onPress={this.itemOnPress.bind(this, index)} >
                                    {this.renderChcek(index, title)}
                                    <View style={{backgroundColor: '#F6F6F6', height: 1, marginLeft: 15}} />
                                </TouchableOpacity>
                            )
                        }
                    </ScrollView>
                </View>
            );
        } else {
            return (null);
        }
    }

    openOrClosePanel(index) {

        this.props.bannerAction ? this.props.bannerAction() : null;

        // var toValue = 0.5;
        if (this.state.activityIndex == index) {
            this.closePanel(index);
            this.setState({
                activityIndex: -1,
            });
            // toValue = 0;
        } else {
            if (this.state.activityIndex > -1) {
                this.closePanel(this.state.activityIndex);
            }
            this.openPanel(index);
            this.setState({
                activityIndex: index,
            });
            // toValue = 0.5;
        }
        // Animated.timing(
        //   this.state.rotationAnims[index],
        //   {
        //     toValue: toValue,
        //     duration: 300,
        //     easing: Easing.linear
        //   }
        // ).start();
    }

    openPanel(index) {
        Animated.timing(
            this.state.rotationAnims[index],
            {
                toValue: 0.5,
                duration: 300,
                easing: Easing.linear
            }
        ).start();
    }

    closePanel(index) {
        Animated.timing(
            this.state.rotationAnims[index],
            {
                toValue: 0,
                duration: 300,
                easing: Easing.linear
            }
        ).start();
    }

    itemOnPress(index) {
        if (this.state.activityIndex > -1) {
            var selectIndex = this.state.selectIndex;
            selectIndex[this.state.activityIndex] = index;
            this.setState({
                selectIndex: selectIndex
            });
            if (this.props.handler) {
                this.props.handler(this.state.activityIndex, index);
            }
        }
        this.openOrClosePanel(this.state.activityIndex);
    }

    renderDropDownArrow(index) {
        var icon = this.props.arrowImg ? this.props.arrowImg : this.defaultConfig.arrowImg;
        return (
            <Animated.Image
                source={{uri:icon}}
                style={{
                    width:6,
                    height:4,
                    marginLeft: 8,
                    tintColor: (index === this.state.activityIndex) ? (this.props.activityTintColor ? this.props.activityTintColor : this.defaultConfig.activityTintColor) : (this.props.tintColor ? this.props.tintColor : this.defaultConfig.tintColor),
                    transform: [{
                        rotateZ: this.state.rotationAnims[index].interpolate({
                            inputRange: [0, 1],
                            outputRange: ['0deg', '360deg']
                        })
                    }]
                }} />
        );
    }

    render() {

        return (
            <View style={{flexDirection: 'column', flex: 1}} >
                <View style={{
                    flexDirection: 'row',
                    backgroundColor: this.props.bgColor ? this.props.bgColor : this.defaultConfig.bgColor}} >
                    {
                        this.props.data.map((rows, index) =>
                            <TouchableOpacity
                                activeOpacity={1}
                                onPress={this.openOrClosePanel.bind(this, index)}
                                key={index}
                                style={{flex: 1, height: 48, alignItems: "center", justifyContent: "center"}} >
                                <View style={{flexDirection: 'row', alignItems: "center", justifyContent: "center"}} >
                                    <Text
                                        style={[
                                            styles.title_style,
                                            this.props.titleStyle,
                                            {color: (index === this.state.activityIndex) ?
                                                    (this.props.activityTintColor ? this.props.activityTintColor : this.defaultConfig.activityTintColor)
                                                    :
                                                    (this.props.tintColor ? this.props.tintColor : this.defaultConfig.tintColor)}
                                        ]} >
                                        {rows[this.state.selectIndex[index]]}
                                    </Text>
                                    {this.renderDropDownArrow(index)}
                                </View>
                            </TouchableOpacity>
                        )
                    }
                </View>
                {this.props.children}

                {this.renderActivityPanel()}

            </View>
        );
    }

}

DropdownMenu.propTypes = {
    bgColor: PropTypes.string,
    tintColor: PropTypes.string,
    activityTintColor: PropTypes.string,
    arrowImg: PropTypes.number,
    checkImage: PropTypes.number,
    data: PropTypes.array,
    bannerAction: PropTypes.func,
    optionTextStyle: PropTypes.object,
    titleStyle: PropTypes.object,
    maxHeight: PropTypes.number
}

const styles = StyleSheet.create({
    title_style: {
        fontSize: 16
    },
    item_text_style: {
        color: '#fdd000',
        fontSize: 16
    }
});

export default DropdownMenu;
下拉列表封裝

  如何使用?

render() {
        var data = [["分類", "分類", "分類", "分類"], ["價格", "價格"], ["篩選", "篩選"]];
        return (
            <View style={{flex: 1}}>
                <View style={styles.dropMenu}/>
                <DropMenu
                    style={{flex:1}}
                    bgColor={'white'}
                    tintColor={'#666666'}
                    activityTintColor={'#fdd000'}
                    // arrowImg={}
                    // checkImage={}
                    // optionTextStyle={{color: '#333333'}}
                    // titleStyle={{color: '#333333'}}
                    // maxHeight={300}
                    handler={(selection, row) => this.setState({text: data[selection][row]})}
                    data={data}
                >

                    <ListView
                        style={styles.listViewStyle}
                        dataSource={this.state.dataSource}
                        renderRow={this.renderRow}
                    />

                </DropMenu>
            </View>
        );
    }
下拉列表的使用

  六、React Native項目中「A+ListView」或「ListView + B」的界面搭建

  項目中ScrollView嵌套ListView會形成手勢滑動衝突,可使用「A+ListView」或「ListView + B」的樣式進行搭建,

  經過:ListView的header或footer來實現。

  七、地圖展現  

  項目中使用的經過jsp API接入到高德地圖。

2、技術難點

  一、組件化思想

  React Native是React在移動端的跨平臺方案。若是想更快地理解和掌握React Native開發,就必須先了解React。

  React是FaceBook開源的一個前端框架,它起源於 Facebook 的內部項目,並於 2013 年 5 月開源。由於React 擁有較高的性能,代碼邏輯很是簡單,因此愈來愈多的人已開始關注和使用它,目前該框架在Github上已經有7萬+star。

  React採用組件化的方式開發,經過將view構建成組件,使得代碼更加容易獲得複用,可以很好的應用在大項目的開發中。有一句話說的很形象:在React中,構建應用就像搭積木同樣。

  React認爲一個組件應該具備如下特徵:
  •  可組合:一個組件易於和其餘組件一塊兒使用,或者嵌套在另外一個組件內部。若是一個組件內部建立了另一個組件,那麼父組件擁有它建立的子組件,經過這個特性,一個複雜的UI能夠拆分紅多個簡單的UI組件;
  •    可重用:每一個組件都是具備獨立功能的,它能夠被使用在多個UI場景;
  •    可維護:每一個小的組件僅僅包含自身的邏輯,更容易被理解和維護。

  二、組件的屬性與狀態

  在React Native裏,組件所持有的數據分爲兩種:

  一、屬性(props):組件的props是不可變的,它只能從其餘的組件(例如父組件)傳遞過來。

  二、狀態(state):組建的state是可變的,它負責處理與用戶的交互。在經過用戶點擊事件等操做之後,若是使得當前組件的某個state發生了改變,那麼當前組件就會觸發render()方法刷新本身。

  props:

  因爲props是從其父組件傳遞過來的,那麼可想而知,props的聲明應該是當前組件的父組件來作。

  三、組件的生命週期

  請參考組件的生命週期

  四、搭建APP的框架:Tab Navigator 和 Stack Navigator  

  請參考學習:React Navigation的應用

  五、組件間通訊

  組件間通訊分爲兩大類;

  一、有直接關係或間接關係的組件之間通訊

  二、無直接關係或間接關係的組件之間通訊

相關文章
相關標籤/搜索