8. react-native-android之----模拟手机百度feed流

非 Y 不嫁゛ 提交于 2020-04-16 08:05:22

【推荐阅读】微服务还能火多久?>>>

欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有:

http://my.oschina.net/MrHou/blog?catalog=3590216&temp=1466310573630

本章,咱们一起动个手,来模仿一下手机百度的新闻流。学习一样东西,最好在有了一定基础之后,最好照着已有的一些产品,简单的实现一下。

1. 手机百度的feed流样式观察

动手之前,我们先来看看手机百度新闻feed流,都由那些元素组成吧,如图1.1:

                                                          图1.1

    我们看到,整体由上方的搜索框+工具条组成,我们暂且叫上方部分为搜索区吧,下方则是由新闻内容区组合而成。下方是由几个工具组成的底部工具栏。在滑动下方新闻feed流过程中,上方的搜索区位置不变。下方的工具栏位置也不变。

 

2. 设计目录结构及代码书写

    好了,我们大致的结构分析完了。要coding?别捉急,我们在coding之前还是要好好设计目录结构的。根据上面分析的信息,我们决定先将整个activity拆分为三大块。

1. 搜索区

2. 中间内容区

3. 底部工具栏区

 

2.1 拆分模块

    所以,我认为,首先要将三个区块拆为三个模块。我们需要一个static文件夹,然后我们在static文件夹下,为每个区域生成一个模块(如图2.1.0):

                图2.1.0

    接下来的样式文件,也是各自放于自己的模块中去维护,包括图片,通过这种方式,组件可以很好的成为一个整体。

    比如,我们的searchArea组件,我们在searchArea中创建一个index.js存放searchArea组件的类与jsx元素。而我们将它的样式放在同目录下的style.js里面,如图2.1.1所示:

                                                                          图2.1.1

好了,组件都建立好了。我们开始书写主入口代码吧。

 

2.2 reactjs基础粗讲(学过react-web的同学,可跳过)

    初学react的同学可能会迷惑,之前不都是写在一个文件里吗,为什么可以拆分成好几个文件,我们在这里简单的先讲解一下react的基础,之后我会在我的reac-native-android系列教学中,详细讲解这一基础知识。

    reactjs中,组件概念是比较强的,举一个简单的例子,我们写一个简单的、分多个快组成的应用的时候,可能会这样写:

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

    render() {
        return <View>
            <View><Text>这是这个应用的第一块</Text></View>
            <View><Text>这是这个应用的第二块</Text></View>
        </View>
    }
}

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

我们看到,这个应用由两个快组成。两个块儿的逻辑如果非常复杂的话,放在一个类里面维护,是个非常痛苦的事情。

    所以react主张,将自己的应用化为多个组建。这样,复用行得到了提高,维护成本也大大降低了。所以,按照标准做法,我们可以将上述两个块儿,拆解成两个组件:

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

    render() {
        return <View>
            <BlockOne />
            <BlockTwo />
            <View><Text>这是这个应用的第二块</Text></View>
        </View>
    }
}

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

    render() {
        return <View><Text>这是这个应用的第一块</Text></View>;
    }
}

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

    render() {
        return <View><Text>这是这个应用的第二块</Text></View>;
    }
}

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

这样达到了代码封装的效果,react支持将两个组建作为基础组建使用,就像上面代码中的BlockOne与BlockTwo,都是可以当做普通组建被插入到组建中去的。

接下来,我们将BlockOne与BlockTwo拆出去,变成两个文件。这样,代码之间就互相隔离了,代码如下:

/**
 * @file index.android.js
 * @author 侯禹
 * @desc 应用的主入口
 */

// 将两个组件以模块的形式导入
let BlockOne = require('blockOne.js');
let BlockTwo = require('blockTwo.js');

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

    render() {
        // 直接使用写好的两个组件,以标签的形式使用即可
        return <View>
            <BlockOne />
            <BlockTwo />
            <View><Text>这是这个应用的第二块</Text></View>
        </View>
    }
}

AppRegistry.registerComponent('hellowReact', () => hellowReact);
/**
 * @file blockOne.js
 * @author 侯禹
 * @desc 第一块儿的组件
 */
class BlockOne extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return <View><Text>这是这个应用的第一块</Text></View>;
    }
}

// 将组件导出
module.exports = BlockOne;
/**
 * @file blockTwo.js
 * @author 侯禹
 * @desc 第二块儿的组件
 */
class BlockTwo extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return <View><Text>这是这个应用的第二块</Text></View>;
    }
}

// 将组件导出
module.exports = BlockTwo;

上面这种形式的代码,在reactjs中是非常常见的。所以各位看官,也尽量要使用这种组件化的方法去写react。接下来,我们的样例代码,也会使用这种组件化的方法去写。

 

2.3 入口代码

    看过我之前教程的同学们可能还记得,react-native工程的入口js文件,安卓版本的是:'index.android.js'。

    我们就先将主入口文件搭建成一个空的架子。    

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

class hellowReact extends Component {
    constructor(props) {
        super(props);
        this.state = {}; 
    }   
    render() {
        return (
            <View style={styles.container}>
            </View>
        );  
    }   
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection: 'column',
        backgroundColor: '#fff',
    },  
});

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

运行上述代码,我们会看到一片空白,这是因为我们只写了一个view,其他的什么都没有写(如图2.3.1):

                                图2.1.1

 

2.4 搜索框部分组件(searchArea)

    我们先写搜索框部分的jsx和样式,如图2.4.1。

                                图2.4.1

我们将两个素材从手机百度的包中拽出来(将手机百度apk的后缀名从.apk改成.zip,然后解压,在其中找找就能找到,或者在本文后面的示例代码中也能找到)。

然后开始书写index.js

我们首先要将必要的基础模块和两个图片引入:

'use strict'
// 必要的基础模块
import React, {Component} from 'react';
import {
    View,
    Text,
    Image
} from 'react-native';

// 所需要的两张资源图片(均从百度的apk中获取)
let homeIcon = require('./icn_feed_go_home.png');
let refreshIcon = require('./icn_feed_refresh.png');

然后我们开始书写自己的react组件,SearchArea,我们先把组件的结果大致写出来,我们会有一个外围包裹的View,然后上面会有一个搜索框,下面有一个工具栏的包裹用的view:

class SearchArea extends Component {

    constructor(props) {
        super(props);
        this.state = {
        };  
    }
    
    render() {
        return (
            <View>
                <View></View>
                <View></View>
            </View>
        );
    }
}

module.exports = SearchArea;

接下来,我们为其添加样式,还是老办法,我们将以前与组建写在一起的style,放在style.js中,再将其导出,并在index.js中,将其引入:

/*
 * @file searchArea/style.js
 * @author houyu
 * @desc 模拟手百的搜索框区域
 */
'use strict'
import React, {Component} from 'react';
import {
    StyleSheet,
    Image
} from 'react-native';

// 创建样式
const headminiStyle = StyleSheet.create({
    topBar: {
        height: 90,
        backgroundColor: '#f8f8f8',
        borderBottomWidth: 1,
        borderColor: '#f2f2f2',
    },
});

// 将样式导出
module.exports.headminiStyle = headminiStyle;

我们在样式中增加一个搜索区域的大致样式,方便我们自己先查看一下(效果如图2.4.2):

// 引入自己模块的样式
import headStyle from './style.js';

class SearchArea extends Component {

    constructor(props) {
        super(props);
        // 将样式放在state中
        this.state = {
            headStyle: headStyle.headminiStyle
        };  
    }
    
    render() {
        //  用state中存下的样式去渲染
        return (
            <View style={this.state.headStyle.topBar}>
                <View></View>
                <View></View>
            </View>
        );
    }
}

module.exports = SearchArea;

                                图2.4.2

接着我们细化一下,将搜索框部分。写出点样式来:

const headminiStyle = StyleSheet.create({
    topBar: {
        height: 90,
        backgroundColor: '#f8f8f8',
        borderBottomWidth: 1,
        borderColor: '#f2f2f2',
    },  
    searchbox: {
        flex: 5,
        marginTop: 7,
        marginLeft: 9,
        marginRight: 9,
        borderColor: '#d0d2d5',
        borderWidth: 1,
    },  
    toolBar: {
        flex: 4,
        marginLeft: 9,
        marginRight: 9,
    },  
});

这里,我们就用到了上节课说到的flex,搜索框与工具栏的高度比例大概是5:4,于是我们写了两个样式的flex为5与4,再将搜索框的边框与背景勾勒出来,就得到了一个大概的搜索框部分(如图2.4.3):

                                图2.4.3

紧接着,我们需要在下面的工具栏里面加上左边的首页按钮(如图2.4.1),和右边的刷新按钮(如图2.4.2),左边是一个图片+一个文字块(首页),右边是一个刷新图片

 图2.4.1


图2.4.2

class SearchArea extends Component {

    constructor(props) {
        super(props);
        this.state = { 
            headStyle: headStyle.headminiStyle
        };  
    }   
                        
    render() {
        return (
            <View style={this.state.headStyle.topBar}>
                <View style={this.state.headStyle.searchbox}></View>
                <View style={this.state.headStyle.toolBar}>
                    <View style={this.state.headStyle.toIndex}>
                        <Image
                            source={homeIcon}
                            style={this.state.headStyle.topBarHomeIcon}
                        />  
                        <Text>首页</Text>
                    </View>
                    <View style={this.state.headStyle.refresh}>
                        <Image
                            source={refreshIcon}
                            style={this.state.headStyle.topBarRefreshIcon}
                        />  
                    </View>
                </View>
            </View>
        );  
    }   
}

我们将元素都写好,加上样式:

const headminiStyle = StyleSheet.create({
    topBar: {
        height: 90,
        backgroundColor: '#f8f8f8',
        borderBottomWidth: 1,
        borderColor: '#f2f2f2',
    },  
    searchbox: {
        // flex的高度占比为搜索框5,工具栏4--5:4
        flex: 5,
        marginTop: 7,
        marginLeft: 9,
        marginRight: 9,
        borderColor: '#d0d2d5',
        borderWidth: 1,
    },  
    toolBar: {
        flex: 4,
        // 让首页按钮与刷新按钮,横向并排
        flexDirection: 'row',
        // 让首页按钮与刷新按钮,上下处于居中对其
        alignItems: 'center',
        // 让首页按钮与刷新按钮左右
        justifyContent: 'space-between',
        marginLeft: 9,
        marginRight: 9,
    },  
    toIndex: {
        // 让首页图片与首页文字并排成一行
        flexDirection: 'row',
        // 让首页图标,与首页文字上下居中
        alignItems: 'center',
    },
    topBarHomeIcon: {
        // 背景图片拉伸
        resizeMode: Image.resizeMode.contain,
        height: 27,
        width: 27,
        marginRight: 5,
    },
    topBarRefreshIcon: {
        // 背景图片拉伸
        resizeMode: Image.resizeMode.contain,
        height: 45,
        width: 45,
    },
});

通过上面的带啊,我们又重温了一些上节的知识点,元素默认是垂直排列的,可是我们需要元素横向排列的话,就得用到flexDirection,设置为row。并且我们需要,"首页按钮"与"刷新按钮",并排显示,且左右分开(如图2.4.3):

                            图2.4.3

于是我们用到了 flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between',这三个属性。于是,左边的首页按钮和右边的刷新按钮横排显示,且左右靠边了,垂直方向上,两侧的按钮也居中对其了。

接下来,我们看一下上部分的整体(如图2.4.4):

                                图2.4.4

接下来,我们先把内容区与底部的工具栏的大致形状给做出来。

2.5 内容区组建与底部栏组建(content与toolBar)

    我们首先将三个组建块儿都引入进来:

// 三个组件块儿一一引入
const SearchArea = require('./static/searchArea');
const Content = require('./static/content');
const ToolBar = require('./static/toolBar');

class hellowReact extends Component {
    constructor(props) {
        super(props);
        this.state = {}; 
    }   
    render() {
        // 三个组建块儿均渲染到主要容器上
        return (
            <View style={styles.container}>
                <SearchArea />
                <Content />
                <ToolBar />
            </View>
        );  
    }   
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection: 'column',
        backgroundColor: '#fff',
    },  
});

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

我们先把中间内容区与底部工具栏的比例按照设计图算出,得到大概这样的比例:

顶部搜索区高度 : 中间内容区高度 : 底部工具栏高度 = 9 : 41 : 5

于是我们将三块儿区域的fex分别写为:9/41/5。

我们把底部工具栏简写一下,目前用图片代替。

'use strict'
import React, {Component} from 'react';
import {
    View,
    Text,
    Image
} from 'react-native';
// 引入底部工具栏的样式
import {ToolBarStyle} from './style.js';
// 底部工具栏暂时不做,使用一张大的图片代替
let bottomBanner = require('./bottom_banner.png');

class ToolBar extends Component {

    constructor(props) {
        super(props);
    }   
        
    render() {
        return (
            <View style={ToolBarStyle.bottomBar}>
                <Image 
                    source={bottomBanner}
                    style={ToolBarStyle.bottomBanner}
                />  
            </View>
        );  
    }   
}
// 导出组件
module.exports = ToolBar;

然后我们写一下没有内容的中间区域,样式也很简单,就是一个白色的块儿:

'use strict'
import React, {Component} from 'react';
import {
    View,
    Text,
    Image,
    ListView
} from 'react-native';
import {contentStyle} from './style.js';

class Content extends Component {

    constructor(props) {
        super(props);
        this.state = {};
    }
   
    render() {
        return (
            <View style={contentStyle.content}>
            </View>
        );
    }
}

module.exports = Content;
'use strict'
import React, {Component} from 'react';
import {
    StyleSheet,
    Image,
} from 'react-native';

const contentStyle = StyleSheet.create({
    content: {
        flex: 41, 
        backgroundColor: '#fff',
    },
});

module.exports.contentStyle = contentStyle;

效果如图2.5.1:

                                图2.5.1

请注意下面的工具栏用的是图片哦。

2.6 新闻列表

    紧接着,因为我们的内容区是新闻列表,所以我们需要一个列表组件。我们还需要去写每一个item的样式。

    react-native中有一个原生组建,叫做listView,顾名思义,这就是个列表组件,下面我们就将会用这个列表组建完成我们的新闻列表。

2.6.1 listView的使用方法

listView需要两个基本的属性一个是dataSoutce,接受列表的数据,另一个是renderRow,指定对于列表中的每一项的渲染方法。

    render() {
        return (
            <View style={contentStyle.content}>
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this.renderOneRow}
                />
            </View>
        );
    }

datasource中传入的数据,需要是DataSource型。所以,我们先建立一个假数据(数据的话,看一下手白大致一个条目上都有哪些信息,抄一下即可):

    constructor(props) {
        super(props);
        var list = [
            {
                title: '英国首相卡梅伦辞职"脱欧派"灵魂人物或接任',
                from: '搜狐新闻',
                time: '3分钟前',
                tag: '视频',
                images: ['http://static.oschina.net/uploads/user/588/1177792_100.jpg?t=1465096324000']
            },
            {
                title: '2.英国首相卡梅伦辞职"脱欧派"灵魂人物或接任',
                from: '搜狐新闻',
                time: '3分钟前',
                tag: '视频',
                images: ['http://static.oschina.net/uploads/user/588/1177792_100.jpg?t=1465096324000']
            },
        ];
        var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
        this.state = {
            dataSource: ds.cloneWithRows(list)
        };
    }

我们还需要建造一个简单的、渲染每一个item的渲染方法--renderOneRow:

renderOneRow(rowData) {
    return <View><Text>一筒最可爱</Text></View>;
}

请注意,这里的rowData就是listView在遍历我们传入的dataSource的时候,给我们的渲染函数传入遍历的当前项数据。

我们可以看到粗略的展示了一个list如图2.6.1

                                图2.6.1

因为我们的假数据写死了两个,所以list中出现了两项。

我们将这两项再变换一下,写成手机百度的样式,我们先来看看手机百度中,每一项大概的层级,应该是左右结构,右边是图片区域,左侧是文字区域。所以,话不多说,先来两个区域再说(如图2.6.2):

                                图2.6.2

renderOneRow(rowData) {
    return <View style={contentStyle.itemContainer}>
        <View style={contentStyle.itemContent}>
            <View style={contentStyle.textArea}>
            </View>
            <View style={contentStyle.imgArea}>
                <Image
                    source={{uri: rowData.images[0]}}
                    style={contentStyle.rightImg}
                />
            </View>
        </View>
    </View>;
}

代码如上,右侧使用一个view包裹的图片,左侧使用一个包裹的view(如图2.6.3所示):

                                图2.6.3

const contentStyle = StyleSheet.create({
    content: {
        flex: 41,
        backgroundColor: '#fff',
    },
    // 每一项的容器的样式,左右留白10dp,高度为100dp
    itemContainer: {
        height: 100,
        paddingHorizontal: 10,
    },
    // 每一项的内部包裹
    itemContent: {
        height: 99,
        // 指定横向排列
        flexDirection: 'row',
        // 内部元素上线居中
        alignItems: 'center',
        // 底部一个灰边
        borderBottomWidth: 1,
        borderBottomColor: '#eee',
    },
    textArea: {
        // 因为不选用成比例,使用flex:1,左侧的文字区域填充满除了图片剩下的那部分区域
        flex: 1,
        height: 70,
        paddingRight: 12,
    },
    imgArea: {
        height: 85,
        width: 110,
    },
    rightImg: {
        height: 85,
        width: 110,
        backgroundColor: '#000',
        // 指定内容图片填充方式
        resizeMode: Image.resizeMode.cover,
    },
});

这样我们就完成了一个基本的item,这个item是左右结构的,左侧的文字区域我们再进一步细化:

左侧文字区域,可以看出是上下结构的(如图2.6.4),高度比例大概是4:1

                                图2.6.4

于是,我们填充了两个块儿,并且左侧内容区域的flexDirection就默认为column就好了。

    renderOneRow(rowData) {
        return <View style={contentStyle.itemContainer}>
            <View style={contentStyle.itemContent}>
                <View style={contentStyle.textArea}>
                    <Text style={contentStyle.textContent}>
                    </Text>
                    <View style={contentStyle.tagArea}>
                    </View>
                </View>
                <View style={contentStyle.imgArea}>
                    <Image
                        source={{uri: rowData.images[0]}}
                        style={contentStyle.rightImg}
                    />
                </View>
            </View>
        </View>;
    }
    tagArea: {
        flex: 1,
        backgroundColor: '#f00',
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
    },
    textContent: {
        flex: 4,
        backgroundColor: '#0f0',
        fontSize: 16,
        fontWeight: 'bold',
        color: '#000',
    },

效果如图2.6.5所示:

                                图2.6.5

good!我们完成了一个上线结构,接着,我们将新闻每一条的标题,填充到上面的绿色框中去(如图2.6.6所示),获取数据,直接从rowData中拿就行,rowData指代的就是上面咱们写的假数据中的每一条:

    renderOneRow(rowData) {
        return <View style={contentStyle.itemContainer}>
            <View style={contentStyle.itemContent}>
                <View style={contentStyle.textArea}>
                    <Text style={contentStyle.textContent}>
                        {rowData.title}
                    </Text>
                    <View style={contentStyle.tagArea}>
                    </View>
                </View>
                <View style={contentStyle.imgArea}>
                    <Image
                        source={{uri: rowData.images[0]}}
                        style={contentStyle.rightImg}
                    />
                </View>
            </View>
        </View>;
    }

                                图2.6.6

接下来的下方tag要注意,是由三个元素组成的左左右,怎么办(如图2.6.7),flex布局中,并没有讲到,如何左左右,只知道如何居中,如何将元素打散到两侧............等等,其实左左右这种结构,不正是左右结构吗?左边是一个由两个元素组成的view,右边是一个由一个元素组成的view,这样一想,我们的思路瞬间清晰了许多,如图2.6.8所示

                   图2.6.7

 

                                图2.6.8

左侧的红色区域可以放入两个tag"视频"和"2分钟前"。如法炮制,我们写两个小的text,即可完成,如图2.6.9所示:

    renderOneRow(rowData) {
        return <View style={contentStyle.itemContainer}>
            <View style={contentStyle.itemContent}>
                <View style={contentStyle.textArea}>
                    <Text style={contentStyle.textContent}>
                        {rowData.title}
                    </Text>
                    <View style={contentStyle.tagArea}>
                        <View style={contentStyle.tags}>
                            <View style={contentStyle.tagContainer}>
                                <Text style={contentStyle.tagEntity}>
                                    {rowData.tag}
                                </Text>
                            </View>
                            <Text style={contentStyle.srcNet}>
                                {rowData.from}
                            </Text>
                        </View>
                        <View style={contentStyle.closeTag}>
                            <Text style={contentStyle.closeTagText}>X</Text>
                        </View>
                    </View>
                </View>
                <View style={contentStyle.imgArea}>
                    <Image
                        source={{uri: rowData.images[0]}}
                        style={contentStyle.rightImg}
                    />
                </View>
            </View>
        </View>;
    }

                                图2.6.9

我们再把假数据多写点,于是一个list就完整的展现了出来:

                                图2.6.10

 

3 课后作业

    又到了课后作业时间,今天的作业是,请完成如图3.1所示形状的item:

聪明的读着,知道该怎么做了吗?

 

本文中提到的例子与素材,均在下面的github上,需要的话请下载:

https://github.com/houyu01/react-native-android-tutorial/tree/master/baiduapp

接下来,我会详细的讲解一下reactjs的基础知识。包括本节中提到的创建组件等,不要走开,请关注我.....

http://my.oschina.net/MrHou/blog/702561

 

原创文章,版权所有,转载请注明出处

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!