ViewPager是Android中比较常见的页面切换控件, 同时, 在UIExplorerApp
中也有ViewPagerAndroid
的示例. 通过使用这个控件, 理解ReactNative的实现逻辑. 我们现在来分析一下ViewPager的使用方式和ReactNative的编程要点, 本文注释也很清晰.
效果
1. 准备
新建ReactNative的项目.
npm install -g react-native-cli
react-native init [TestViewPager]
-g
是全局,react-native-cli
是react-native的命令行工具(command line interface). 安装需要管理员权限(sudo), 安装一次即可, 使用react-native命令. 参考.
修改项目架构, 主页直接跳转至ViewPager模块.
'use strict';
var React = require('react-native');
var {
AppRegistry,
} = React;
var ViewPagerModule = require('./ViewPagerModule/index')
AppRegistry.registerComponent('TestViewPager', () => ViewPagerModule);
2. 概述
ViewPager包含若干滑动页面; 点赞选项-模拟页面交互; 按键和滚动条-模拟滚动监听.
'use strict'
var React = require('react-native');
var {
View,
Text,
Image,
TouchableNativeFeedback, // 触碰响应
TouchableOpacity, // 触碰更换透明度的属性
ViewPagerAndroid, // Android的ViewPager
} = React;
// Styles
var styles = require('./style');
var PAGES = 5; // 页数
// 颜色
var BGCOLOR = ['#8ad3da', '#eecde2', '#e682b4', '#b7badd','#f1c7dd'];
// 本地图片地址
var IMAGE_URIS = [
require('./images/jessicajung.png'),
require('./images/tiffany.png'),
require('./images/seohyun.png'),
require('./images/taeyeon.png'),
require('./images/yoona.png'),
];
// 名称
var NAMES = ['Jessica', 'Tiffany', 'Seohyun', 'Taeyeon', 'Yoona'];
/**
* 点赞功能页面
* @param {likes: 点赞数}
* @return {点赞视图} [点赞按钮, 动态增加点赞数]
*/
var LikeCount = React.createClass({
// 初始化状态
getInitialState: function() {
return {
likes: 0,
};
},
// 点击增加
onClick: function() {
this.setState({likes: this.state.likes + 1});
},
render: function() {
var thumbsUp = '\uD83D\uDC4D'; // 图标
return (
<View style = {styles.likeContainer}>
<TouchableOpacity
onPress={this.onClick}
style={styles.likeButton}>
<Text style={styles.likesText}>
{thumbsUp}
</Text>
</TouchableOpacity>
<Text style={styles.likesText}>
{this.state.likes + ' 喜欢'}
</Text>
</View>
);
},
});
/**
* 按钮: 添加点击状态(enabled)和文本(text)
* @param {enabled:点击状态} {text:显示文本} {onPress:点击事件}
* @return {TouchableNativeFeedback} [触摸反馈的视图]
*/
var Button = React.createClass({
_handlePress: function() {
if (this.props.enabled && this.props.onPress) {
this.props.onPress();
}
},
render: function() {
return (
<TouchableNativeFeedback onPress={this._handlePress}>
<View style={[styles.button, this.props.enabled ? {} : styles.buttonDisabled]}>
<Text style={styles.buttonText}>
{this.props.text}
</Text>
</View>
</TouchableNativeFeedback>
);
}
});
/**
* 滚动条, fractionalPosition滚动条长度, progressBarSize当前大小
* @param {size:滚动条大小} {progress:过程}
* @return {View} [里外两层视图, 背景白框黑底, 显示白框]
*/
var ProgressBar = React.createClass({
render: function() {
var fractionalPosition = (this.props.progress.position + this.props.progress.offset);
var progressBarSize = (fractionalPosition / (PAGES - 1)) * this.props.size;
return (
<View style={[styles.progressBarContainer, {width: this.props.size}]}>
<View style={[styles.progressBar, {width: progressBarSize}]}/>
</View>
);
}
});
var ViewPagerModule = React.createClass({
/**
* 初始化状态
* @return {状态} [页面]
*/
getInitialState: function() {
return {
page: 0, // 当前位置
progress: { // Progress位置
position: 0,
offset: 0,
}
};
},
// 页面选择
onPageSelected: function(e) {
this.setState({page: e.nativeEvent.position});
},
// 页面滚动
onPageScroll: function(e) {
this.setState({progress: e.nativeEvent});
},
// 移动页面
move: function(delta) {
var page = this.state.page + delta;
this.go(page);
},
// 跳转页面
go: function(page) {
this.viewPage.setPage(page);
this.setState({page});
},
render: function() {
var pages = [];
for (var i=0; i<PAGES; i++) {
// 背景
var pageStyle = {
backgroundColor: BGCOLOR[i % BGCOLOR.length],
alignItems: 'center',
padding: 20,
}
pages.push(
<View
key={i}
style={pageStyle}
collapsable={false}>
<Image
style={styles.image}
resizeMode={'cover'}
source={IMAGE_URIS[i%PAGES]}
/>
<Text style={styles.nameText}>
{NAMES[i%PAGES]}
</Text>
<LikeCount />
</View>
);
}
var {page} = this.state;
return (
<View style={styles.container}>
<ViewPagerAndroid
style={styles.viewPager}
initialPage={0}
onPageScroll={this.onPageScroll}
onPageSelected={this.onPageSelected}
ref={viewPager => {this.viewPage = viewPager;}}>
{pages}
</ViewPagerAndroid>
<View style={styles.buttons}>
<Button
text="首页"
enabled={page > 0}
onPress={() => this.go(0)}/>
<Button
text="上一页"
enabled={page > 0}
onPress={() => this.move(-1)}/>
<Text style={styles.buttonText}>
页 {page+1} / {PAGES}
</Text>
{/*进度条*/}
<ProgressBar
size={80}
progress={this.state.progress}/>
<Button
text="下一页"
enabled={page < PAGES - 1}
onPress={() => this.move(1)}/>
<Button
text="尾页"
enabled={page < PAGES - 1}
onPress={() => this.go(PAGES -1)}/>
</View>
</View>
);
},
});
module.exports = ViewPagerModule;
引入RN的原生模块
var {
View,
Text,
Image,
TouchableNativeFeedback, // 触碰响应
TouchableOpacity, // 触碰更换透明度的属性
ViewPagerAndroid, // Android的ViewPager
} = React;
TouchableNativeFeedback
, 接触时会受到原生的响应;TouchableWithoutFeedback
, 接触时无响应.TouchableOpacity
, 接触时会改变透明度. 在加入onclick
方法时, 可以模拟按钮视图.
定义控件: LikeCount
点赞, Button
按钮, ProgressBar
滚动条.
3. 点赞(LikeCount)控件
likes
存储喜欢的数量. 点击时, 更新数量状态, 刷新页面. 其中, 喜欢按钮
是触碰控件, 在接触时会改变透明度(Opacity).
/**
* 点赞功能页面
* @param {likes: 点赞数}
* @return {点赞视图} [点赞按钮, 动态增加点赞数]
*/
var LikeCount = React.createClass({
// 初始化状态
getInitialState: function() {
return {
likes: 0,
};
},
// 点击增加
onClick: function() {
this.setState({likes: this.state.likes + 1});
},
render: function() {
var thumbsUp = '\uD83D\uDC4D'; // 图标
return (
<View style = {styles.likeContainer}>
<TouchableOpacity
onPress={this.onClick}
style={styles.likeButton}>
<Text style={styles.likesText}>
{thumbsUp}
</Text>
</TouchableOpacity>
<Text style={styles.likesText}>
{this.state.likes + ' 喜欢'}
</Text>
</View>
);
},
});
直接调用, 即可使用.
<LikeCount />
state的like属性, 仅仅作用于当前页面, 每次更新时刷新.
注意: 一定要谨慎使用全局state
, 这样会刷新整个页面, 影响效率.
4. 按钮(Button)控件
添加使用状态
, 是否可以点击, 根据状态修改视图样式和点击回调.
/**
* 按钮: 添加点击状态(enabled)和文本(text)
* @param {enabled:点击状态} {text:显示文本} {onPress:点击事件}
* @return {TouchableNativeFeedback} [触摸反馈的视图]
*/
var Button = React.createClass({
_handlePress: function() {
if (this.props.enabled && this.props.onPress) {
this.props.onPress();
}
},
render: function() {
return (
<TouchableNativeFeedback onPress={this._handlePress}>
<View style={[styles.button, this.props.enabled ? {} : styles.buttonDisabled]}>
<Text style={styles.buttonText}>
{this.props.text}
</Text>
</View>
</TouchableNativeFeedback>
);
}
});
在RN中, 并没有提供Button视图, 可以使用
Touchable
类的视图, 设置点击事件, 如TouchableNativeFeedback
, 根据状态, 修改按钮的样式和点击. 注意:props
表示属性, 在使用视图时提供.
使用Button, 设置text
显示, enabled
状态, onPress
点击事件.
<Button
text="首页"
enabled={page > 0}
onPress={() => this.go(0)}/>
5. 滚动条(ProgressBar)控件
主要监听ViewPager的滚动事件, 这和Android的滚动非常类似, fractionalPosition
是偏移比例, progressBarSize
是偏移量.
/**
* 滚动条, fractionalPosition滚动条长度, progressBarSize当前大小
* @param {size:滚动条大小} {progress:过程}
* @return {View} [里外两层视图, 背景白框黑底, 显示白框]
*/
var ProgressBar = React.createClass({
render: function() {
var fractionalPosition = (this.props.progress.position + this.props.progress.offset);
var progressBarSize = (fractionalPosition / (PAGES - 1)) * this.props.size;
return (
<View style={[styles.progressBarContainer, {width: this.props.size}]}>
<View style={[styles.progressBar, {width: progressBarSize}]}/>
</View>
);
}
});
控件需要设置
ProgressBar
的属性progress
(滚动位置)和size
(大小).
使用控件, 属性: size大小; progress进度状态, 其中position是位置, offset是偏移.
progress: { // Progress位置
position: 0,
offset: 0,
}
...
<ProgressBar
size={80}
progress={this.state.progress}/>
注意: progress是主页面属性, 重置状态时, 刷新全部页面.
6. ViewPager页面
根据页面定制属性, 根据页面ID设置不同图片和文字.
for (var i=0; i<PAGES; i++) {
// 背景
var pageStyle = {
backgroundColor: BGCOLOR[i % BGCOLOR.length],
alignItems: 'center',
padding: 20,
}
pages.push(
<View
key={i}
style={pageStyle}
collapsable={false}>
<Image
style={styles.image}
resizeMode={'cover'}
source={IMAGE_URIS[i%PAGES]}
/>
<Text style={styles.nameText}>
{NAMES[i%PAGES]}
</Text>
<LikeCount />
</View>
);
}
注意设置图片资源时,
使用IMAGE_URIS =['xxx.png']
和require(IMAGE_URIS[i%PAGES])
会显示模块丢失.
在数组IMAGE_URIS
使用require('./images/jessicajung.png')
加载source就可以.
思考很久的问题...
ViewPager页面
<View style={styles.container}>
<ViewPagerAndroid
style={styles.viewPager}
initialPage={0}
onPageScroll={this.onPageScroll}
onPageSelected={this.onPageSelected}
ref={viewPager => {this.viewPage = viewPager;}}>
{pages}
</ViewPagerAndroid>
<View style={styles.buttons}>
<Button
text="首页"
enabled={page > 0}
onPress={() => this.go(0)}/>
<Button
text="上一页"
enabled={page > 0}
onPress={() => this.move(-1)}/>
<Text style={styles.buttonText}>
页 {page+1} / {PAGES}
</Text>
{/*进度条*/}
<ProgressBar
size={80}
progress={this.state.progress}/>
<Button
text="下一页"
enabled={page < PAGES - 1}
onPress={() => this.move(1)}/>
<Button
text="尾页"
enabled={page < PAGES - 1}
onPress={() => this.go(PAGES -1)}/>
</View>
</View>
使用方法, 监听ViewPagerAndroid
的事件, onPageScroll
页面滚动, onPageSelected
页面选择, 按钮关联页面. 核心在于go
方法, 设置state
的viewPage
页.
// 跳转页面
go: function(page) {
this.viewPage.setPage(page);
this.setState({page});
},
7. Styles
引入styles
, 使用独立文件.
// Styles
var styles = require('./style');
样式
'use strict';
var React = require('react-native');
var {
StyleSheet,
} = React;
var styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
height: 40,
backgroundColor: 'pink',
alignItems: 'center',
justifyContent: 'space-between',
},
// 按钮可点击状态
button: {
flex: 1,
width: 0,
margin: 2,
borderColor: 'gray',
borderWidth: 1,
backgroundColor: 'gray',
},
// 按钮非点击装
buttonDisabled: {
backgroundColor: 'black',
opacity: 0.5,
},
buttonText: {
fontSize: 12,
color: 'white',
textAlign: 'center',
},
// 文字显示
nameText: {
fontSize: 16,
margin: 4,
color: 'white',
textAlign: 'center',
},
container: {
flex: 1,
backgroundColor: 'white',
},
image: {
flex: 1,
width: 300,
padding: 20,
},
likeButton: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
borderColor: '#333333',
borderWidth: 1,
borderRadius: 5,
flex: 1,
margin: 8,
padding: 8,
},
likeContainer: {
flexDirection: 'row',
},
likesText: {
flex: 1,
fontSize: 18,
alignSelf: 'center',
},
progressBarContainer: {
height: 10,
margin: 5,
borderColor: '#eeeeee',
borderWidth: 2,
},
progressBar: {
alignSelf: 'flex-start',
flex: 1,
backgroundColor: '#eeeeee',
},
viewPager: {
flex: 1,
},
});
module.exports = styles;
里面包含各个控件的样式, 主要使用了Flex+RN的Style.
动画效果
Github下载地址
OK, 通过实现简单的ViewPagerAndroid学习了很多知识.