React Native之ViewPagerAndroid仿淘宝首页顶部分类布局效果实现

Posted 胖子爱你520

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Native之ViewPagerAndroid仿淘宝首页顶部分类布局效果实现相关的知识,希望对你有一定的参考价值。

转载请注明出处:http://blog.csdn.net/woshizisezise/article/details/51030410

大家好,趁着现在别人都去吃饭的时间,来给大家讲一讲React Native中android部分的控件ViewPagerAndroid的讲解,这里特别提醒一下,我写的博客都是基于大家有一些React Native基础的前提下,因为关于React Native这一系列的博文我是最近开始更新,由于自身工作繁重(每天早上10点干到晚上10点,是不是很惨?),所以暂时还没有那么多的精力从基础(比如安装啊,运行初始程序啊)开始写,以后有时间的话一定会将这部分给补回来,并给出链接,这里希望大家谅解一下哈!

首先我们来看看淘宝的效果以及我们最终完成的效果:
淘宝
第一页
点击单个条目
第二页

(一)基本概念

首先我们要知道这是个什么玩意儿,其实它就相当于Android原生的ViewPager,效果是一样一样的,只不过使用方式有点不相同,下面我们慢慢介绍。那么它能干什么,以及如何干,这都是我们了解一个控件需要知道的东西,那么第一步,我们去看官网是如何介绍这个控件的,官网给出的解释是酱紫的:

Container that allows to flip left and right between child views. Each child view of the ViewPagerAndroid will be treated as a separate page and will be stretched to fill the ViewPagerAndroid.

It is important all children are s and not composite components. You can set style properties like padding or backgroundColor for each child.

翻译成白话文的意思就是:

一个允许在子视图之间左右翻页的容器。每一个ViewPagerAndroid的子容器会被视作一个单独的页,并且会被拉伸填满ViewPagerAndroid。

注意所有的子视图都必须是纯View,而不能是自定义的复合容器。你可以给每个子视图设置样式属性譬如padding或backgroundColor。

  • 这里要特别的注意一下,ViewPagerAndroid的所有子视图都必须是纯View,而不能是自定义的复合容器。什么意思呢?意思就是说他的子节点必须是以< View >开头,以< /View >结尾,来看看官网给出的简单实例:
render: function() {
  return (
    <ViewPagerAndroid
      style={styles.viewPager}
      initialPage={0}>
      <View style={styles.pageStyle}>
        <Text>First page</Text>
      </View>
      <View style={styles.pageStyle}>
        <Text>Second page</Text>
      </View>
    </ViewPagerAndroid>
  );
}

...

var styles = {
  ...
  pageStyle: {
    alignItems: 'center',
    padding: 20,
  }
}

运行效果也很简单,和安卓原生的ViewPager的两个简单左右滑动的View布局一样,这里就不做演示了。

(二)它的属性

  • initialPage number

初始选中的页的下标。你可以用setPage 函数来翻页,并且用onPageSelected来监听页的变化。

  • keyboardDismissMode enum(‘none’, “on-drag”)

决定在滑动的时候是否要让软键盘消失。

none (默认值),拖拽不会让键盘消失。

on-drag, 当拖拽开始的时候会让键盘消失。

  • onPageScroll function

当在页间切换时(不论是由于动画还是由于用户在页间滑动/拖拽)执行。

回调参数中的event.nativeEvent对象会包含如下数据:

position 从左数起第一个当前可见的页面的下标。

offset 一个在[0,1)(大于等于0,小于1)之间的范围,代表当前页面切换的状态。值x表示现在”position”所表示的页有(1 -
x)的部分可见,而下一页有x的部分可见。

  • onPageScrollStateChanged function

页面滑动状态变化时调用此回调函数。页面滑动状态可能为以下三种之一:

idle 空闲,意味着当前没有交互。

dragging 拖动中,意味着当前页面正在被拖动。

settling 处理中,意味着当前页面发生过交互,且正在结束开头或收尾的动画。

  • onPageSelected function

这个回调会在页面切换完成后(当用户在页面间滑动)调用。

回调参数中的event.nativeEvent对象会包含如下的字段:

position 当前被选中的页面下标

ok,它的一些属性也写的比较明白,大家在使用过程中就可以去仔细体会了,下面我们正式进入主题,仿照美团首页顶部的viewpager轮播图,来用React Native实现。

(三)我的实例

1.首先,打开电脑的终端(windows平台是输入cmd就可以进入命令行工具),新建一个项目:react-native init ViewPagerDemo,等待新项目的创建。

2.项目创建完成后,打开目录下的index.android.js文件,这里介绍一下,我开发的编辑器是Atom,目前来说还是比较好用的,大家因人而异,这个没有关系。
这里写图片描述

打开后在安卓模拟上按Menu键(通常是F2,在Genymotion模拟器中是⌘+M)然后选择 Reload JS 就可以看到你的最新修改。初始运行效果如下:
这里写图片描述

下面我们正式开始编写我们的程序了,先提到一点就是,之前我在网上看到过类似的项目效果,但是写那个博客的人编写这样一套轮播图中包含很多子类的效果,从头到尾竟然都是硬写的,这样写出来的代码很长,也没有什么美感,我记得我的一位师傅告诉我说,当你的某个代码写的超过20行,说明这代码是有问题的,所以基于此,我并不想死死的写代码,因为我们可想而知的是这些类别到时候肯定是从服务器端请求返回的一个集合或者数组,如果是我们自己手写写死了布局,那到时候如何修改呢,所以我们写代码一定要灵活,不能把自己局限起来。

接下来我们来编写主页面的场景,这里由于以后可能会多次使用到该项目(不可能我每次写一个demo就新建一个工程吧,这样太糟蹋了,所以我决定将所有的项目写在同一个工程路径下,这里通过React Native提供的Navigator来控制我们要显示哪一个页面(嗯,说到这里我觉得Navigator也可以单独写一篇博客了。。。),下面是index.android.js文件的代码:

import React, {
  AppRegistry,
  StyleSheet,
  Text,
  Navigator,
  BackAndroid,
  View
} from 'react-native';

import ViewPagerScreen from './pages/ViewPagerScreen.js';

var _navigator;

BackAndroid.addEventListener('hardwareBackPress', function() {
  if (_navigator && _navigator.getCurrentRoutes().length > 1) {
      _navigator.pop();
      return true;
  }
  return false;
});

var DemosApplication = React.createClass({

  RouteMapper:function(route, navigationOperations, onComponentRef){
    _navigator = navigationOperations;
    switch (route.name) {
      case 'viewpagerscreen':
        return (
          <View style={styles.container}>
            <ViewPagerScreen navigator={navigationOperations}/>
          </View>
        );
    }
  },

  getInitialState: function() {
    return {
      splashed: true,
    };
  },

  render() {
    if (this.state.splashed) {
      var initialRoute = {name: 'viewpagerscreen'};
      return(
        <Navigator
          style={styles.container}
          initialRoute={initialRoute}
          configureScene={() => Navigator.SceneConfigs.FadeAndroid}
          renderScene={this.RouteMapper} />
      );
    }
  }
});

var styles = StyleSheet.create({
  wrapper: {
    borderRadius: 5,
    marginBottom: 5,
  },
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('DemosApplication', () => DemosApplication);

这里简单的说明一下,我是通过Navigator来控制页面显示效果的

<Navigator
          style={styles.container}
          initialRoute={initialRoute}
          configureScene={() => Navigator.SceneConfigs.FadeAndroid}
          renderScene={this.RouteMapper} />

然后路由在这里进行分发显示:

RouteMapper:function(route, navigationOperations, onComponentRef){
    _navigator = navigationOperations;
    switch (route.name) {
      case 'viewpagerscreen':
        return (
          <View style={styles.container}>
            <ViewPagerScreen navigator={navigationOperations}/>
          </View>
        );
    }
  },

由于我指定的路由名称为viewpagerscreen,所以这里会返回ViewPagerScreen组件,也就是我们今天的项目,接下来,我们来编写ViewPagerScreen页面。

我们在ViewPagerScreen.js页面首先定义一个数组模拟服务器返回的数据:

var source = [
  {title:'长途',icon:require('../asset/01.png')},
  {title:'交通',icon:require('../asset/02.png')},
  {title:'住宿',icon:require('../asset/03.png')},
  {title:'餐饮',icon:require('../asset/04.png')},
  {title:'补助',icon:require('../asset/05.png')},
  {title:'办公',icon:require('../asset/06.png')},
  {title:'福利',icon:require('../asset/07.png')},
  {title:'市场',icon:require('../asset/08.png')},
  {title:'研发',icon:require('../asset/09.png')},
  {title:'广告',icon:require('../asset/10.png')},
  {title:'促销',icon:require('../asset/11.png')},
  {title:'宣传',icon:require('../asset/12.png')},
  {title:'购物',icon:require('../asset/13.png')},
  {title:'其他',icon:require('../asset/14.png')},
  {title:'备用',icon:require('../asset/15.png')},
  {title:'火车',icon:require('../asset/16.png')},
  {title:'招待',icon:require('../asset/17.png')},
  {title:'通讯',icon:require('../asset/18.png')},
  {title:'材料',icon:require('../asset/19.png')}
];

然后我们声明一个组件,用于显示单个条目的布局,代码如下:

var PagerItem = React.createClass({
  render(){
    return(
      <View style = {{height:80,width:(WINDOW_WIDTH/5),justifyContent:'center'}}>
        <TouchableOpacity activeOpacity={0.8} onPress={()=>this.itemOnClick()}>
          <Image source={this.props.item.icon} style = {styles.imageStyle} />
        </TouchableOpacity>
        <Text style={styles.textStyle}>{this.props.item.title}</Text>
      </View>
    );
  },

  itemOnClick(){
    this.props.onItemClick();
  }
});

接着我们就来编写ViewPagerScreen组件,代码如下:

class ViewPagerScreen extends Component {

  renderPagerItem(item,index){
    return <PagerItem key={index} item={item} onItemClick={() => this.onItemClick(item)}/>;
  }

  onItemClick(item){
    this.toastMessage(item.title);
  }

  render() {
    return(
      <View style={{backgroundColor:'#ffffff',height:160,borderColor:'#546979',borderBottomWidth:0.5}}>
        <ViewPagerAndroid style = {{alignItems:'center',height:160}} initialPage={0}>
          {
            this.getAllPager()
          }
        </ViewPagerAndroid>
      </View>
    );
  }

  //返回ViewPager的所有View
  getAllPager(){
    var viewArray = [];
    var data1 = [],data2 = [],data = [];
    var page = 0;
    var result = Math.floor(source.length / 10);
    var rest = source.length % 10;
    if (result > 0) {
      page = rest > 0 ? result + 1 : result;
    }else{
      page = rest > 0 ? 1 : 0;
    }
    var num = page;
    for (var i = 0; i < page; i++) {
      data = num > 1 ? source.slice(i * 10,(i + 1) * 10) : source.slice(i * 10,source.length);
      viewArray.push(this.getPagerView(i,data));
      num--;
    }
    return viewArray;
  }

  //返回ViewPager的页面
  getPagerView(i,data){
    return(
      <View key={i}>
        {
          this.getRowView(data)
        }
      </View>
    );
  }

  //返回ViewPager一页View对象
  getRowView(array){
    if (array.length > 0) {
      return(
        <View style={{flexDirection:'row',flexWrap:'wrap'}}>
          {
            array.map((item,i) => this.renderPagerItem(item,i))
          }
        </View>
      );
    }
  }

  toastMessage(msg){
    ToastAndroid.show(msg,ToastAndroid.SHORT);
  }
}

这里最主要的方法就是对数据进行分析,一页最多展示10个数据,所以对于超出部分需要另起一页,所以我的做法是:

getAllPager(){
    var viewArray = [];
    var data1 = [],data2 = [],data = [];
    var page = 0;
    var result = Math.floor(source.length / 10);
    var rest = source.length % 10;
    if (result > 0) {
      page = rest > 0 ? result + 1 : result;
    }else{
      page = rest > 0 ? 1 : 0;
    }
    var num = page;
    for (var i = 0; i < page; i++) {
      data = num > 1 ? source.slice(i * 10,(i + 1) * 10) : source.slice(i * 10,source.length);
      viewArray.push(this.getPagerView(i,data));
      num--;
    }
    return viewArray;
  }

对于每一页数据是如何加载的,我们将数组通过map方法生成单个View对象并返回:

getRowView(array){
    if (array.length > 0) {
      return(
        <View style={{flexDirection:'row',flexWrap:'wrap'}}>
          {
            array.map((item,i) => this.renderPagerItem(item,i))
          }
        </View>
      );
    }
  }

到这里我们的工作就完成了,重新Reload JS,看看发生的奇妙变化吧,这里我们对单个条目的点击也做了处理,点击单个条目时,会返回当前条目的名称,好了,让我们来看看效果图吧:
这里写图片描述

这里写图片描述

这是第二页的效果:

这里写图片描述

效果是不是还可以呢?好了,如果有问题,欢迎大家在下方留言,看到我会回复大家,一起进步才是最重要的。

  • ViewPagerScreen.js完整代码
'use strict';

import React, {
  AppRegistry,
  StyleSheet,
  Text,
  ViewPagerAndroid,
  ToastAndroid,
  Component,
  Image,
  Dimensions,
  TouchableOpacity,
  View
} from 'react-native';

var WINDOW_WIDTH = Dimensions.get('window').width;
var WINDOW_HEIGHT = Dimensions.get('window').height;

var source = [
  {title:'长途',icon:require('../asset/01.png')},
  {title:'交通',icon:require('../asset/02.png')},
  {title:'住宿',icon:require('../asset/03.png')},
  {title:'餐饮',icon:require('../asset/04.png')},
  {title:'补助',icon:require('../asset/05.png')},
  {title:'办公',icon:require('../asset/06.png')},
  {title:'福利',icon:require('../asset/07.png')},
  {title:'市场',icon:require('../asset/08.png')},
  {title:'研发',icon:require('../asset/09.png')},
  {title:'广告',icon:require('../asset/10.png')},
  {title:'促销',icon:require('../asset/11.png')},
  {title:'宣传',icon:require('../asset/12.png')},
  {title:'购物',icon:require('../asset/13.png')},
  {title:'其他',icon:require('../asset/14.png')},
  {title:'备用',icon:require('../asset/15.png')},
  {title:'火车',icon:require('../asset/16.png')},
  {title:'招待',icon:require('../asset/17.png')},
  {title:'通讯',icon:require('../asset/18.png')},
  {title:'材料',icon:require('../asset/19.png')}
];

var PagerItem = React.createClass({
  render(){
    return(
      <View style = {{height:80,width:(WINDOW_WIDTH/5),justifyContent:'center'}}>
        <TouchableOpacity activeOpacity={0.8} onPress={()=>this.itemOnClick()}>
          <Image source={this.props.item.icon} style = {styles.imageStyle} />
        </TouchableOpacity>
        <Text style={styles.textStyle}>{this.props.item.title}</Text>
      </View>
    );
  },

  itemOnClick(){
    this.props.onItemClick();
  }
});

class ViewPagerScreen extends Component {

  renderPagerItem(item,index){
    return <PagerItem key={index} item={item} onItemClick={() => this.onItemClick(item)}/>;
  }

  onItemClick(item){
    this.toastMessage(item.title);
  }

  render() {
    return(
      <View style={{backgroundColor:'#ffffff',height:160,borderColor:'#546979',borderBottomWidth:0.5}}>
        <ViewPagerAndroid style = {{alignItems:'center',height:160}} initialPage={0}>
          {
            this.getAllPager()
          }
        </ViewPagerAndroid>
      </View>
    );
  }

  //返回ViewPager的所有View
  getAllPager(){
    var viewArray = [];
    var data1 = [],data2 = [],data = [];
    var page = 0;
    var result = Math.floor(source.length / 10);
    var rest = source.length % 10;
    if (result > 0) {
      page = rest > 0 ? result + 1 : result;
    }else{
      page = rest > 0 ? 1 : 0;
    }
    var num = page;
    for (var i = 0; i < page; i++) {
      data = num > 1 ? source.slice(i * 10,(i + 1) * 10) : source.slice(i * 10,source.length);
      viewArray.push(this.getPagerView(i,data));
      num--;
    }
    return viewArray;
  }

  //返回ViewPager的页面
  getPagerView(i,data){
    return(
      <View key={i}>
        {
          this.getRowView(data)
        }
      </View>
    );
  }

  //返回ViewPager一页View对象
  getRowView(array){
    if (array.length > 0) {
      return(
        <View style={{flexDirection:'row',flexWrap:'wrap'}}>
          {
            array.map((item,i) => this.renderPagerItem(item,i))
          }
        </View>
      );
    }
  }

  toastMessage(msg){
    ToastAndroid.show(msg,ToastAndroid.SHORT);
  }
}

var styles = StyleSheet.create({
  //消费类型部分
  imageStyle:{
    alignSelf:'center',
    width:45,
    borderRadius:22.5,
    height:45
  },
  textStyle:{
   marginTop:5,
   alignSelf:'center',
   fontSize:14,
   color:'#555555',
   textAlign:'center'
  },
});

module.exports = ViewPagerScreen;

欢迎大家订阅公众号,我会不定期更新资源,供大家一起学习。

这里写图片描述

以上是关于React Native之ViewPagerAndroid仿淘宝首页顶部分类布局效果实现的主要内容,如果未能解决你的问题,请参考以下文章

React Native开发React Native控件之ProgressBarAndroid进度条讲解(12)

React-Native入门指导之iOS篇 —— 一准备工作

React Native开发React Native控件之DrawerLayoutAndroid抽屉导航切换组件讲解(13)

React Native 之 Flex

REACT NATIVE 系列教程之九REACT NATIVE版本升级步骤与注意事项!

React Native开发React Native控件之WebView组件详解以及实例使用(22)