小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
1.简介
前面已经写了一个简单的App首页,现在我们开始再进行一些完善。主要包括:代码管理、搜索框功能、广告Banner、商品列表、下拉刷新、页面跳转等。
2.代码管理
相信大家都比较熟悉了,使用Git或Svn都可以,我把代码放在了gitee上(rn_tb链接),仅供参考。
创建工程的时候,rn已经创建好了.gitignore文件,所以在另一台设备下载完代码后,需要使用npm install来安装依赖。
2.1 重构代码
前面已经用到了StyleSheet,作用就是将控件的样式配置集中到一个位置,相当于css文件的作用,基本使用
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column'
},
input: {
flex: 1,
borderColor: 'gray',
borderWidth: 2,
borderRadius: 10
}
)};
格式为key-value,在使用是使用styles.container即可,代码中重复的样式使用可以提取至StyleSheet中。
banner部分代码样式重构:
return (
<View style={styles.ad_banner}>
<ScrollView
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
ref = {com => this._banner = com}
>
<TouchableHighlight
onPress={() => {
Alert.alert('点击banner', null, null);
}}>
<Image
style={{width:Dimensions.get('window').width, height: 180}}
source={image_list.img1}/>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
Alert.alert('点击banner', null, null);
}}>
<Image
style={{width:Dimensions.get('window').width, height: 180}}
source={image_list.img2}/>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
Alert.alert('点击banner', null, null);
}}>
<Image
style={{width:Dimensions.get('window').width, height: 180}}
source={image_list.img3}/>
</TouchableHighlight>
</ScrollView>
</View>
);
在代码中修改:
const ScreenWidth = Dimensions.get('window').width;
// 样式
const styles = StyleSheet.create({
...
advContent: {
width: ScreenWidth,
height: 180
}
...
});
return (
<View style={styles.ad_banner}>
<ScrollView
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
ref = {com => this._banner = com}
>
<TouchableHighlight
onPress={() => {
Alert.alert('点击banner', null, null);
}}>
<Image
style={styles.advContent}
source={image_list.img1}/>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
Alert.alert('点击banner', null, null);
}}>
<Image
style={styles.advContent}
source={image_list.img2}/>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
Alert.alert('点击banner', null, null);
}}>
<Image
style={styles.advContent}
source={image_list.img3}/>
</TouchableHighlight>
</ScrollView>
</View>
);
在banner中TouchableHighlight大部分代码还是重复的,只有很少的区别,可以使用js中的map()方法来进一步简化,类似java中的for in操作。
//定义广告数据
const Advertisements = [
{title: '广告1', src: image_list.img1},
{title: '广告2', src: image_list.img2},
{title: '广告3', src: image_list.img3}
];
//修改代码
return (
<View style={styles.ad_banner}>
<ScrollView
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
ref = {com => this._banner = com}
>
{Advertisements.map((adv, index) => {
return (
<TouchableHighlight key={index} onPress={() => Alert.alert('点击banner:' + index, null, null)}>
<Image style={styles.advContent} source={adv.src}/>
</TouchableHighlight>
);
})}
</ScrollView>
</View>
);
注:TouchableHighlight中的key={index}不能省略
代码少了很多,后面可以直接通过修改Advertisements的值来改变Banner。
3.搜索框功能-TextInput
点击搜索按钮后,可根据输入的内容进行搜索,首先要获取到输入的内容,这里需要使用到两个知识点
- 使用TextInput中的onChangeText方法可以监听文本的变化
- 使用useState可以将文本的内容保存下来
具体实现就是:
const SearchBar = () => {
const [searchText, setSearchText] = useState('');
return (
<View style={styles.search_bar}>
<TextInput style={styles.input} placeholder="搜索" onChangeText={(text) => {
setSearchText(text);
}}>{searchText}</TextInput>
<Button onPress={() => {
Alert.alert("输入内容:" + searchText, null, null);
}} title="搜索"></Button>
</View>
);
}
效果:
4.banner指示器
一般广告控件都会增加一个指示器,用于标记当前显示的是第几个。我们可以偷懒一下,使用文本符号•来代表圆点,具体改动如下:
// 样式
const styles = StyleSheet.create({
...
indicator: {
width: ScreenWidth,
height: 20,
backgroundColor: 'rgba(0,0,0,0)',
alignItems: 'center',
justifyContent: 'center',
top: 150,
position: 'absolute',
flexDirection: 'row'
}
});
return (
<View style={styles.ad_banner}>
<ScrollView
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
ref = {com => this._banner = com}
>
{Advertisements.map((adv, index) => {
return (
<TouchableHighlight key={index} onPress={() => Alert.alert('点击banner:' + index, null, null)}>
<Image style={styles.advContent} source={adv.src}/>
</TouchableHighlight>
);
})}
</ScrollView>
<View style={[styles.indicator]}>
{Advertisements.map((_, index) => {
return (
<Text key={index} style={[(index === pageNumber) ? {color: 'orange'} : {color: 'white'}, {fontSize: 24}]}>
•
</Text>
);
})}
</View>
</View>
);
};
效果:
5.商品列表-FlatList+Refresh
我们将商品列表改为Android中ListView的常用格式,左侧为icon,右侧为text,改动如下:
// 样式
const styles = StyleSheet.create({
...
product_list: {
flex: 1,
justifyContent: 'center'
},
product_item: {
padding: 10,
fontSize: 18,
height: 44,
justifyContent: 'flex-start',
flexDirection: 'row',
alignItems: 'center'
},
product_img: {
height: 40,
width: 40,
borderWidth: 0.5,
borderColor: 'gray'
}
...
});
const Products = () => {
var data = [];
for (var i = 0; i < 100; i++) {
data.push({key: i, title: "商品" + i, desc: "这是描述信息"});
}
_renderImte = (item) => {
return (
<View style={styles.product_item}>
<Image style={styles.product_img} source={require('./images/product_icon.png')}/>
<View>
<Text >{item.title}</Text>
<Text >{item.desc}</Text>
</View>
</View>
);
};
return (
<View style={styles.product_list}>
<FlatList
style={{flex: 1}}
data={data}
renderItem={({item}) => this._renderImte(item)}/>
</View>
);
}
将renderItem提取成一个函数,处理item的View,增加了左侧的图片引用,效果如下:
flatList中还提供了Header、Footer、SeparateLine等的实现,如下:
_seperateLine = () => {
return (
<View style={{height: 1, backgroundColor: 'purple'}}></View>
);
};
_itemHeader = () => {
return (
<Text style={{borderColor: 'red', borderWidth: 1}}>这是Header</Text>
);
};
_itemFooter = () => {
return (
<Text style={{borderColor: 'yellow', borderWidth: 1}}>这是Footer</Text>
);
};
return (
<View style={styles.product_list}>
<FlatList
style={{flex: 1}}
data={data}
renderItem={({item}) => this._renderImte(item)}
ItemSeparatorComponent={this._seperateLine}
ListHeaderComponent={this._itemHeader}
ListFooterComponent={this._itemFooter}/>
</View>
);
效果:
增加下拉刷新,Flatlist中也提供了这个方法,真是太贴心了
const Products = () => {
const [refreshing, setRefreshing] = useState(false)
var data = [];
for (var i = 0; i < 20; i++) {
data.push({key: i, title: "商品" + i, desc: "这是描述信息"});
}
_renderImte = (item) => {
return (
<View style={styles.product_item}>
<Image style={styles.product_img} source={require('./images/product_icon.png')}/>
<View>
<Text >{item.title}</Text>
<Text >{item.desc}</Text>
</View>
</View>
);
};
_seperateLine = () => {
return (
<View style={{height: 1, backgroundColor: 'purple'}}></View>
);
};
_loadData = () => {
setRefreshing(true);
setTimeout(() => {
Alert.alert("刷新完成", null, null);
setRefreshing(false);
}, 2000);
};
return (
<View style={styles.product_list}>
<FlatList
style={{flex: 1}}
data={data}
renderItem={({item}) => this._renderImte(item)}
ItemSeparatorComponent={this._seperateLine}
refreshing={refreshing}
onRefresh={
this._loadData
}/>
</View>
);
}
效果:
使用onRefresh、refreshing两个方法即可轻松实现
6.页面跳转-ReactNavigation
页面跳转需要用到一个控件ReactNavigation,使用方法很简单:
第一步:安装控件及其依赖的控件
npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack
第二步:定义路由文件,用于跳转,相当于Android中的ARouter,声明完之后就可以用对于的Key进行跳转
//router.js文件
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Launch">
<Stack.Screen name="Launch" component={Launcher} options={{headerShown: false}}/>
<Stack.Screen name="Main" component={MainApp} options={{headerShown: false}}/>
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
第三步,跳转方法,在ReactNavigation声明过的控件中,props包含有navigation相关的值,可以打印一下日志查看,调用props.navigation.navigate就可以跳转界面啦,同样的还可以在第二个参数里传值
props.navigation.navigate('Main', {itemId: 1024, otherParm: 'fffffa'});
基于上面的说明,可以做一个这样的实现:增加一个launch界面,2s后跳转进入主界面;点击商品列表中的项,进入详情页面,实现代码如下:
//router.js
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Launch">
<Stack.Screen name="Launch" component={Launcher} options={{headerShown: false}}/>
<Stack.Screen name="Main" component={MainApp} options={{headerShown: false}}/>
<Stack.Screen name="Detail" component={DetailPage}/>
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
//luanch.js
export const Launcher = (props) => {
useEffect(() => {
setTimeout(() => {
props.navigation.replace('Main', {itemId: 1024, otherParm: 'fffffa'});
}, 2000);
return () => {
}
}, [])
return (
<Image style={{flex: 1, width: Dimensions.get('window').width, height: Dimensions.get('window').height}} source={require('./images/product_icon.png')}/>
);
}
//main.js
_renderImte = (item) => {
return (
<TouchableHighlight key={item.key} onPress={() => {
props.navigation.navigate('Detail', {itemId: 1234, otherParams: item.title})
}}>
<View style={styles.product_item}>
<Image style={styles.product_img} source={require('./images/product_icon.png')}/>
<View>
<Text >{item.title}</Text>
<Text >{item.desc}</Text>
</View>
</View>
</TouchableHighlight>
);
};
//detail.js
export function DetailPage({route, navigation}) {
const name = route.params.otherParams
return (
<View>
<Text>123123123</Text>
<Text>{name}</Text>
</View>
);
}
目前为止,App的效果如下:
我们已经使用到了很多个常用的功能控件,其中部分只是一笔带过,比如下拉刷新等还有很多API可以使用,有空多查一下文档,多写写就熟悉了,重要的还是实战。
最近以我的菜鸟水平也开始被安排写一些项目中的RN代码了,有了收获会继续分享出来的。