[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, width: 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 = { width: 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 = { width: 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 = { width: 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, width: circleSize }; innerCircle = ( <Image source={iconSource} style={[iconStyle, this.props.iconStyle]} /> ); break; case "dot": let dotStyle = { height: circleSize / 2, width: 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: { width: 16, height: 16, borderRadius: 10, position: "absolute", left: -8, alignItems: "center", justifyContent: "center" }, dot: { width: 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
转载请著名出处!谢谢~~