案例說明
- 案例界面
項目由搜索框+輪播圖+一個可以下拉的列表組成;不涉及後臺接口(均爲模擬數據) - 案例所用技術
需要使用react native腳手架+expo;主要是對react native的組件進行學習 - 文檔
https://reactnative.cn/docs/tutorial/
使用腳手架創建空的一個項目
# 沒有腳手架自行安裝腳手架
create-react-native-app myStore
# 打開項目並啓動
cd myStore
expo start
安裝模擬器
啓動模擬器,將我們的expo軟件拖動進去,此時它會幫我們安裝這個軟件;安裝完成後打開
我們回到項目,按下a鍵會自動編譯到模擬器中(如果報錯可能是我們的版本問題;打開我們的app.json-- 更改 “sdkVersion”: “31.0.0”,)
首頁架子
React Native採用的是伸縮佈局;而且它屏幕不會因爲像瀏覽器那樣超出會出現滾動條…所以我們可以使用flex屬性來使其佔滿達到我們想要的效果
首頁組件抽取(在項目根目錄下創建components)
- /component/Searchbar.js
import React from 'react'
import {View,Text,StyleSheet} from 'react-native'
export default class Searchbar extends React.Component{
render(){
return <View style={styles.searchbar}>
<Text>搜素</Text>
</View>
}
}
const styles = StyleSheet.create({
searchbar:{
height:40,
backgroundColor: 'red'
}
})
- /component/Adverticement.js
import React from 'react'
import {View,Text,StyleSheet} from 'react-native'
export default class Adverticement extends React.Component{
render(){
return <View style={styles.adverticement}>
<Text>廣告</Text>
</View>
}
}
const styles = StyleSheet.create({
adverticement:{
height:200,
backgroundColor: 'green'
}
})
- /component/Products.js
import React from 'react'
import {View,Text,StyleSheet} from 'react-native'
export default class Products extends React.Component{
render(){
return <View style={styles.products}>
<Text>產品</Text>
</View>
}
}
const styles = StyleSheet.create({
products:{
flex: 1,
backgroundColor: 'blue'
}
})
導入到我們的App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
// 導入組件
import Adverticement from './components/Adverticement.js'
import Products from './components/Products.js'
import SearchBar from './components/SearchBar.js'
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<SearchBar></SearchBar>
<Adverticement></Adverticement>
<Products></Products>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
});
狀態欄的配置
我們可以看到頂部狀態欄遮擋住我們的頁面;我們此時可以使用StatusBar控制應用狀態欄的組件
進行配置。
由於StatusBar可以在任意視圖中加載,且後加載的設置會覆蓋先前的設置。因此在配合導航器使用時,請務必考慮清楚StatusBar的放置順序。
<View style={styles.container}>
<StatusBar
hidden={false}
animated={true}
backgroundColor="#ccc"
barStyle="light-content"
translucent={false}
></StatusBar>
<SearchBar></SearchBar>
<Adverticement></Adverticement>
<Products></Products>
</View>
searchBar頁面
我們從圖片中可以看出,它是由一個搜索框TextInput
和一個按鈕組成Button
;我們佈局的話使用伸縮佈局,而按鈕的背景色需要在props給的屬性裏面設置也就是給color
屬性一個值,搜索框flex:1
使其佔滿自適應
import React from 'react'
import {View,Button,StyleSheet,TextInput,Alert} from 'react-native'
export default class Searchbar extends React.Component{
// 文本框改變處理函數
constructor(props){
super(props)
this.state = {
searchVal:''
}
}
// 文本框改變時,改變內容狀態
changeTextHandle = (newSearchVal)=>{
this.setState({
searchVal:newSearchVal
})
}
searchHandle = () =>{
Alert.alert(this.state.searchVal)
}
render(){
return <View style={styles.searchbar}>
<TextInput
style={styles.input}
placeholder='你的白豬王子登門造訪'
value={this.state.value}
onChangeText={this.changeTextHandle}
></TextInput>
<Button
style={styles.button}
onPress={this.searchHandle}
title="搜索"
></Button>
</View>
}
}
const styles = StyleSheet.create({
searchbar:{
flexDirection: "row",
justifyContent: "center",
alignItems: 'center',
paddingHorizontal: 10,
height: 40,
},
input: {
flex: 1,
marginRight: 10,
paddingLeft: 6,
height: 30,
borderWidth: 2,
borderColor: "#ccc",
borderRadius: 5,
lineHeight: 12,
fontSize: 12
},
button: {
}
})
我們使用inputText
需要綁定數據;然後通過操作狀態數據來改變其內容(可以理解爲模擬雙向數據綁定);我們點擊按鈕發送請求(因爲我們這裏不涉及真正的接口,所以給一個彈框);最後測試一下各個方法有沒有寫錯
Adverticement頁面
Adverticement部分是一個輪播圖
如果只是開發安卓的話可以使用react-native-swiper
;我們這裏使用scrollView
來兼容平臺
- 我們需要使用
Dimensions
來獲取屏幕的寬度 - 自己準備圖片;使用Image;引入圖片;給圖片設置寬度
- 使用
scrollView
實現輪播圖:隱藏滾動條、水平分佈排列、使滾動條分頁滑動
明白以上三點;再使用魔法能量;一個輪播圖就產生了…
import React from 'react'
import {View,Dimensions,StyleSheet,ScrollView,Image} from 'react-native'
export default class Adverticement extends React.Component{
// 這是模擬數據
constructor(props) {
super(props);
this.state = {
currentPage: 0,
advertisements: [
{
uri: require("../assets/double-11.png")
},
{
uri: require("../assets/eyes.png")
},
{
uri: require("../assets/five-year.png")
}
]
}
}
render(){
return <View style={styles.adverticement}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
ref="scrollView"
>
{this.state.advertisements.map((item)=>{
return <View style={styles.itemCon} key={item.uri}>
<Image
source={item.uri}
style={styles.image}
resizeMode="cover"
></Image>
</View>
})}
</ScrollView>
</View>
}
}
const styles = StyleSheet.create({
adverticement:{
height:200,
},
itemCon:{
width: Dimensions.get("window").width,
height: 200,
backgroundColor: 'green'
},
image:{
width:'100%',
height:'100%'
}
})
當然,我們還需要設置自動輪播;使用其方法scrollTo()
;來實現自動輪播
// 啓動定時器實現輪播
componentDidMount(){
this.startTimerHandler()
}
startTimerHandler = ()=>{
this.timerId = setInterval(()=>{
// 每次都切換一張圖片
let nextPage = this.state.currentPage + 1
// 當切換到最後一張,跳回到第一張
nextPage = nextPage>=this.state.advertisements.length?0:nextPage
this.setState({
currentPage:nextPage
})
let offsetX = Dimensions.get("window").width * this.state.currentPage
this.refs.scrollView.scrollTo({x: offsetX, y: 0, animated: true})
},3000)
}
當然輪播圖也可以深入,比如做一下分頁器或則做成緩動動畫
Products
我們已經將屏幕剩下的高度佔滿,但我們的內容需要滾動,此時則需要使用FlatList
實現上拉加載;這是一個懶加載的.
- 我們首先定義一個模擬數據,綁定到該組件FlatList的data上
- 使用
renderItem
渲染我們的數據;此時記得渲染時的參數是解構賦值 - keyExtractor綁定唯一的key值
- 接着寫樣式觀看一下效果
- 這裏使用onRefresh、refreshing模擬一下下拉刷新的功能
import React from 'react'
import {View,Text,StyleSheet,FlatList,Image} from 'react-native'
export default class Products extends React.Component{
constructor(props) {
super(props);
this.state = {
products: [
{
id: "1",
title: "小米MIX3",
subTitle: "滑蓋手機,咔咔咔",
image: "",
uri:{uri:'https://img1.360buyimg.com/pop/s590x470_jfs/t1/37180/6/6998/68136/5ccaaefeE884199bf/89f7a9047ab2fffa.jpg!q90!cc_590x470.webp'}
},
{
id: "2",
title: "華爲Mate20",
subTitle: "黑科技,牛逼牛逼",
image: "",
uri:{uri:'https://img11.360buyimg.com/n7/jfs/t1/38769/11/2224/318485/5cbfcfaaEfe8197e5/f67f128aef875b28.jpg'}
},
{
id: "3",
title: "魅族",
subTitle: "漂亮無需多言",
image: "",
uri:{uri:'https://img1.360buyimg.com/pop/s590x470_jfs/t1/37180/6/6998/68136/5ccaaefeE884199bf/89f7a9047ab2fffa.jpg!q90!cc_590x470.webp'}
},
{
id: "4",
title: "錘子",
subTitle: "漂亮的不像實力派",
image: "",
uri:{uri:'https://imgcps.jd.com/ling/100000770620/6JCl5YW75L-d5YGl/5q-P5ruhMzAw5YePNDA/t-5bd95d4f8e34e21f3ff67e71/65275632.jpg'}
},
{
id: "5",
title: "三星",
subTitle: "我的電池絕對靠譜",
image: "",
uri:{uri:'https://img1.360buyimg.com/pop/s590x470_jfs/t1/54899/5/1500/99514/5cf4dd53E4e65595d/ec452f29a3874f16.jpg!q90!cc_590x470.webp'}
},
{
id: "6",
title: "蘋果",
subTitle: "我的價格是真的不貴",
image: "",
uri:{uri:'https://img1.360buyimg.com/pop/s590x470_jfs/t1/40249/38/7444/64209/5ceb5c2bE89cadbd8/812bcae0b22e2a38.jpg!q90!cc_590x470.webp'}
},
{
id: "7",
title: "oppo",
subTitle: "照亮你的美",
image: "",
uri:{uri:'https://img10.360buyimg.com/n7/jfs/t29065/335/1630058179/447068/5770c26f/5ce66f29Nca358e47.png'}
},
{
id: "8",
title: "vivo",
subTitle: "柔光拍攝",
image: "",
uri:{uri:'https://img11.360buyimg.com/n7/jfs/t1/38769/11/2224/318485/5cbfcfaaEfe8197e5/f67f128aef875b28.jpg'}
},
],
loading:true
}
}
renderItemHandler = ({item,index})=>{//從data(products)中抽取數據進行渲染
return <View style={styles.item}>
<Image
source={item.uri}
style={styles.image}>
</Image>
<View style={styles.content}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle}>{item.subTitle}</Text>
</View>
</View>
}
keyExtractorHandler = (item)=>{ //需要指定key;否則會警告
return item.id
}
// 模擬下拉刷新功能
timerId = ()=>{
setTimeout(()=>{
this.setState({
loading:false
})
},2000)
}
componentDidMount(){
this.timerId()
}
loadingHandler = ()=>{
this.setState({
loading:true
},()=>{
this.timerId()
})
}
render(){
return (
<FlatList
onEndReachedThreshold={0.1}
onRefresh={this.loadingHandler}
refreshing={this.state.loading}
data={this.state.products}
renderItem={this.renderItemHandler}
keyExtractor={this.keyExtractorHandler}
>
</FlatList>
)
}
}
const styles = StyleSheet.create({
products: {
flex: 1,
backgroundColor: "blue"
},
item: {
flexDirection: 'row',
justifyContent: "center",
alignContent: 'center',
marginHorizontal: 10,
marginTop: 30,
height: 60,
},
image: {
marginRight: 10,
width: 50,
height: 50,
backgroundColor: "green"
},
content: {
flex: 1
},
title: {
lineHeight: 28,
fontSize: 16,
color: "#000"
},
subTitle: {
lineHeight: 18,
fontSize: 12,
color: "#ccc"
}
})
基本的頁面已經構成了…接下來豐富我們的頁面
Adverticement組件添加分頁器(完善輪播圖)
也就是添加指示器,就是輪播圖上面可以點擊的小圓圈;我們做web的時候一般都是ul和li,然後給其樣式,最後定位;我們同樣可以使用View然後通過定位和伸縮佈局來做其效果;當然爲了方便維護我們可以將初始化其大小等一些基本樣式…
- 我們現在狀態裏面初始化它的大小;
this.state = {
circleSize: 8,//指示器的大小
circleMR: 5//指示器左右的距離
}
- 寫組件,初始化樣式
- 我們通過自調用函數,將基本的樣式寫在
circleStyle
;動態寫數據方便我們後期的維護
render(){
return <View style={styles.adverticement}>
<ScrollView
......
</ScrollView>
<View style={styles.circle}>
{
// 一個自調用函數
(()=>{
// 先把基本的樣式在狀態裏面定義好
const circleStyle = {
width: this.state.circleSize,
height: this.state.circleSize,
borderRadius: this.state.circleSize /2,
marginHorizontal: this.state.circleMR
}
return this.state.advertisements.map((item,index)=>{
return (
<View
style={[circleStyle,this.state.currentPage===index?styles.circleActiveStyle:styles.indicator]}
key={index}>
</View>
)
})
})()
}
</View>
</View>
}
- 定位和高亮顯示
const styles = StyleSheet.create({
......
// 給指示器進行定位
circle:{
position: 'absolute',
left: '50%',
bottom: 10,
display: 'flex',
flexDirection: 'row',
marginLeft: -32
},
// 指示器背景顏色
indicator:{
backgroundColor: '#ccc',
},
// 指示器高亮樣式
circleActiveStyle:{
backgroundColor: 'red'
},
})
5. 當然可以根據自己的思維寫;以上的寫法只是爲了方便修改數據
完善Products加載功能
上面我們只是模擬上拉加載的效果;現在完善加載數據
- 我們使用
RefreshControl
組件(這一組件可以用在ScrollView或FlatList內部,爲其添加下拉刷新的功能);需要引入該組件RefreshControl
- 我們將方法還有一些樣式全部定義在該組件內
render(){
return (
<FlatList
onEndReachedThreshold={0.1}
data={this.state.products}
renderItem={this.renderItemHandler}
keyExtractor={this.keyExtractorHandler}
refreshControl={
<RefreshControl
refreshing={this.state.loading}
onRefresh={this.loadingHandler}
title="loading"
colors={['red','yellow']}
progressBackgroundColor={['transparent']}
progressViewOffset={50}
></RefreshControl>
}
>
</FlatList>
)
}
- 定義一個模擬數據,下拉更新數據
loadingHandler = ()=>{
const products = Array.from(Array(10)).map((v,i)=>{
return {
id: i.toString(),
title: "vivo"+i,
subTitle: "vivo-限時至高直降300+領券減300iQOO水滴全面屏超廣",
image: "",
uri:{uri:'https://img11.360buyimg.com/n7/jfs/t1/42675/4/1533/86437/5cc6a322E10252bba/64158e66e444403b.jpg'}
}
})
this.setState({
products,
loading:true
},()=>{
this.timerId()
})
}
- 對於
RefreshControl
組件的屬性不懂也可以查閱文檔;看一下效果也能明白
走過路過不要錯過,以下是對新鮮便宜的路由導航的學習
點擊Products的每一項調整到詳情頁面
react-navigation
文檔
https://reactnavigation.org/docs/zh-Hans/getting-started.html
可能遇到的一些問題
- 我們下載過程儘量使用
npm
來下載這套包,不要使用cnpm或則yarn - 如果還是報錯,刪除我們的包
node_modules
;然後重新安裝npm i
- 在不行就下載穩定版本得
npm install --save [email protected]
- 使用的使用記得導入的配置
import {
createStackNavigator,
createAppContainer
} from 'react-navigation';
我們對整體的文件進行改造
- 上面我們的搜索欄,輪播圖,產品列表都是我們首頁的內容;所以我們將其放到我們的Home.js
import React from 'react';
import { StyleSheet, Text, View,StatusBar } from 'react-native';
// 導入組件
import Adverticement from '../../components/Adverticement.js'
import Products from '../../components/Products.js'
import SearchBar from '../../components/SearchBar.js'
export default class Home extends React.Component {
render() {
return (
<View style={styles.container}>
<StatusBar
hidden={true}
animated={true}
backgroundColor="#ccc"
barStyle="light-content"
translucent={false}
></StatusBar>
<SearchBar></SearchBar>
<Adverticement></Adverticement>
<Products></Products>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
});
- 在App.js使用我們的
react-navigation
// import GlobalStack from './navigation/GlobalStack.js'
// export default GlobalStack
import {
createStackNavigator,
createAppContainer
} from 'react-navigation';
import Home from './pages/Home/Home.js';
import Detail from './pages/Products/Detail.js';
const RootStack = createStackNavigator(
{
home: {
screen: Home,
navigationOptions: ({navigation, navigationOptions}) => ({
header: null,
})
},
detail: {
screen: Detail,
navigationOptions: ({navigationOptions}) => ({
title: "商品詳情",
})
}
},
{
initialRouteName: "detail"
}
)
const App = createAppContainer(RootStack)
export default App;
- 我們更改默認配置使用的導航測試一下是否引入成功
initialRouteName: "home"
- 我們點擊Products的列表需要跳轉到我們的詳情頁面’./pages/Products/Detail.js’;那麼我們的home頁面需要到導航傳給它
<Products {...this.props}></Products>
- 我們到Products頁面綁定一個點擊事件,導入TouchableNativeFeedback;將產品列表每一項item包裹在裏面
renderItemHandler = ({item,index})=>{//從data(products)中抽取數據進行渲染
return (
<TouchableNativeFeedback
onPress={this.toProductDetailHandler.bind(this,item)}
>
<View style={styles.item}>
<Image
source={item.uri}
style={styles.image}>
</Image>
<View style={styles.content}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle}>{item.subTitle}</Text>
</View>
</View>
</TouchableNativeFeedback>
)
}
- 點擊跳轉
toProductDetailHandler = (item)=>{
const {navigation} = this.props
// console.log(navigation)
navigation.push("detail",item)
}