8. react-native-android之----模擬手機百度feed流

歡迎你們收看react-native-android系列教程,跟着本系列教程學習,能夠熟練掌握react-native-android的開發,你值得擁有:javascript

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

本章,我們一塊兒動個手,來模仿一下手機百度的新聞流。學習同樣東西,最好在有了必定基礎以後,最好照着已有的一些產品,簡單的實現一下。react

1. 手機百度的feed流樣式觀察android

動手以前,咱們先來看看手機百度新聞feed流,都由那些元素組成吧,如圖1.1:git

                                                          圖1.1github

    咱們看到,總體由上方的搜索框+工具條組成,咱們暫且叫上方部分爲搜索區吧,下方則是由新聞內容區組合而成。下方是由幾個工具組成的底部工具欄。在滑動下方新聞feed流過程當中,上方的搜索區位置不變。下方的工具欄位置也不變。web

 

2. 設計目錄結構及代碼書寫react-native

    好了,咱們大體的結構分析完了。要coding?別捉急,咱們在coding以前仍是要好好設計目錄結構的。根據上面分析的信息,咱們決定先將整個activity拆分爲三大塊。app

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

 

原創文章,版權全部,轉載請註明出處

相關文章
相關標籤/搜索