【稀饭】react native 实战系列教程之首页列表UI实现

首页设计与实现

首先,这章节讲的是首页内容的设计与实现,不包括主界面的设计,因为一开始入手,我希望能立马获取到数据并能展示出来,后面再来搭木积似的一步一步把整体框架做起来。

设计

(图丑,莫见怪~)

主界面的设计如下

主界面设计

首页的设计如下

首页设计

有人可能会觉得,为什么不把TitleBar抽出来放在主界面,而要单独放在每个页面里。因为,这个跟你把Navigator(页面导航器)放在什么地方有关系,现在先不说为什么,就这么认为吧,不是每个页面都需要TitleBar的,放在各自页面,就可以很好的控制是否需要标题栏,需要时候再添加。

实现

这章节先实现首页部分。可以看到,最新和最热的内容上结构是一致的,所以,先定义一个组件来显示即可。

先来看下项目的目录结构:
目录结构

现在用的IDE是WebStorm(之前都是用sublime text 3 敲的代码,那个苦啊/(ㄒoㄒ)/~~)。

自定义组件

新建好js/component 文件夹之后,在component文件夹下创建DramaComponent.js文件

初始代码如下:

import React,{Component} from 'react';
import {
    View,
    Text,
} from 'react-native';

class DramaComponent extends Component{
    constructor(props){
        super(props);
    }

    render(){
        return(
            <View>
                <Text>
                    我是列表内容
                </Text>
            </View>
        );
    }
}

然后,我们这个组件是要作为公共组件的,要给别的组件引用,因此,我们需要将它‘暴露’出去,需要做如下处理:

export default class DramaComponent extends Component {
    ...
}

就是在定义class前加上export default

接着,我们在index.android.js使用import引用它:

import React, { Component} from 'react';
import {
  AppRegistry,
  View,
} from 'react-native';

import DramaComponent from './js/component/DramaComponent';

class XiFan extends Component {

  render(){
    return(
        <DramaComponent />
    );
  }
}

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

看下有没有正常被引用,使用命令行cd到项目根目录下,执行命令(如果已经启动并运行过了,模拟器下双击键盘R键,重新加载即可)

react-native run-android

启动命令

可以看到组件可以被正常使用并显示了。(默认你已经配置好调试环境,已经连接模拟器并设置好IP)

组件UI编写

可以正常被调用之后,我们就开始编写具体的UI层。

这是一个列表,我们优先想到的是用ListView这个控件

export default class DramaComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            movies:new ListView.DataSource({
                rowHasChanged:(r1,r2) => r1!=r2,
            }),
        }
    }
    //渲染ListView item view
    _renderMovieView(movie){

    }
    //ListView 拉到底部时调用
    _onEndReached(){

    }

    render() {
        return (
            <ListView
                dataSource = {this.state.movies}
                renderRow = {this._renderMovieView.bind(this)}
                style = {styles.listview}
                initialListSize = {10}
                pageSize = {10}
                onEndReachedThreshold = {5}
                onEndReached = {this._onEndReached.bind(this)}
                enableEmptySections = {true}
                contentContainerStyle = {styles.grid}
            />
        );
    }
}

ListView的几个属性:

dataSource:数据源

renderRow:ListView渲染的每一item view

initialListSize:初始渲染item的个数

pageSize:每次事件循环(每帧)渲染的行数

onEndReachedThreshold:调用onEndReached之前的临界值,单位是像素

onEndReached:当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用

更多属性介绍可以看下文档介绍

dataSource是一个动态数据,会变化,所以使用state来绑定数据。

每一部剧包含:影片名称、标题、主要演员、图片,还有点击item跳转时的详情链接,所以数据结构设计如下:

var dramaItem ={
    name:'',//影片名称
    title:'',//标题
    actor:'',//演员
    pic:'',//图片地址
    url:'',//详情链接
};

接着,我们要将item view绘制出来,_renderMovieView函数实现如下:


    //渲染ListView item view
    _renderMovieView(movie){
        return(
            <View style={styles.row} key={movie.url}>
                <TouchableOpacity onPress={this._onItemPress.bind(this,movie)} activeOpacity={0.8} >
                    <View>
                        <Image source={{uri:movie.pic}} style={styles.thumbnail}>
                            <Text style={styles.title}>{movie.title}</Text>
                        </Image>
                        <Text numberOfLines={1} style={styles.name}>{movie.name}</Text>
                        <Text numberOfLines={1} style={styles.actor}>{movie.actor}</Text>
                    </View>
                </TouchableOpacity>
            </View>

        );
    }

    //item 点击事件
    _onItemPress(movie){
        console.log(movie);
    }

这里关于UI如何布局就不详细说了,看着UI效果大家应该都知道怎么写了。这里需要注意的是TouchableOpacity 的onPress方法需要在点击的时候将movie对象传递下去,所以这里会有个组件的函数怎么传参的问题。

这里有两种方法:

  • 第一种

    如上面代码所示,在函数后面调用bind,第一个参数是上下文对象,后面就是参数对象数组

onPress={this._onItemPress.bind(this,movie)}
  • 第二种
onPress={()=>this._onItemPress(movie)}

一般使用第一种方法,因为很多时候在定义的方法_onItemPress内,会调用this.xxxx,这时候如果你没有使用_onItemPress.bind(this),将上下文对象传递下去,那么会报错,提示找不到xxxx方法或对象。

最后,我们在组件生命周期componentDidMount方法内,进行模拟数据:

componentDidMount(){
        var dramaItem ={
            name:'原来是美男啊',//影片名称
            title:'第16集中字',//标题
            actor:'张根硕',//演员
            pic:'http://img.y3600.com/d/file/p/2016/08/24/b216e94526fbf2d49f40dc5aaa1897a4.jpg',//图片地址
            url:'http://www.y3600.com/hanju/2016/907.html',//详情链接
        };
        var datas = [];
        datas.push(dramaItem);
        this.setState({
            movies:this.state.movies.cloneWithRows(datas),
        });
    }

这里关键是通过setState进行状态更新,进而刷新页面,要注意的是调用cloneWithRows进行数据填充,datas是个数据集合。关键代码:

this.setState({
    movies:this.state.movies.cloneWithRows(datas),
});

然后重新刷新(双击RR)

图片

这里只模拟了一条数据(多条数据多次调用datas.push即可),可以看到已经可以正常显示了,点击item也有log输出了(通过android studio查看logcat)

至此,我们已经实现了列表数据UI的编写,如果你运行上面的代码,你会发现少了些样式style,那下面贴下这节的全部代码:

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

export default class DramaComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            movies:new ListView.DataSource({
                rowHasChanged:(r1,r2) => r1!=r2,
            }),
        }
    }

    componentDidMount(){
        var dramaItem ={
            name:'原来是美男啊',//影片名称
            title:'第16集中字',//标题
            actor:'张根硕',//演员
            pic:'http://img.y3600.com/d/file/p/2016/08/24/b216e94526fbf2d49f40dc5aaa1897a4.jpg',//图片地址
            url:'http://www.y3600.com/hanju/2016/907.html',//详情链接
        };
        var datas = [];
        datas.push(dramaItem);
        this.setState({
            movies:this.state.movies.cloneWithRows(datas),
        });
    }

    //渲染ListView item view
    _renderMovieView(movie){
        return(
            <View style={styles.row} key={movie.url}>
                <TouchableOpacity onPress={this._onItemPress.bind(this,movie)} activeOpacity={0.8} >
                    <View>
                        <Image source={{uri:movie.pic}} style={styles.thumbnail}>
                            <Text style={styles.title}>{movie.title}</Text>
                        </Image>
                        <Text numberOfLines={1} style={styles.name}>{movie.name}</Text>
                        <Text numberOfLines={1} style={styles.actor}>{movie.actor}</Text>
                    </View>
                </TouchableOpacity>
            </View>

        );
    }

    _onItemPress(movie){
        console.log(movie);
    }

    //ListView 拉到底部时调用
    _onEndReached(){
        console.log('加载下一页');
    }

    render() {
        return (
            <ListView
                dataSource = {this.state.movies}
                renderRow = {this._renderMovieView.bind(this)}
                style = {styles.listview}
                initialListSize = {10}
                pageSize = {10}
                onEndReachedThreshold = {5}
                onEndReached = {this._onEndReached.bind(this)}
                enableEmptySections = {true}
                contentContainerStyle = {styles.grid}
            />
        );
    }
}
const WIN_WIDTH = Dimensions.get('window').width;
var width = WIN_WIDTH/3;
var styles = StyleSheet.create({
    grid:{
        justifyContent: 'flex-start',
        flexDirection: 'row',
        flexWrap: 'wrap'
    },
    row:{
        height:200,
        width:width,
        flexDirection:'column',
        justifyContent:'center',
        alignItems:'center',
        paddingTop:10,
        paddingBottom:10,
        marginTop:5,
        marginBottom:5,
    },
    thumbnail:{
        flex:1,
        width:width-20,
        height:140,
        justifyContent:'flex-end',
        resizeMode: Image.resizeMode.strech,
    },
    title:{
        fontSize:10,
        textAlign:'center',
        color:'white',
        backgroundColor:'#27272790',
    },
    name:{
        fontSize:12,
        width:width-20,
        color:'black',
        marginTop:8,
        marginBottom:5,
        textAlign:'center',
    },
    actor:{
        fontSize:10,
        color:'#707070',
        width:width-20,
        textAlign:'center',
    },
    listview:{
        backgroundColor:'#f5fcff',
    },
});

这里可能要提一下,我们使用的是ListView,但是最后效果好像是一个Android中GridView的效果,或者你可能会有疑问react native中如何实现GridView的效果。其实在react native中使用ListView在配合好style就可以实现GridView的效果了,主要样式如下:

    grid:{
        justifyContent: 'flex-start',
        flexDirection: 'row',
        flexWrap: 'wrap'
    },
    row:{
        height:200,
        width:width,
        flexDirection:'column',
        justifyContent:'center',
        alignItems:'center',
        paddingTop:10,
        paddingBottom:10,
        marginTop:5,
        marginBottom:5,
    },

grid是ListView的contentContainerStyle,justifyContent: ‘flex-start’使每个item靠左,flexDirection: ‘row’让item是水平从左往右排列,flexWrap: ‘wrap’是当一行item排列不下时自动换行。row是每个item的style,需要设置好height和width。

总结

本节讲了应用的的整体设计,并实现了列表UI,只是数据还是模拟阶段,主要技术点在于ListView的使用,熟悉dataSource、renderRow、ListView.DataSource、cloneWithRows以及函数的传参问题。下一节,我们在实现真实数据的获取和解析,使用到cheerio这个html解析库,如果你还不知道这是用来干嘛的,你可以先去了解下

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值