1. 程式人生 > >從零學React Native之04自定義對話方塊

從零學React Native之04自定義對話方塊

本篇主要介紹:
1. 自定義元件
2. Alert 對話方塊

自定義對話方塊

之前的我都是利用React Native提供的基礎元件對它們進行排列組合, 其實自定義也很簡單, 我們還是拿上一篇文章的例子進行擴充套件。

當我們點選註冊的時候,可以彈出一個對話方塊,讓使用者確認一下,如下圖:


接下來就來試試,
首先在專案目錄下建立ConfirmDialog.js
程式碼如下:

import React, { Component } from 'react';
import {
    StyleSheet,
    Text,  // RN提供的元件
    View,
    BackAndroid
} from 'react-native'
; let Dimensions = require('Dimensions'); let totalWidth = Dimensions.get('window').width;//寬 let totalHeight = Dimensions.get('window').height;//高 // 直接匯出元件,不用寫 module.exports=ConfirmDialog;了 export default class ConfirmDialog extends Component { render() { // onPress事件直接與父元件傳遞進來的屬性掛接 //numberOfLines 可顯示3行
// {'\r\n'}確 定 回車換行後跟著確定,為了克服Text元件不能垂直居中顯示 return ( <View style={styles.confirmCont}> <View style={styles.dialogStyle}> <Text style={styles.textPrompt}> {this.props.promptToUser} </Text> <Text style={styles.yesButton} onPress={this
.props.userConfirmed} numberOfLines={3}> {'\r\n'}確 定 </Text> <Text style={styles.cancelButton} onPress={this.props.userCanceled} numberOfLines={3}> {'\r\n'}取 消 </Text> </View> </View> ); } } const styles = StyleSheet.create({ confirmCont: { //全屏顯示 半透明 可以看到之前的控制元件但是不能操作了 position:'absolute', //宣告絕對定位 top:0, width:totalWidth, height:totalHeight, backgroundColor:'rgba(52,52,52,0.5)' //rgba a0-1 其餘都是十進位制數 }, dialogStyle: { position:'absolute', left:totalWidth/10, // 定義Dilaog起點位置 top:totalHeight*0.4, width:totalWidth*0.8, height:totalHeight*0.3, backgroundColor:'white' }, textPrompt: { position:'absolute', top:10, left:10, fontSize:20, color:'black' }, yesButton: { position:'absolute', bottom:10, left:10, width:totalWidth*0.35, height:totalHeight*0.12, backgroundColor:'grey', fontSize:20, color:'white', textAlign:'center' }, cancelButton: { position:'absolute', bottom:10, right:10, width:totalWidth*0.35, height:totalHeight*0.12, backgroundColor:'grey', fontSize:20, color:'white', textAlign:'center' } });

可以看到,上面的佈局使用絕對佈局,通過把Text和View拼裝到一起組成了對話方塊的元件, 最外層的View是一個全屏的遮罩.
顏色值是rgba, 前三個數的範圍 0-255 分別表示紅色,綠色和藍色, 最後一位數範圍是0-1 1表示完全不透明, 0表示完全透明。顏色值也可以 用#開頭後面跟著6位16進位制的數表示,沒兩位表示一種顏色, 分別為RGB。

我們的對話方塊元件用到了幾個屬性, 如:{this.props.promptToUser} 這些屬性需要在使用該元件的時候傳遞進來。

掛載元件

接下來我們需要修改上一篇文章中用到的RegisterLeaf.js檔案, 這個檔案我們之前用來建立註冊頁面。
因為涉及到了對話方塊的顯示和隱藏, 我們需要新增一個新的狀態機變數。該變數為了控制對話方塊的顯示與隱藏。

import React, { Component } from 'react';
import {
    AppRegistry, //框架提供的API
    StyleSheet,
    Text,  // RN提供的元件
    View,
    TextInput // 記得引入元件
} from 'react-native';
//let是更完美的var
let Dimensions = require('Dimensions');// 寬高
let totalWidth = Dimensions.get('window').width;  //聲明瞭三個變數,根據螢幕動態變化
let leftStartPoint = totalWidth * 0.1;
let componentWidth = totalWidth * 0.8;
//匯入對話方塊
let ConfirmDialog=require('./ConfirmDialog');

class RegisterLeaf extends Component {

    //建構函式, 元件建立的時候會執行
    constructor(props) {
        super(props); //必須有這句程式碼 父元件向子元件傳遞屬性, 比如styles屬性等
        // 宣告狀態機的初始值
        this.state = {
            inputedNum: '',
            inputedPw: '',
            needToConfirm: false //匯入新的狀態機
        };
        // ES6
        this.userPressConfirm=this.userPressConfirm.bind(this);
        //方法不繫結 其它元件沒法呼叫
        this.userConfirmed=this.userConfirmed.bind(this);
        this.userCanceled=this.userCanceled.bind(this);
    }
    // 定義函式
    updateNum(newText) {
        this.setState((state)=> {
            return {
                inputedNum: newText
            }
        });
    }
    // 定義函式
    updatePW(newText) {
        this.setState(()=> { // 用不到的引數也可以不用寫
            return {
                inputedPw: newText
            }
        });
    }

    userPressConfirm(){
        this.setState(()=>{
            return {
                needToConfirm:true
            }
        });

    }
    // 對話方塊取消時如何處理
    userCanceled(){
        this.setState({needToConfirm:false});
        console.log("userPressConfirm");

    }
    // 對話方塊確定
    userConfirmed(){
        this.setState({needToConfirm:false});
        this.props.navigator.push({
            phoneNumber:this.state.inputedNum,
            userPW:this.state.inputedPw,
            name:'waiting'
        })
    }


    renderWithDialog(){
        return(
            <View style={styles.container}>
                <TextInput style={styles.numberInputStyle}
                           keyboardType={'phone-pad'}
                           placeholder={'請輸入手機號'}
                           onChangeText={(newText)=>this.updateNum(newText)}/>

                <Text style={styles.textPromptStyle}>
                    您輸入的手機號:{this.state.inputedNum}
                </Text>
                <TextInput secureTextEntry={true}
                           style={styles.passwordInputStyle}
                           placeholder='請輸入密碼'
                           onChangeText={(newText)=>this.updatePW(newText)}/>
                <Text style={styles.bigTextPrompt}
                      onPress={this.userPressConfirm}>
                    注  冊
                </Text>
                <ConfirmDialog userConfirmed={this.userConfirmed}
                userCanceled={this.userCanceled}
                promptToUser={'使用'+this.state.inputedNum+'號碼登入?'}/>
            </View>
        )
    }

    // 繪製渲染的控制元件
    render() {
        // 根據不同的狀態 做相應的繪製,當需要繪製對話方塊時,呼叫renderWithDialog
        if(this.state.needToConfirm)  return this.renderWithDialog();
        return (
            /*(newText)=>this.updateNum(newText)
             它將收到的字串為引數呼叫當前元件的updateNum函式,並且將updateNum函式的返回值返回
             當前函式在輸入框文字變化的時候會呼叫
             語句可以改成
             this.updateNum
             但一定不要寫成
             this.updateNum(newText) 因為有右箭頭函式的時候newText是形式引數
             沒有箭頭函式的時,newText就沒有定義
             */
            <View style={styles.container}>
                <TextInput style={styles.numberInputStyle}
                           keyboardType={'phone-pad'}
                           placeholder={'請輸入手機號'}
                           onChangeText={(newText)=>this.updateNum(newText)}/>

                <Text style={styles.textPromptStyle}>
                    您輸入的手機號:{this.state.inputedNum}
                </Text>
                <TextInput secureTextEntry={true}
                           style={styles.passwordInputStyle}
                           placeholder='請輸入密碼'
                           onChangeText={(newText)=>this.updatePW(newText)}/>
                <Text style={styles.bigTextPrompt}
                      onPress={this.userPressConfirm}>
                    注  冊
                </Text>
            </View>
        );
    }
}
// 樣式  const變數只能在宣告的時候賦值一次
const styles = StyleSheet.create({
    //各個元件都沒有定義高度,父View設定了flex1,他會沾滿整個高度,子元件沒有設定會包裹內容
    container: {
        flex: 1,  //表示寬高會自動擴充套件
        backgroundColor: 'white'
    },
    numberInputStyle: {
        top: 20,     // top left表示從父元件的頂端(左側) 向下(向右) 多少位置顯示
        left: leftStartPoint,
        // height:30,  // IOS開發需要加上該高度
        width: componentWidth,
        backgroundColor: 'gray',
        fontSize: 20
    },
    textPromptStyle: {
        top: 30,
        left: leftStartPoint,
        //  // height:30,  // IOS開發需要加上該高度 因為IOS中TextInput不會自動設定高度
        width: componentWidth,
        fontSize: 20
    },
    passwordInputStyle: {
        top: 50,
        left: leftStartPoint,
        width: componentWidth,
        backgroundColor: 'gray',
        fontSize: 20
    },
    bigTextPrompt: {
        top: 70,
        left: leftStartPoint,
        width: componentWidth,
        backgroundColor: 'gray',
        color: 'white',
        textAlign: 'center',//位置居中顯示
        fontSize: 60
    }
});
module.exports=RegisterLeaf;

可以看到,我們渲染自定義對話方塊時, 給對話方塊傳遞了三個屬性其中userConfirmed={this.userConfirmed},userCanceled={this.userCanceled}兩個屬性對應的是父元件的函式,通過將子元件屬性初始化為父元件的某個函式,打通了子元件向父元件通訊的通道,這就是一個無引數的函式。但在需要的情況下,子元件可以通過有引數的函式向父元件傳遞資料。

Android中返回鍵處理

首先在RegisterLeaf元件中再增加一個函式:

    //告訴對話方塊什麼時候時候需要攔截返回事件
    tellConfirmDialogItsStatus() {
        return this.state.needToConfirm;
    }

記得在構造方法中繫結this

    //建構函式, 元件建立的時候會執行
    constructor(props) {
        super(props);
        //...       

     this.tellConfirmDialogItsStatus=this.tellConfirmDialogItsStatus.bind(this);
    }

然後在掛接ConfirmDialog時再增加一個屬性 amIStillAlive,如下:

<ConfirmDialog userConfirmed={this.userConfirmed}
                         userCanceled={this.userCanceled}
                         amIStillAlive={this.tellConfirmDialogItsStatus}
                         promptToUser={'使用'+this.state.inputedNum+'號碼登入?'}/>

當然我們還需要在ConfirmDialog時再增加兩個生命週期的函式,再元件掛載時監聽返回鍵按下的事件, 元件移除時,取消監聽(貌似RN還有Bug,取消監聽還不能用)

  //掛載時
    componentDidMount() {
        //這個位置並不是多餘的,見後面說明
        var amIStillAlive=this.props.amIStillAlive;
        BackAndroid.addEventListener('ConfirmDialogListener',()=>{
            if(amIStillAlive()){
                this.props.userCanceled();
                return true;
            }
            return false;
        })
    }
    componentWillUnMount() {
        //RN bug 無法取消監聽
        BackAndroid.removeEventListener('ConfirmDialogListener');
    }

BackAndroid API的工作機制是, 當掛接多個Listener後,使用者按下返回鍵時,多個Listener都會監聽到返回鍵被按下事件,執行的順序就是新增的順序,不會因為前面的處理函式處理了返回事件,後面的處理函式就不會執行了。這些處理函式都執行完後,只要有一個處理函式返回了true,返回鍵被按下事件就不會交給Android框架處理,也就是說沒辦法退出。

注意componentDidMount()監聽處理函式中判斷條件的取值,並沒有直接使用this.props.amIStillAlive(),而是先用一個變數獲取屬性的值,然後再將這個變數值傳入。
這個位置並沒有多餘,因為監聽處理函式在ConfirmDialog中只是描述了它的實現,當它真正被執行時,執行的上下文並不在ConfirmDialog中。也就是說,當它被執行時,如果執行this.props.amIStillAlive() 語句,它會發現找不到this.props.amIStillAlive這個變數。而amIStillAlive卻是可以找到的,並且可以通過這個函式呼叫RegisterLeaf中相應的函式得到正確的條件值。

屬性宣告

因為自定義元件時可以複用了, 我們開發過程中可能一個專案組有多個人同時開發,其他同事可能會用到我們自定義的元件, 但是他們使用的時候很容易忘記使用某些屬性,這時候我們應該在自定義元件中宣告一些屬性。

export default class ConfirmDialog extends Component {
  //....
}
ConfirmDialog.propTypes = {
    userConfirmed: React.PropTypes.func.isRequired,
    userCanceled: React.PropTypes.func.isRequired,
    amIStillAlive: React.PropTypes.func.isRequired,
    promptToUser:React.PropTypes.string.isRequired
};

上面宣告的屬性都是 isRequired, 如果不傳遞這些屬性程式會在開發階段出現警告。

這裡寫圖片描述
當然除了上面兩種型別, 還可以約束其它幾種型別,屬性確認的語法也有好幾種,大家可以參考我的另一篇文章 React Native宣告屬性和屬性確認

我們還可以給屬性指定一個預設值,當沒有傳遞該屬性時使用預設值,如:

ConfirmDialog.defaultProps = {
    promptToUser: '確定嗎?'
};

同時記得要將指定 promptToUser為必須的’isRequired’ 去掉.

Alert Api對話方塊

彈出對話方塊是程式開發中經常需要使用的UI手段,React Native也為開發者提供了Alert API, 沒有特殊要求可以使用Alert Api彈出對話方塊, 這種對話方塊和原生程式碼的對話筐樣式一樣,Android和IOS樣式有些區別。
首先在RegisterLeaf.js中匯入Alert元件

import {
    ...
    Alert
} from 'react-native';

然後修改userPressConfirm()程式碼:

    //使用Alert Api
    userPressConfirm() {
        Alert.alert(
            '標題',
            '正文',
            [
                {text:'確定',onPress:this.userConfirmed},
                {text:'取消',onPress:this.userCanceled,style:'cancel'},
                {text:'額外選項一',onPress:this.userCanceled},
                {text:'額外選項二',onPress:this.userCanceled}
            ]
        );
    }

Android選項最多支援3個, 多餘的直接忽略不會報錯, IOS可以支援多個。style:’cancel’ 的選項再最後顯示, 該樣式對Android無效, Android第一個選項空間比較大。

這裡寫圖片描述

IOS彈出對話方塊, 點選對話方塊周圍是無法取消對話方塊的, 但是Android可以取消,如果Android想做成和ios一樣的效果,需要在RN 0.33版本以上。在對話方塊上新增如下程式碼:

    userPressConfirm() {
        Alert.alert(
            '標題',
            '正文',
            [
                {text:'確定',onPress:this.userConfirmed},
                {text:'取消',onPress:this.userCanceled,style:'cancel'},
                {text:'額外選項一',onPress:this.userCanceled},
                {text:'額外選項二',onPress:this.userCanceled}
            ],
            {
                cancelable: false
            }
        );
    }

更多精彩請關注微信公眾賬號likeDev,公眾賬號名稱:愛上Android。
這裡寫圖片描述