项目创建与目录结构设计
- app
- image
- *.(jpg|png|git|*): 图片资源
- api
- index.js: 项目所需接口
- constant
- index.js: 项目常量数据
- components
- Searchbar.js: 搜索条
- Advertisement.js: 轮播广告
- Products.js: 商品列表
- page
- Home.js 首页
- Profile.js 个人页
- navigation
- Main.js 入口Tab页导航
首页原型和代码

App.js
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
StatusBar
} from 'react-native';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
{/* 状态栏配置 */}
<StatusBar
hidden={false}
animated={true}
backgroundColor="#ccc"
barStyle="light-content"
translucent={false}
></StatusBar>
{/* 搜索条 */}
<View style={styles.searchbar}>
<Text>搜索框</Text>
</View>
{/* 轮播广告 */}
<View style={styles.advertisement}>
<Text>广告</Text>
</View>
{/* 商品列表 */}
<View style={styles.products}>
<Text>商品列表</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
searchbar: {
height: 40,
backgroundColor: "green"
},
advertisement: {
height: 200,
backgroundColor: "yellow",
},
products: {
flex: 1,
backgroundColor: "blue"
}
});
首页组件抽取
- 创建搜索条组件:
app/components/Searchbar.js - 创建轮播广告组件:
app/components/Adverticement.js - 创建商品列表组件:
app/components/Products.js
抽取后首页代码
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
StatusBar
} from 'react-native';
import Searchbar from './app/components/Searchbar';
import Adverticement from './app/components/Adverticement';
import Products from './app/components/Products';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
{/* 状态栏配置 */}
<StatusBar
hidden={false}
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: '#F5FCFF',
},
});
搜索条组件
import React, { Component } from 'react'
import {
Text,
StyleSheet,
View,
TextInput,
Button,
Alert
} from 'react-native'
export default class Searchbar extends Component {
constructor(props) {
super(props);
this.state = {
searchValue: ""
}
}
_changeText = (newValue) => {
this.setState({searchValue: newValue})
}
_search = () => {
Alert.alert(this.state.searchValue);
}
render() {
return (
<View style={styles.searchbar}>
<TextInput
placeholder="输入搜索关键字"
value={this.state.searchval}
onChangeText={this._changeText}
style={styles.input}
></TextInput>
<Button
title="搜索"
onPress={this._search}
style={styles.button}
></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: {
width: 40,
height: 30,
backgroundColor: "green"
}
})
轮播广告组件
版本一,使用ScrollView实现轮播效果
import React, { Component } from 'react'
import {
Text,
StyleSheet,
View,
ScrollView,
Dimensions,
Image
} from 'react-native'
export default class Adverticement extends Component {
constructor(props) {
super(props);
this.state = {
currentPage: 0,
advertisements: [
{
uri: "https://gw.alicdn.com/imgextra/i1/136/O1CN01xoz2hX1CsKQIYu7SH_!!136-0-lubanu.jpg"
},
{
uri: "https://img.alicdn.com/tps/i4/TB1Vi2mfy_1gK0jSZFqSuwpaXXa.jpg"
},
{
uri: "https://gw.alicdn.com/imgextra/i1/1/O1CN01JSzuqi1BsUxCNVonv_!!1-0-lubanu.jpg"
}
]
}
}
render() {
return (
<View style={styles.advertisement}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
>
{
this.state.advertisements.map((item, index) => {
return (
<View
key={index}
style={[styles.item, {backgroundColor: "blue"}]}>
<Image
source={item.uri}
style={styles.image}>
</Image>
</View>
)
})
}
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create({
advertisement: {
height: 200,
backgroundColor: "yellow",
},
item: {
width: Dimensions.get("window").width,
height: 200
}
})
版本二,自动轮播效果
import React, { Component } from 'react'
import {
Text,
StyleSheet,
View,
ScrollView,
Dimensions
} from 'react-native'
export default class Adverticement extends Component {
constructor(props) {
super(props);
this.state = {
currentPage: 0,
advertisements: ["1", "2", '3']
}
}
// 挂载后启动定时器
componentDidMount() {
this._startTimer();
}
// 下载后清除定时器
componentWillUnmount() {
this._endTimer();
}
_startTimer = () => {
this.timerId = setInterval(() => {
// 计算下一页码
let nextPage = this.state.currentPage + 1;
nextPage = nextPage >= this.state.advertisements.length? 0 : nextPage;
this.setState({currentPage: nextPage});
// 计算scrollView组件的offsetX值,实现自动轮播
let offsetX = Dimensions.get("window").width * this.state.currentPage;
this.refs.scrollView.scrollTo({x: offsetX, y: 0, animated: true});
}, 1500);
}
_endTimer = () => {
clearInterval(this.timerId);
}
render() {
return (
<View style={styles.advertisement}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
ref="scrollView"
>
<View style={[styles.item, {backgroundColor: "blue"}]}>
<Text>广告1</Text>
</View>
<View style={[styles.item, {backgroundColor: "pink"}]}>
<Text>广告2</Text>
</View>
<View style={[styles.item, {backgroundColor: "orange"}]}>
<Text>广告3</Text>
</View>
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create({
advertisement: {
height: 200,
backgroundColor: "yellow",
},
item: {
width: Dimensions.get("window").width,
height: 200
}
})
版本三,增加点状指示器
版本四,点击路由跳转
商品列表组件
import React, { Component } from 'react'
import {
Text,
StyleSheet,
View,
FlatList,
Image
} from 'react-native'
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{
id: "1",
title: "小米MIX3",
subTitle: "滑盖手机,咔咔咔",
image: ""
},
{
id: "2",
title: "华为Mate20",
subTitle: "黑科技,牛逼牛逼",
image: ""
},
{
id: "3",
title: "魅族",
subTitle: "漂亮无需多言",
image: ""
},
{
id: "4",
title: "锤子",
subTitle: "漂亮的不像实力派",
image: ""
},
{
id: "5",
title: "三星",
subTitle: "我的电池绝对靠谱",
image: ""
},
{
id: "6",
title: "苹果",
subTitle: "我的价格是真的不贵",
image: ""
}
]
}
}
_renderItem = ({item, index}) => {
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>
)
}
_keyExtractor = (item, index) => {
return item.id;
}
render() {
return (
<FlatList
data={this.state.products}
renderItem={this._renderItem}
keyExtractor={this._keyExtractor}
></FlatList>
)
}
}
const styles = StyleSheet.create({
products: {
flex: 1,
backgroundColor: "blue"
},
item: {
flexDirection: 'row',
justifyContent: "center",
alignContent: 'center',
marginHorizontal: 10,
marginTop: 10,
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"
}
})
首页完整代码
import React from "react";
import {
Platform,
Dimensions,
StyleSheet,
Text,
View,
StatusBar,
ScrollView,
TextInput,
Button,
FlatList,
TouchableNativeFeedback,
Alert,
Image,
RefreshControl
} from 'react-native';
const width = Dimensions.get("window").width;
const circleSize = 8;
const circleMargin = 5;
export default class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
currentPage: 0,
searchText: "xxx",
isRefreshing: false,
advertisements: [
{
title: "美女",
backgroundColor: "orange",
image: require("../../img/double-11.png")
},
{
title: "豪车",
backgroundColor: "purple",
image: require("../../img/eyes.png")
},
{
title: "美景",
backgroundColor: "skyblue",
image: require("../../img/five-year.png")
}
],
products: [
{key: 'Devin', data: {
image: require("../../img/eyes.png"),
title: "商品1",
subTitle: "描述1"
}},
{key: 'Jackson', data: {
image: require("../../img/eyes.png"),
title: "商品2",
subTitle: "描述2"
}},
{key: 'James', data: {
image: require("../../img/eyes.png"),
title: "商品3",
subTitle: "描述3"
}},
{key: 'Joel', data: {
image: require("../../img/eyes.png"),
title: "商品4",
subTitle: "描述4"
}},
{key: 'John', data: {
image: require("../../img/eyes.png"),
title: "商品5",
subTitle: "描述5"
}},
{key: 'Jillian', data: {
image: require("../../img/eyes.png"),
title: "商品6",
subTitle: "描述6"
}},
{key: 'Jimmy', data: {
image: require("../../img/eyes.png"),
title: "商品7",
subTitle: "描述7"
}},
{key: 'Julie', data: {
image: require("../../img/eyes.png"),
title: "商品8",
subTitle: "描述8"
}},
]
}
}
componentDidMount() {
this._startTimer()
}
componentWillUnmount() {
this._endTimer();
}
_startTimer() {
this.timer = setInterval(() => {
let nextPage = this.state.currentPage + 1;
nextPage = nextPage < 3? nextPage : 0;
this.setState({currentPage: nextPage});
const offsetX = width * nextPage;
this.refs.scrollView.scrollTo({x: offsetX, y: 0, animated: true})
}, 2000);
}
_endTimer() {
clearInterval(this.timer);
}
_scrollBeginDrag = () => {
this._endTimer();
}
_scrollEndDrag = () => {
this._startTimer();
}
_renderItem = ({item}) => {
return (
<TouchableNativeFeedback onPress={() => {
const {navigation} = this.props;
navigation && navigation.push("detail", {a:"传参"})
}}>
<View style={styles.productRow}>
<Image style={styles.productImage} source={item.data.image}></Image>
<View style={styles.productText}>
<Text style={styles.productTitle}>{item.data.title}</Text>
<Text style={styles.productSubtitle}>{item.data.subTitle}</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
_renderSeparator = ({index}) => {
return (
<View key={index} style={styles.divider}></View>
)
}
_renderRefreshContrl = () => {
this.setState({isRefreshing: true});
setTimeout(() => {
const newProducts = Array.from(Array(10)).map((v, i) => ({
key: ""+i,
data: {
image: require("../../img/eyes.png"),
title: `新商品${i+1}`,
subTitle: `新描述${i+1}`
}
}));
this.setState({isRefreshing: false, products: newProducts});
}, 2000);
}
render() {
// 计算轮播指示器位置
const advertisementCount = this.state.advertisements.length;
const indicatorWidth = circleSize * advertisementCount
+ circleMargin * advertisementCount * 2;
const indicatorOffsetX = (width - indicatorWidth) / 2;
return (
<View style={styles.container}>
{/* 状态栏背景色和透明只在Android中有效 */}
<StatusBar
barStyle="light-content"
backgroundColor="#eee"
translucent={false}>
</StatusBar>
{/* 搜索条 */}
<View style={styles.searchBar}>
<TextInput
style={styles.searchBar_input}
underlineColorAndroid="transparent"
onChangeText={text => {
this.setState({searchText: text});
console.log(text);
}}
placeholder="搜索商品">
</TextInput>
<Button
style={styles.searchBar_button}
color="#841584"
onPress={() => {
Alert.alert(`搜索内容:${this.state.searchText}`);
}}
title="搜索">
</Button>
</View>
{/* 轮播广告 */}
<View style={styles.advertisement}>
<ScrollView
ref="scrollView"
onScrollBeginDrag={this._scrollBeginDrag}
onScrollEndDrag={this._scrollEndDrag}
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}>
{
this.state.advertisements.map((item, i) => {
return (
<TouchableNativeFeedback key={i}>
<Image
source={item.image}
style={[styles.advertisement_item, {
backgroundColor: item.backgroundColor
}]}>
</Image>
</TouchableNativeFeedback>
)
})
}
</ScrollView>
<View style={[
styles.indicator, {
left: indicatorOffsetX
}
]}>
{
this.state.advertisements.map((item, i) => {
const dynamicStyle =
i === this.state.currentPage
? styles.circleSelected
: {};
return (<View key={i} style={[styles.circle, dynamicStyle]}></View>)
})
}
</View>
</View>
{/* 商品列表 */}
<View style={styles.products}>
{/* 默认情况下每行都需要提供一个不重复的 key 属性。你也可以提供一个keyExtractor函数来生成 key。 */}
<FlatList
data={this.state.products}
renderItem={this._renderItem}
onRefresh={this._renderRefreshContrl}
refreshing={this.state.isRefreshing}
ItemSeparatorComponent={this._renderSeparator}>
{/* refreshControl={<RefreshControl
refreshing={this.state.isRefreshing}
onRefresh={this._renderRefreshContrl}
title="正在刷新"
colors={["red", "yellow"]}
progressBackgroundColor="blue"
progressViewOffset={50}
/>}> */}
</FlatList>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
searchBar: {
flexDirection: "row",
padding: 4,
marginTop: 24,
height: 40,
backgroundColor: "#eee"
},
searchBar_input: {
flex: 1,
borderWidth: 2,
borderRadius: 8,
borderColor: "gray",
marginRight: 6,
paddingLeft: 10
},
searchBar_button: {
color: "white",
},
advertisement: {
height: 180,
backgroundColor: "green"
},
advertisement_item: {
width: width,
height: "100%",
backgroundColor: "red",
},
indicator: {
position: "absolute",
top: 160,
flexDirection: "row"
},
circle: {
width: circleSize,
height: circleSize,
borderRadius: circleSize / 2,
backgroundColor: '#ccc',
marginHorizontal: circleMargin
},
circleSelected: {
backgroundColor: '#fff',
},
products: {
flex: 1,
backgroundColor: "#eee"
},
productRow: {
flexDirection: "row",
alignItems: 'center',
height: 60
},
productImage: {
alignSelf: 'center',
marginHorizontal: 10,
width: 40,
height: 40
},
productText: {
flex: 1,
marginVertical: 10,
},
productTitle: {
flex: 3,
fontSize: 16
},
productSubtitle: {
flex: 2,
fontSize: 14,
color: "#ccc"
},
divider: {
height: 1,
marginHorizontal: 5,
backgroundColor: "lightgray"
}
});
Tab路由管理
运行yarn add react-navigation安装路由插件。
Tab首页
把App.js内容复制到app/page/Home.js
Tab个人页
创建app/page/Profile.js
import React from "react";
import { StyleSheet, View, Text } from 'react-native';
export default MovieDetail = (props) => {
return (
<View style={styles.container}>
<Text>个人信息</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
});
Tab路由
app/page/index.js
import React from "react";
import Home from './home'
import Profile from './profile'
export default createBottomTabNavigator(
{
// 路由配置,key值会作为tabBar的label显示
Home: {
screen: Home,
navigationOptions: () => ({
tabBarLabel: "首页"
})
},
Profile: {
screen: Profile,
navigationOptions: () => ({
tabBarLabel: "我的"
})
},
},
{
}
)
主路由
商品详情页
app/page/Detail.js
import React, { Component } from 'react';
import { Text, StyleSheet, View, TouchableOpacity } from 'react-native'
export default class Detail extends Component {
constructor(props) {
super(props);
this.navigation = this.props.navigation;
this.navigationParams = this.navigation.state.params;
}
_pressBack = () => {
const {navigation} = this.props;
navigation && navigation.pop();
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._pressBack}>
<Text style={styles.back}>返回{this.navigationParams.a}</Text>
</TouchableOpacity>
<Text style={styles.text}> 商品详情 </Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: 'center',
},
text: {
fontSize: 20
},
back: {
fontSize: 20,
color: "yellow"
}
})
主路由配置
App.js
import {createStackNavigator} from 'react-navigation';
import Main from './app/page/Main';
import Detail from './app/page/Detail';
export default createStackNavigator(
{
main: {
screen: Main,
navigationOptions: ({navigation, navigationOptions}) => ({
header: null,
})
},
detail: {
screen: Detail,
navigationOptions: ({navigationOptions}) => ({
title: "商品详情",
})
}
},
{
initialRouteName: "main"
}
)