從零學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。