看完全文并且do it你將收獲:
- 導航器組件Navigator的使用
- 文本輸入組件TextInput的使用(當然不重點說這個,注釋里見!)
- 安卓特有組件ToastAndroid(iOS直接用alert替代吧)
老套路,來先看看今天要實現一個怎么樣的效果:
OK,現在開始來實現:
那個username和password的小圖標我放在文末,先去另存為一下吧~
界面布局
組件的布局樣式使用簡直就是小菜一碟對不對?那我們直接把布局給寫出來!(千萬記得import需要的組件噢)
export default class Login extends Component {
render() {
return (
<View
style={styles.container}>
<View
style={styles.inputBox}>
<Image
style={styles.img}
source={require('./image/username.png')}/>//注意你的圖片引用路徑
<TextInput
style={styles.input}/>
</View>
<View
style={styles.inputBox}>
<Image
source={require('./image/pwd.png')}//注意你的圖片引用路徑
style={styles.img}/>
<TextInput
style={styles.input}/>
</View>
<TouchableOpacity
style={styles.button}>
<Text
style={styles.btText}>登錄</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}>
<Text
style={styles.btText}>注冊</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
img: {
width: 30,
height: 30,
},
input: {
width: 200,
height: 40,
color: '#fff',//輸入框輸入的文本為白色
},
inputBox: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
width: 280,
height: 50,
borderRadius: 8,
backgroundColor: '#66f',
marginBottom: 8,
},
button: {
height: 50,
width: 280,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
backgroundColor: '#66f',
marginBottom: 8,
},
btText: {
color: '#fff',
}
});
初步寫成的界面是這樣:
這個TextInput下劃線那么丑?!不行,我得把他干掉:
......
<View
style={styles.inputBox}>
<Image
style={styles.img}
source={require('./image/username.png')}/>
<TextInput
style={styles.input}
//secureTextEntry={true} 如果是密碼框,請開啟這個屬性!!
placeholderTextColor={'#fff'}//提示文本的顏色
placeholder={'username'}//提示文本內容
underlineColorAndroid={'transparent'}/>{/*設置下劃線顏色為透明,就相當于不見了*/}
</View>
......
OK,這樣就把賬號的輸入框搞定了,那密碼框就一樣的了,把secureTextEntry設置true,然后placeholder內容改成password就行了!
來,看看效果:
PS.饒了我吧,我也不知道那個光標的顏色要怎么改/(ㄒoㄒ)/~~
不過界面效果確實很理想,接下來咱們把登錄按鈕的功能實現一下,預想功能是這樣的:(簡單判斷賬號密碼是否與預設相同)
- 點擊登錄,如果賬號密碼不匹配,則彈出提示密碼錯誤
- 點擊登錄,如果賬號密碼匹配,則跳轉到應用主界面
那么現在問題來了,我要怎么在點擊登錄的時候獲取到TextInput的文本值呢?
原來TextInput有一個onChangeText屬性,該屬性接收一個"參數為當前文本的回調函數",會在文本內容發生變化的時候回調此函數。
既然這樣,那就可以在state中設置兩個變量分別為username和password,在onChangeText回調函數內setState來更新他們的值!
這里就得說一下了,每次調用setState的時候,都會觸發render重新渲染界面(請參考組件生命周期相關知識),這樣太消耗資源了,既然這樣,那還是不把username和password寫在state中吧
username、password和onChangeText:
export default class Login extends Component {
username = '';
password = '';
//賬號框文本變化的回調函數,該回調函數接收的參數為:輸入框當前文本內容
//通過綁定此函數給onChangeText就實現實時更新username變量
onUsernameChanged = (newUsername) => {
console.log(newUsername);//運行后可以在輸入框隨意輸入內容并且查看log驗證!
this.username = newUsername;
};
//密碼框文本變化的回調函數,該回調函數接收的參數為:輸入框當前文本內容
//通過綁定此函數給onChangeText就實現實時更新password變量
onPasswordChanged = (newPassword) => {
console.log(newUsername);//運行后可以在輸入框隨意輸入內容并且查看log驗證!
this.password = newPassword;
};
render() {
return (
......
<TextInput
onChangeText={this.onUsernameChanged}//綁定文本變化的回調函數
style={styles.input}
placeholderTextColor={'#fff'}
placeholder={'username'}
underlineColorAndroid={'transparent'}/>
......
<TextInput
onChangeText={this.onPasswordChanged}//綁定文本變化的回調函數
placeholderTextColor={'#fff'}
placeholder={'password'}
underlineColorAndroid={'transparent'}
secureTextEntry={true}
style={styles.input}/>
......
);
}
}
現在能獲取到輸入框的內容了,接下來就給登陸按鈕綁定一個點擊事件吧!
......
login = () => {
if (this.username == 'admin' && this.password == '123') {
//其實安卓的Toast...簡直簡單到我不用說...(記得引入ToastAndroid)
ToastAndroid.show('登錄成功',ToastAndroid.SHORT);
} else {
//iOS直接用alert就行了
ToastAndroid.show('登錄失敗',ToastAndroid.SHORT);
}
};
render() {
return (
......
<TouchableOpacity
onPress={this.login}//綁定一下點擊事件
style={styles.button}>
<Text
style={styles.btText}>登錄</Text>
</TouchableOpacity>
......
)
}
好了,寫了這么一坨代碼,現在來看看效果:double r
還不錯,那么下一個目標就是如果賬號密碼正確,不僅僅是彈一個吐司那么簡單,咱們進入一個新的界面(場景Scene)
Navigator
Navigator導航器可以幫我們實現場景(Scene)的跳轉,那么第一個肯定問題肯定要問啥是場景(Scene)呢:來自RN中文網的解釋:
無論是View中包含Text,還是一個排滿了圖片的ScrollView,渲染各種組件現在對你來說應該已經得心應手了。這些擺放在一個屏幕中的組件,就共同構成了一個“場景(Scene)”。場景簡單來說其實就是一個全屏的React組件。與之相對的是單個的Text、Image又或者是你自定義的什么組件,僅僅占據頁面中的一部分。
原來場景(Scene)就是一個全屏的React組件!也就是說我們剛剛寫的那個界面,就是一個場景(Scene)咯!
在使用Navigator之前,咱們先把這個登錄的界面做成一個“可復用的組件(場景)”,關于怎么制作一個可復用的組件,可以參考我前面的文章ReactNative制作Component控件并且復用
這里我們在項目根目錄新建一個scene文件夾,里面用來存放各個scene,接著在文件夾內新建一個LoginScene.js文件,將剛剛寫的代碼遷移過去(這個過程中注意修改各個和路徑有關的地方),最后再index.android.js中引用:【index.android.js】
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
} from 'react-native';
import LoginScene from './scene/LoginScene';
export default class Login extends Component {
render() {
return (
<LoginScene/>
);
}
}
AppRegistry.registerComponent('Login', () => Login);
完美,效果和之前完全一樣!(不放圖預覽啦,反正一樣的)
Navigator的使用極其簡單!他是一個組件,所以使用他也就是一個標簽就行了:<Navigator/> 當然你要注意引入這個組件
Navigator有兩個必要屬性renderScene和initialRoute
- renderScene屬性接收一個"返回需要渲染的場景的回調函數","該回調函數接收兩個參數"---(數據集合/即路由,這個集合中要包含需要展示的scene)和(navigator對象實例),
- initialRoute屬性接收一個數據對象,其中必須包含默認展示的場景(啟動默認顯示的界面),當然你可以在其中添加其他數據(用于數據傳遞)。注意了,這個數據對象就是第一次傳給renderScene的回調函數的第一個參數!!
OK,現在知道Navigator需要的東西,那咱們就給他準備好:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
Navigator
} from 'react-native';
import LoginScene from './scene/LoginScene';
export default class Login extends Component {
//第一次調用的時候,第一個參數route就是initialRoute
renderScene = (route, navigator) => {
return(
<route.scene //這里返回route中包含的場景,在界面上渲染該場景
navigator={navigator}/>//并且將navigator作為一個參數傳遞給這個場景,以便在這個場景中做場景跳轉
);
}
//默認的route數據,其中必須包含第一次需要渲染的場景,不然顯示啥?
initialRoute = {
scene: LoginScene,
//你也可以在這里繼續添加其他數據,然后在renderScene中取出,用于場景的數據傳遞,不展開敘述這個了!
}
render() {
return (
<Navigator
initialRoute={this.initialRoute}
renderScene={this.renderScene}/>
);
}
}
AppRegistry.registerComponent('Login', () => Login);
這個時候還是來double r看一看效果:
好吧由于界面效果和之前完全一樣,沒有變化,就不貼圖了。(沒紅就是最好的效果啦)
那么怎么控制場景切換,在哪里控制場景切換呢?在上面的代碼我們看到,navigator對象實例傳遞給了每一個Scene,而場景切換正是由navigator實例對象來控制的:
navigator擁有一系列方法,用于場景切換RN中文網Navigator API
- getCurrentRoutes() - 獲取當前棧里的路由,也就是push進來,沒有pop掉的那些。
- jumpBack() - 跳回之前的路由,當然前提是保留現在的,還可以再跳回來,會給你保留原樣。
- jumpForward() - 上一個方法不是調到之前的路由了么,用這個跳回來就好了。
- jumpTo(route) - 跳轉到已有的場景并且不卸載。
- push(route) - 跳轉到新的場景,并且將場景入棧,你可以稍后跳轉過去
- pop() - 跳轉回去并且卸載掉當前場景
- replace(route) - 用一個新的路由替換掉當前場景
- replaceAtIndex(route, index) - 替換掉指定序列的路由場景
- replacePrevious(route) - 替換掉之前的場景
- resetTo(route) - 跳轉到新的場景,并且重置整個路由棧
- immediatelyResetRouteStack(routeStack) - 用新的路由數組來重置路由棧
- popToRoute(route) - pop到路由指定的場景,在整個路由棧中,處于指定場景之后的場景將會被卸載。
- popToTop() - pop到棧中的第一個場景,卸載掉所有的其他場景。
所有場景都在棧中,由navigator控制出棧入棧,這些解釋我相信大家很容易就明白
在第一個顯示的場景是LoginScene的情況下,場景切換的控制權當然在[LoginScene.js]中了!!不過這個時候你更應該想到的是:要切換場景就需要>=2個場景,但是現在卻只有一個場景,所以現在的任務就是在scene文件夾下新建一個【HomeScene.js】文件了!
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet
} from 'react-native';
export default class HomeScene extends Component {
render() {
return (
<View
style={styles.container}>
<Text>登錄成功!這是主頁!</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
接下來,就是需要在【LoginScene.js】中控制賬號密碼匹配后跳轉到這個主頁場景中:
.....
import HomeScene from './HomeScene';//千萬別忘了引入!!
......
login = () => {
if (this.username == 'admin' && this.password == '123') {
this.props.navigator.push({//還記得navigator作為屬性傳給了每一個scene嗎!對了,就是這樣取到他
scene: HomeScene,//通過push方法將一個scene入棧,push方法接收一個route,其中必須包含一個scene
});
ToastAndroid.show('登錄成功',ToastAndroid.SHORT);
} else {
ToastAndroid.show('登錄失敗',ToastAndroid.SHORT);
}
};
......
這個時候來看一下整體的效果:double r
可以看到,這個時候已經實現了場景跳轉,并且可以看到在第二個場景可以通過在左邊緣向右滑返回上一個場景。可是現在還有一個重大bug,就是第二個場景出來的時候,居然和第一個場景出現了圖像的重疊??
經過分析和嘗試,發現問題出在這兒:【HomeScene.js】
......
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
//在這里我們并沒有給第二個場景設置背景顏色!如果沒有設置背景顏色,默認就是透明的
//這就導致了第二個場景在出現并疊在第一個場景之上的時候,會出現圖像的重疊
//解決方法就是給第二個場景加上一個背景顏色!
backgroundColor: '#F5FCFF',
},
});
這個時候就解決圖像重疊的問題了!不信你double r試試看
這里有一個特別有意思的地方,就是你在登錄成功并且跳轉到第二個場景之后,你再按兩下R重載界面試試?
是不是發現無論多瘋狂的按R鍵,都不會重載界面?
這個時候你從左邊緣向右滑動,返回上一個場景,你會發現密碼框出現了一坨的r
- 這就是有趣的地方,RN的場景切換,現在看來應該只是把第一個場景給隱藏了!可以想象成向左滑動一點距離之后設置透明度為0,這樣就看不見了。
剛剛登陸場景向左滑動并且隱藏,可是密碼輸入框的焦點還在,所以這個時候你按r當然就是在向密碼框輸入內容了!
當然另一方面,在邏輯上來說,既然用戶是登錄進入了第二個場景,那么自然不可能通過滑動返回到登錄界面!所以再登錄按鈕的方法中navigator.push()應該換成navigator.replace(),這樣在登錄成功的情況下就無法返回到上一個場景了,因為棧中已經沒有登錄的場景了!
既然看到了這兒,那么Navigator最基本的用法你已經掌握了,開頭的效果,你也很容易能加上了-----無非是每個場景多加一個button來實現場景切換嘛!
接下來把圖片給你們!
我靠,在文章里這么大.....算了,拿著用吧分辨率是510*510的...的...好吧我之前一直沒注意用的是這么大的圖片!!
接下來就是代碼啦:
【index.android.js】
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
Navigator
} from 'react-native';
import LoginScene from './scene/LoginScene';
export default class Login extends Component {
renderScene = (route, navigator) => {
return(
<route.scene
navigator={navigator}/>
);
}
initialRoute = {
scene: LoginScene,
}
render() {
return (
<Navigator
initialRoute={this.initialRoute}
renderScene={this.renderScene}/>
);
}
}
AppRegistry.registerComponent('Login', () => Login);
【LoginScene.js】
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
Image,
TouchableOpacity,
ToastAndroid
} from 'react-native';
import HomeScene from './HomeScene.js';
export default class LoginScene extends Component {
username = '';
password = '';
onUsernameChanged = (newUsername) => {
this.username = newUsername;
};
onPasswordChanged = (newPassword) => {
this.password = newPassword;
};
login = () => {
if (this.username == 'admin' && this.password == '123') {
this.props.navigator.replace({
scene: HomeScene,
});
ToastAndroid.show('登錄成功',ToastAndroid.SHORT);
} else {
ToastAndroid.show('登錄失敗',ToastAndroid.SHORT);
}
};
render() {
return (
<View
style={styles.container}>
<View
style={styles.inputBox}>
<Image
style={styles.img}
source={require('../image/username.png')}/>
<TextInput
onChangeText={this.onUsernameChanged}
style={styles.input}
placeholderTextColor={'#fff'}//提示文本的顏色
placeholder={'username'}//提示文本內容
underlineColorAndroid={'transparent'}/>{/*設置下劃線顏色為透明,就相當于不見了*/}
</View>
<View
style={styles.inputBox}>
<Image
source={require('../image/pwd.png')}
style={styles.img}/>
<TextInput
onChangeText={this.onPasswordChanged}
placeholderTextColor={'#fff'}
placeholder={'password'}
underlineColorAndroid={'transparent'}
secureTextEntry={true}//密碼輸入框
style={styles.input}/>
</View>
<TouchableOpacity
onPress={this.login}
style={styles.button}>
<Text
style={styles.btText}>登錄</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}>
<Text
style={styles.btText}>注冊</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
img: {
width: 30,
height: 30,
},
input: {
width: 200,
height: 40,
color: '#fff',
},
inputBox: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
width: 280,
height: 50,
borderRadius: 8,
backgroundColor: '#66f',
marginBottom: 8,
},
button: {
height: 50,
width: 280,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
backgroundColor: '#66f',
marginBottom: 8,
},
btText: {
color: '#fff',
}
});
【HomeScene.js】
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet
} from 'react-native';
export default class HomeScene extends Component {
render() {
return (
<View
style={styles.container}>
<Text>登錄成功!這是主頁!</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});