1. 程式人生 > >React Native_手把手教你做專案(五.下拉重新整理RefreshControl&封裝自定義Cell)

React Native_手把手教你做專案(五.下拉重新整理RefreshControl&封裝自定義Cell)

接下來我們繼續下拉重新整理的功能,主要是快取資料的拼接與後臺伺服器的配合。把資料最後的id傳給後臺,後臺根據id返回給你新的id之後的資料,因為沒有伺服器,所以這裡的程式碼僅僅做演示使用。

下拉重新整理RefreshControl

list.js

import React, {Component} from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    ListView,
    TouchableOpacity,
    Image,
    ActivityIndicator,
    RefreshControl
} from 'react-native'
; import Icon from 'react-native-vector-icons/Ionicons'; import Dimensions from 'Dimensions'; const {width, height} = Dimensions.get('window'); import config from '../common/config'; import request from '../common/request'; // Mockjs 解析隨機的文字 import Mock from 'mockjs'; let cachedResult = { nexPage: 1
, item: [], total: 0, } export default class list extends Component { constructor(props) { super(props); this.state = { dataSource: new ListView.DataSource({ rowHasChanged: (r1, r2)=>r1 !== r2, }), isLoadingTail: false
, //沒有載入資料 isRefreshing: false } } //即將顯示 componentWillMount() { //載入本地資料 // this.dsfetchData(); } componentDidMount() { //載入網路資料 this._fetchData(1); } _fetchData(page) { if (page !== 0) { this.setState({ isLoadingTail: true }); } else { this.setState({ isRefreshing: true }); } this.setState({ isLoadingTail: true }); //傳送網路請求 request.get(config.api.base + config.api.list, { accessToken: '001', a: 'list', c: 'data', type: 29, page: page }).then( (data) => { //將伺服器得到的資料快取進來 let items = cachedResult.item.slice(); // let items = cachedResult.item.concat(data.list);//把快取的資料進行拼接 if (page !== 0) {//載入更多 items = items.concat(data.list); cachedResult.nexPage += 1; } else {//重新整理資料 items = data.list.concat(items); } //最後儲存資料 cachedResult.item = items; cachedResult.total = items.total; // console.log(items); // console.log('總資料是:' + cachedResult.total); // console.log('當前到了第:' + cachedResult.item.length + '個!'); if (page !== 0) {//載入更多 this.setState({ dataSource: this.state.dataSource.cloneWithRows( cachedResult.item ), isLoadingTail: false }); }else { this.setState({ dataSource: this.state.dataSource.cloneWithRows( cachedResult.item ), isRefreshing: false }); } } ).catch(//如果有錯 (error) => { if(page !=0){ this.setState({ isLoadingTail:false }) }else { this.setState({ isRefreshing:false }) } console.log('err' + error); } ) } // dsfetchData() { // let result = Mock.mock({ // "data|20": [ // { // "_id": "@ID", // "thumb": "@IMG(1024x700,@COLOR(),\'\u56fe\u7247\u4e0a\u7684\u6587\u5b57\')", // "title": "@cparagraph(1, 3)", // "video": "\'http:\/\/v.youku.com\/v_show\/id_XMzY5ODY5MDI3Ng==.html?spm=a2h1n.8251846.0.0\'" // } // ], // "success": true // }) // this.setState({ // dataSource: this.state.dataSource.cloneWithRows(result.data) // }) // } render() { return ( <View style={styles.container}> {/*導航條*/} <View style={styles.header}> <Text style={styles.headerText}> 視訊列表 </Text> </View> {/*列表頁面*/} <ListView dataSource={this.state.dataSource} renderRow={this._renderRow} style={styles.listView} onEndReached={this._fetchMoreData}//滾到底部載入更多資料 onEndReachedThreshold={20} //距離底部還有多遠觸發 renderFooter={this._renderFooter} refreshControl={ <RefreshControl refreshing={this.state.isRefreshing} onRefresh={this._onRefresh} /> } /> </View> ); } _onRefresh = ()=> { if (!this._hasMore() || this.state.isRefreshing) { return } this._fetchData(0); } //自定義Footer檢視 _renderFooter = ()=> { if (!this._hasMore() && cachedResult.total !== 0) { return ( <View style={styles.loadingMore}> <Text style={styles.loadingText}>沒有更多資料啦...</Text> </View> ) } // if(!this.state.isLoadingTail){ // return(<View></View>) // } //顯示一朵小菊花 return ( <ActivityIndicator style={styles.loadingMore}></ActivityIndicator> ) } //思路:有多種解決方案 //1.傳送請求 2.儲存請求引數 3.對比引數 //重新整理請求 2.儲存request = refresh 3.對比 refresh==儲存request 狀態機中的page值 //狀態機 loading = refresh //上拉載入更多資料 _fetchMoreData = ()=> { //判斷是否有更多資料或者是否在請求中 if (!this._hasMore || this.isLoadingTail) { return } let page = cachedResult.nexPage; //去伺服器去載入更多資料 this._fetchData(page); } //判斷是否還有更多資料 _hasMore() { return cachedResult.item.length !== cachedResult.total; } //下劃線代表內部類自己用的函式,屬於規範 _renderRow = (rowData)=> { return ( <TouchableOpacity> {/*整個Cell*/} <View style={styles.cellStyle}> {/*標題文字*/} <Text style={styles.title}>{rowData.text}</Text> <Image style={styles.thumb} source={{uri: rowData.profile_image}}> </Image> <Icon name="ios-play" size={30} style={styles.play} /> {/*點贊&評論*/} <View style={styles.cellFooter}> {/*點贊*/} <View style={styles.footerBox}> <Icon name="ios-heart-outline" size={30} style={styles.boxIcon} /> {/*點贊文字*/} <Text style={styles.boxText}>點贊</Text> </View> {/*評論*/} <View style={styles.footerBox}> <Icon name="ios-chatbubbles-outline" size={30} style={styles.boxIcon} /> {/*點贊文字*/} <Text style={styles.boxText}>評論</Text> </View> </View> </View> </TouchableOpacity> ); } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5FCFF', }, header: { // marginTop:Platform.OS == 'ios'?20:0, paddingTop: 25, paddingBottom: 15, backgroundColor: '#dddddd', borderBottomWidth: 0.5, borderBottomColor: 'black', }, headerText: { fontWeight: '600', textAlign: 'center', fontSize: 16, }, listView: {}, cellStyle: { width: width, marginTop: 10, backgroundColor: 'white', }, title: { padding: 10, color: 'black', fontSize: 18 }, thumb: { width: width, height: width * 0.56, resizeMode: 'cover' }, play: { position: 'absolute', bottom: 100, right: 14, width: 46, height: 46, paddingTop: 8, paddingLeft: 18, backgroundColor: 'transparent', borderColor: 'black', borderWidth: 0.5, borderRadius: 23, }, cellFooter: { flexDirection: 'row', justifyContent: 'space-between', backgroundColor: '#dddddd', }, footerBox: { padding: 10, flexDirection: 'row', backgroundColor: 'white', flex: 1, marginLeft: 1, justifyContent: 'center', }, boxIcon: { fontSize: 22, color: '#333', }, boxText: { fontSize: 18, color: '#333', paddingLeft: 12, marginTop: 2 }, loadingMore: { marginVertical: 20 }, loadingText: { fontSize: 18, color: '#777', textAlign: 'center' } });

演示示例:

這裡寫圖片描述

封裝自定義Cell

從上面的程式碼中,我們可以看出,僅僅這麼low的一個頁面,包括css樣式,js邏輯程式碼和render html介面都在list.js檔案中,程式碼300多行,顯得十分混亂,那麼我們能不能把cell進行抽取呢?

List檔案下建立Item.js檔案

Item.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

import Dimensions from 'Dimensions';
const {width, height} = Dimensions.get('window');
import Icon from 'react-native-vector-icons/Ionicons';

export default class item extends Component {

    constructor(props) {
        super(props);
        this.state = {
            rowData: this.props.rowData,
        }
    }

    render() {
        let rowData = this.state.rowData;

        return (
            <TouchableOpacity>

                {/*整個Cell*/}
                <View style={styles.cellStyle}>
                    {/*標題文字*/}
                    <Text style={styles.title}>{rowData.text}</Text>


                    <Image style={styles.thumb} source={{uri: rowData.profile_image}}>

                    </Image>
                    <Icon name="ios-play"
                          size={30}
                          style={styles.play}
                    />
                    {/*點贊&評論*/}
                    <View style={styles.cellFooter}>
                        {/*點贊*/}
                        <View style={styles.footerBox}>
                            <Icon name="ios-heart-outline"
                                  size={30}
                                  style={styles.boxIcon}
                            />
                            {/*點贊文字*/}
                            <Text style={styles.boxText}>點贊</Text>
                        </View>

                        {/*評論*/}
                        <View style={styles.footerBox}>
                            <Icon name="ios-chatbubbles-outline"
                                  size={30}
                                  style={styles.boxIcon}
                            />
                            {/*點贊文字*/}
                            <Text style={styles.boxText}>評論</Text>
                        </View>
                    </View>
                </View>
            </TouchableOpacity>
        );
    }
}

const styles = StyleSheet.create({
    cellStyle: {
        width: width,
        marginTop: 10,
        backgroundColor: 'white',

    },
    title: {
        padding: 10,
        color: 'black',
        fontSize: 18
    },
    thumb: {
        width: width,
        height: width * 0.56,
        resizeMode: 'cover'
    },
    play: {
        position: 'absolute',
        bottom: 100,
        right: 14,
        width: 46,
        height: 46,
        paddingTop: 8,
        paddingLeft: 18,
        backgroundColor: 'transparent',
        borderColor: 'black',
        borderWidth: 0.5,
        borderRadius: 23,
    },
    cellFooter: {
        flexDirection: 'row',
        justifyContent: 'space-between',
        backgroundColor: '#dddddd',
    },
    footerBox: {
        padding: 10,
        flexDirection: 'row',
        backgroundColor: 'white',
        flex: 1,
        marginLeft: 1,
        justifyContent: 'center',
    },
    boxIcon: {
        fontSize: 22,
        color: '#333',
    },
    boxText: {
        fontSize: 18,
        color: '#333',
        paddingLeft: 12,
        marginTop: 2
    }
});

list.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

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

import Icon from 'react-native-vector-icons/Ionicons';
import Dimensions from 'Dimensions';

const {width, height} = Dimensions.get('window');

import config from '../common/config';
import request from '../common/request';

import Item from './Item';

// Mockjs 解析隨機的文字
import  Mock from 'mockjs';

let cachedResult = {
    nexPage: 1,
    item: [],
    total: 0,
}

export default class list extends Component {
    constructor(props) {
        super(props);
        this.state = {
            dataSource: new ListView.DataSource({
                rowHasChanged: (r1, r2)=>r1 !== r2,
            }),
            isLoadingTail: false,  //沒有載入資料
            isRefreshing: false
        }
    }

    //即將顯示
    componentWillMount() {
        //載入本地資料
        // this.dsfetchData();
    }

    componentDidMount() {
        //載入網路資料
        this._fetchData(1);
    }

    _fetchData(page) {

        if (page !== 0) {
            this.setState({
                isLoadingTail: true
            });
        } else {
            this.setState({
                isRefreshing: true
            });
        }

        this.setState({
            isLoadingTail: true
        });

        //傳送網路請求
        request.get(config.api.base + config.api.list, {
            accessToken: '001',
            a: 'list',
            c: 'data',
            type: 29,
            page: page
        }).then(
            (data) => {
                //將伺服器得到的資料快取進來
                let items = cachedResult.item.slice();
                // let items = cachedResult.item.concat(data.list);//把快取的資料進行拼接
                if (page !== 0) {//載入更多
                    items = items.concat(data.list);
                    cachedResult.nexPage += 1;
                } else {//重新整理資料
                    items = data.list.concat(items);
                }
                //最後儲存資料
                cachedResult.item = items;
                cachedResult.total = items.total;
                // console.log(items);
                // console.log('總資料是:' + cachedResult.total);
                // console.log('當前到了第:' + cachedResult.item.length + '個!');

                if (page !== 0) {//載入更多
                    this.setState({
                        dataSource: this.state.dataSource.cloneWithRows(
                            cachedResult.item
                        ),
                        isLoadingTail: false
                    });
                }else {
                    this.setState({
                        dataSource: this.state.dataSource.cloneWithRows(
                            cachedResult.item
                        ),
                        isRefreshing: false
                    });
                }

            }
        ).catch(//如果有錯
            (error) => {
                if(page !=0){
                    this.setState({
                        isLoadingTail:false
                    })
                }else {
                    this.setState({
                        isRefreshing:false
                    })
                }
                console.log('err' + error);
            }
        )
    }

    render() {
        return (
            <View style={styles.container}>
                {/*導航條*/}
                <View style={styles.header}>
                    <Text style={styles.headerText}>
                        視訊列表
                    </Text>
                </View>
                {/*列表頁面*/}
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow}
                    style={styles.listView}
                    onEndReached={this._fetchMoreData}//滾到底部載入更多資料
                    onEndReachedThreshold={20} //距離底部還有多遠觸發
                    renderFooter={this._renderFooter}

                    refreshControl={
                        <RefreshControl
                            refreshing={this.state.isRefreshing}
                            onRefresh={this._onRefresh}
                        />
                    }
                />
            </View>
        );
    }

    //下拉重新整理
    _onRefresh = ()=> {
        if (!this._hasMore() || this.state.isRefreshing) {
            return
        }

        this._fetchData(0);
    }
    //自定義Footer檢視
    _renderFooter = ()=> {
        if (!this._hasMore() && cachedResult.total !== 0) {
            return (
                <View style={styles.loadingMore}>
                    <Text style={styles.loadingText}>沒有更多資料啦...</Text>
                </View>
            )
        }
        // if(!this.state.isLoadingTail){
        //     return(<View></View>)
        // }
        //顯示一朵小菊花
        return (
            <ActivityIndicator style={styles.loadingMore}></ActivityIndicator>
        )
    }


    //思路:有多種解決方案

    //1.傳送請求 2.儲存請求引數 3.對比引數

    //重新整理請求 2.儲存request = refresh 3.對比 refresh==儲存request 狀態機中的page值
    //狀態機 loading = refresh
    //上拉載入更多資料
    _fetchMoreData = ()=> {
        //判斷是否有更多資料或者是否在請求中
        if (!this._hasMore || this.isLoadingTail) {
            return
        }

        let page = cachedResult.nexPage;
        //去伺服器去載入更多資料
        this._fetchData(page);
    }

    //判斷是否還有更多資料
    _hasMore() {
        return cachedResult.item.length !== cachedResult.total;
    }

    //返回item
    //下劃線代表內部類自己用的函式,屬於規範
    _renderRow = (rowData)=> {
        return (
           <Item rowData={rowData} />
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#F5FCFF',
    },
    header: {
        // marginTop:Platform.OS == 'ios'?20:0,
        paddingTop: 25,
        paddingBottom: 15,
        backgroundColor: '#dddddd',
        borderBottomWidth: 0.5,
        borderBottomColor: 'black',
    },
    headerText: {
        fontWeight: '600',
        textAlign: 'center',
        fontSize: 16,
    },
    listView: {

    },
    loadingMore: {
        marginVertical: 20
    },
    loadingText: {
        fontSize: 18,
        color: '#777',
        textAlign: 'center'
    }
});

程式碼抽取很簡單,可以詳細看看抽取了哪些程式碼。