[RN] React Native 好用的时间线 组件

 React Native 好用的时间线 组件

效果如下:

 

实现方法:

一、组件封装

CustomTimeLine.js

"use strict";

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

const ds = new ListView.DataSource({
    rowHasChanged: (r1, r2) => r1 !== r2,
    sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});

const defaultCircleSize = 16;
const defaultCircleColor = "#007AFF";
const defaultLineWidth = 2;
const defaultLineColor = "#007AFF";
const defaultTimeTextColor = "black";
const defaultDotColor = "white";
const defaultInnerCircle = "none";

export default class CustomTimeLine extends Component {
    static defaultProps = {
        circleSize: defaultCircleSize,
        circleColor: defaultCircleColor,
        lineWidth: defaultLineWidth,
        lineColor: defaultLineColor,
        innerCircle: defaultInnerCircle,
        columnFormat: "single-column-left",
        separator: false,
        showTime: true
    };

    constructor(props, context) {
        super(props, context);

        this._renderRow = this._renderRow.bind(this);
        this.renderTime = (this.props.renderTime
                ? this.props.renderTime
                : this._renderTime
        ).bind(this);
        this.renderDetail = (this.props.renderDetail
                ? this.props.renderDetail
                : this._renderDetail
        ).bind(this);
        this.renderCircle = (this.props.renderCircle
                ? this.props.renderCircle
                : this._renderCircle
        ).bind(this);
        this.renderEvent = this._renderEvent.bind(this);

        this.state = {
            data: this.props.data,
            dataSource: ds.cloneWithRows(this.props.data),
            x: 0,
             0
        };
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            data: nextProps.data,
            dataSource: ds.cloneWithRows(nextProps.data)
        });
    }

    render() {
        return (
            <View style={[styles.container, this.props.style]}>
                <ListView
                    ref="listView"
                    style={[styles.listview, this.props.listViewStyle]}
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow}
                    automaticallyAdjustContentInsets={false}
                    {...this.props.options}
                />
            </View>
        );
    }

    _renderRow(rowData, sectionID, rowID) {
        let content = null;

        switch (this.props.columnFormat) {
            case "single-column-left":
                content = (
                    <View style={[styles.rowContainer, this.props.rowContainerStyle]}>
                        {this.renderTime(rowData, sectionID, rowID)}
                        {this.renderEvent(rowData, sectionID, rowID)}
                        {this.renderCircle(rowData, sectionID, rowID)}
                    </View>
                );
                break;
            case "single-column-right":
                content = (
                    <View style={[styles.rowContainer, this.props.rowContainerStyle]}>
                        {this.renderEvent(rowData, sectionID, rowID)}
                        {this.renderTime(rowData, sectionID, rowID)}
                        {this.renderCircle(rowData, sectionID, rowID)}
                    </View>
                );
                break;
            case "two-column":
                content =
                    rowID % 2 === 0 ? (
                        <View style={[styles.rowContainer, this.props.rowContainerStyle]}>
                            {this.renderTime(rowData, sectionID, rowID)}
                            {this.renderEvent(rowData, sectionID, rowID)}
                            {this.renderCircle(rowData, sectionID, rowID)}
                        </View>
                    ) : (
                        <View style={[styles.rowContainer, this.props.rowContainerStyle]}>
                            {this.renderEvent(rowData, sectionID, rowID)}
                            {this.renderTime(rowData, sectionID, rowID)}
                            {this.renderCircle(rowData, sectionID, rowID)}
                        </View>
                    );
                break;
        }
        return <View key={rowID}>{content}</View>;
    }

    _renderTime(rowData, sectionID, rowID) {
        if (!this.props.showTime) {
            return null;
        }
        var timeWrapper = null;
        switch (this.props.columnFormat) {
            case "single-column-left":
                timeWrapper = {
                    alignItems: "flex-end"
                };
                break;
            case "single-column-right":
                timeWrapper = {
                    alignItems: "flex-start"
                };
                break;
            case "two-column":
                timeWrapper = {
                    flex: 1,
                    alignItems: rowID % 2 === 0 ? "flex-end" : "flex-start"
                };
                break;
        }
        return (
            <View style={timeWrapper}>
                <View style={[styles.timeContainer, this.props.timeContainerStyle]}>
                    <Text style={[styles.time, this.props.timeStyle]}>
                        {rowData.time}
                    </Text>
                </View>
            </View>
        );
    }

    _renderEvent(rowData, sectionID, rowID) {
        const lineWidth = rowData.lineWidth
            ? rowData.lineWidth
            : this.props.lineWidth;
        const isLast = this.props.renderFullLine
            ? !this.props.renderFullLine
            : this.state.data.slice(-1)[0] === rowData;
        const lineColor = isLast
            ? "rgba(0,0,0,0)"
            : rowData.lineColor ? rowData.lineColor : this.props.lineColor;
        let opStyle = null;

        switch (this.props.columnFormat) {
            case "single-column-left":
                opStyle = {
                    borderColor: lineColor,
                    borderLeftWidth: lineWidth,
                    borderRightWidth: 0,
                    marginLeft: 20,
                    paddingLeft: 20
                };
                break;
            case "single-column-right":
                opStyle = {
                    borderColor: lineColor,
                    borderLeftWidth: 0,
                    borderRightWidth: lineWidth,
                    marginRight: 20,
                    paddingRight: 20
                };
                break;
            case "two-column":
                opStyle =
                    rowID % 2 == 0
                        ? {
                            borderColor: lineColor,
                            borderLeftWidth: lineWidth,
                            borderRightWidth: 0,
                            marginLeft: 20,
                            paddingLeft: 20
                        }
                        : {
                            borderColor: lineColor,
                            borderLeftWidth: 0,
                            borderRightWidth: lineWidth,
                            marginRight: 20,
                            paddingRight: 20
                        };
                break;
        }

        return (
            <View
                style={[styles.details, opStyle]}
                onLayout={evt => {
                    if (!this.state.x && !this.state.width) {
                        const {x, width} = evt.nativeEvent.layout;
                        this.setState({x, width});
                    }
                }}
            >
                <TouchableOpacity
                    disabled={this.props.onEventPress == null}
                    style={[this.props.detailContainerStyle]}
                    onPress={() =>
                        this.props.onEventPress ? this.props.onEventPress(rowData) : null
                    }
                >
                    <View style={styles.detail}>
                        {this.renderDetail(rowData, sectionID, rowID)}
                    </View>
                    {this._renderSeparator()}
                </TouchableOpacity>
            </View>
        );
    }

    _renderDetail(rowData, sectionID, rowID) {
        let title = rowData.description ? (
            <View>
                <Text style={[styles.title, this.props.titleStyle]}>
                    {rowData.title}
                </Text>
                <Text style={[styles.description, this.props.descriptionStyle]}>
                    {rowData.description}
                </Text>
            </View>
        ) : (
            <Text style={[styles.title, this.props.titleStyle]}>{rowData.title}</Text>
        );
        return <View style={styles.container}>{title}</View>;
    }

    _renderCircle(rowData, sectionID, rowID) {
        var circleSize = rowData.circleSize
            ? rowData.circleSize
            : this.props.circleSize ? this.props.circleSize : defaultCircleSize;
        var circleColor = rowData.circleColor
            ? rowData.circleColor
            : this.props.circleColor ? this.props.circleColor : defaultCircleColor;
        var lineWidth = rowData.lineWidth
            ? rowData.lineWidth
            : this.props.lineWidth ? this.props.lineWidth : defaultLineWidth;

        var circleStyle = null;

        switch (this.props.columnFormat) {
            case "single-column-left":
                circleStyle = {
                     this.state.x ? circleSize : 0,
                    height: this.state.x ? circleSize : 0,
                    borderRadius: circleSize / 2,
                    backgroundColor: circleColor,
                    left: this.state.x - circleSize / 2 + (lineWidth - 1) / 2
                };
                break;
            case "single-column-right":
                circleStyle = {
                     this.state.width ? circleSize : 0,
                    height: this.state.width ? circleSize : 0,
                    borderRadius: circleSize / 2,
                    backgroundColor: circleColor,
                    left: this.state.width - circleSize / 2 - (lineWidth - 1) / 2
                };
                break;
            case "two-column":
                circleStyle = {
                     this.state.width ? circleSize : 0,
                    height: this.state.width ? circleSize : 0,
                    borderRadius: circleSize / 2,
                    backgroundColor: circleColor,
                    left: this.state.width - circleSize / 2 - (lineWidth - 1) / 2
                };
                break;
        }

        var innerCircle = null;
        switch (this.props.innerCircle) {
            case "icon":
                let iconSource = rowData.icon ? rowData.icon : this.props.icon;
                let iconStyle = {
                    height: circleSize,
                     circleSize
                };
                innerCircle = (
                    <Image
                        source={iconSource}
                        style={[iconStyle, this.props.iconStyle]}
                    />
                );
                break;
            case "dot":
                let dotStyle = {
                    height: circleSize / 2,
                     circleSize / 2,
                    borderRadius: circleSize / 4,
                    backgroundColor: rowData.dotColor
                        ? rowData.dotColor
                        : this.props.dotColor ? this.props.dotColor : defaultDotColor
                };
                innerCircle = <View style={[styles.dot, dotStyle]}/>;
                break;
        }
        return (
            <View style={[styles.circle, circleStyle, this.props.circleStyle]}>
                {innerCircle}
            </View>
        );
    }

    _renderSeparator() {
        if (!this.props.separator) {
            return null;
        }
        return <View style={[styles.separator, this.props.separatorStyle]}/>;
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1
    },
    listview: {
        flex: 1
    },
    sectionHeader: {
        marginBottom: 15,
        backgroundColor: "#007AFF",
        height: 30,
        justifyContent: "center"
    },
    sectionHeaderText: {
        color: "#FFF",
        fontSize: 18,
        alignSelf: "center"
    },
    rowContainer: {
        flexDirection: "row",
        flex: 1,
        //alignItems: 'stretch',
        justifyContent: "center"
    },
    timeContainer: {
        minWidth: 45
    },
    time: {
        textAlign: "right",
        color: defaultTimeTextColor
    },
    circle: {
         16,
        height: 16,
        borderRadius: 10,
        position: "absolute",
        left: -8,
        alignItems: "center",
        justifyContent: "center"
    },
    dot: {
         8,
        height: 8,
        borderRadius: 4,
        backgroundColor: defaultDotColor
    },
    title: {
        fontSize: 16,
        fontWeight: "bold"
    },
    details: {
        borderLeftWidth: defaultLineWidth,
        flexDirection: "column",
        flex: 1
    },
    detail: {paddingTop: 10, paddingBottom: 10},
    description: {
        marginTop: 10
    },
    separator: {
        height: 1,
        backgroundColor: "#aaa",
        marginTop: 10,
        marginBottom: 10
    }
});

代码调用:

import React, {Component} from 'react';
import {StyleSheet, View} from 'react-native';
import Timeline from './CustomTimeLine';

export default class TestTimeLine extends Component {
    constructor() {
        super()
        this.data = [
            {time: '09:00', title: '商家已接单', description: 'Event 1 Description'},
            {time: '10:45', title: '正在沟通', description: 'Event 2 Description'},
            {time: '12:00', title: '合同签订', description: 'Event 3 Description'},
            {time: '14:00', title: '订单完成', description: 'Event 4 Description'},
            {time: '16:30', title: '用户评价', description: 'Event 5 Description'}
        ]
    }

    render() {
        return (
            <View style={styles.container}>
                <Timeline
                    style={styles.list}
                    data={this.data}
                    showTime={true}
                />
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        padding: 20,
        paddingTop: 65,
        backgroundColor: 'white'
    },
    list: {
        flex: 1,
        marginTop: 20,
    },
});

更多使用方法,参考:

https://github.com/thegamenicorus/react-native-timeline-listview

本博客地址: wukong1688

本文原文地址:https://www.cnblogs.com/wukong1688/p/11049014.html

转载请著名出处!谢谢~~

原文地址:https://www.cnblogs.com/wukong1688/p/11049014.html