300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > React-Native ScrollView自定义横向滑动进度条

React-Native ScrollView自定义横向滑动进度条

时间:2019-02-05 16:31:39

相关推荐

React-Native ScrollView自定义横向滑动进度条

React-Native ScrollView自定义横向滑动进度条

概要需求自定义滑动进度条确定参数计算参数滑动进度条的实现 首页定制菜单确定参数渲染方式遍历输出 效果图源码IndicatorScrollView.jsScroll.js

概要

本篇文章概述了通过React-Native实现一个允许自定义横向滑动进度条的ScrollView组件。

需求

开发一个首页摆放菜单入口的ScrollView可滑动组件(类似某淘首页上的菜单效果),允许自定义横向滑动进度条,且内部渲染的菜单内容支持自定义展示的行数和列数,在内容超出屏幕后,渲染顺序为纵向由上至下依次排列

Animated 动画 点此进入学习ScrollView 滑动组件 点此进入学习

自定义滑动进度条

确定参数

首先,让我们确定一下自定义滑动进度条需要哪些参数来支持:

初始位置时,确定显示进度的条的宽度(barWidth)滑动进度,以此来确定上面这个条的位置现在应该到哪里了(marLeftAnimated)

计算参数

1.想要确定显示进度的条的宽度(barWidth),那么必须先知道三个值:

ScrollView总宽度(containerStyle传入)进度条背景的宽度(indicatorBgStyle传入)ScrollView内部内容总宽度(childWidth,通过onContentSizeChange方法测量)

然后我们就可以进行如下计算,这样得到的_barWidth就是显示进度的条的宽度(barWidth):

let _barWidth = (this.props.indicatorBgStyle.width * this.props.containerStyle.width) / this.state.childWidth;

2.想要确定显示进度的条的位置(marLeftAnimated),那么必须先知道两个值:

ScrollView可滑动距离(scrollDistance)进度部分可滑动距离(leftDistance)

然后我们就可以进行如下定义,这样得到的marLeftAnimated,输出值即为进度条的距左距离:

let scrollDistance = this.state.childWidth - this.props.containerStyle.width...//显示滑动进度部分的距左距离let leftDistance = this.props.indicatorBgStyle.width - _barWidth;const scrollOffset = this.state.scrollOffsetthis.marLeftAnimated = scrollOffset.interpolate({inputRange: [0, scrollDistance], //输入值区间为内容可滑动距离outputRange: [0, leftDistance], //映射输出区间为进度部分可改变距离extrapolate: 'clamp', //钳制输出值useNativeDriver: true,})

滑动进度条的实现

通过Animated.View,定义绝对位置,将两个条在Z轴上下重叠一起。

<View style={[{alignSelf:'center'},this.props.indicatorBgStyle]}><Animated.Viewstyle={[this.props.indicatorStyle,{position: 'absolute',width: this.state.barWidth,top: 0,left: this.marLeftAnimated,}]}/></View>

之后就通过onSroll事件获取滑动偏移量,然后通过偏移量改变动画的值,这里我就不多说了,不明白的可以看我上一篇文章。

首页定制菜单

确定参数

首先,让我们确定一下实现首页定制菜单需要哪些参数来支持:

列数量(columnLimit)行数量(rowLimit)

渲染方式

根据行列数量,决定每屏的菜单总数。根据行数量,决定渲染结果数组里有几组,一行就是一组。

let optionTotalArr = []; //存放所有option样式的数组//根据行数,声明用于存放每一行渲染内容的数组for( let i = 0; i < rowLimit; i++ ) optionTotalArr.push([])

1.没超出屏幕时,确定渲染行的方式如下:

if(index < columnLimit * rowLimit){//没超出一屏数量时,根据列数更新行标识rowIndex = parseInt(index / columnLimit)}

2.超出屏幕时,确定渲染行的方式如下:

//当超出一屏数量时,根据行数更新行标识rowIndex = index % rowLimit;

遍历输出

根据行数,遍历存放计算后的行内容数组。

optionTotalArr[rowIndex].push(<TouchableOpacity key={index} activeOpacity={0.7}style={[styles.list_item,{width:size}]} onPress={()=>alert(item.name)}><View style={{width:size-20,backgroundColor:'#FFCC00',alignItems:'center',justifyContent:'center'}}><Text style={{fontSize:18, color:'#333',marginVertical:20}}>{item.name}</Text></View></TouchableOpacity>)

效果图

源码

IndicatorScrollView.js

import React, {PureComponent } from 'react';import {StyleSheet,View,ScrollView,Animated,Dimensions,} from 'react-native';import PropTypes from 'prop-types';const {width, height } = Dimensions.get('window');export default class IndicatorScrollView extends PureComponent {static propTypes = {//最外层样式(包含ScrollView及滑动进度条的全部区域containerStyle: PropTypes.oneOfType([ PropTypes.object,PropTypes.array,]),//ScrollView的样式style: PropTypes.oneOfType([PropTypes.object,PropTypes.array,]),//滑动进度条底部样式indicatorBgStyle: PropTypes.oneOfType([PropTypes.object,PropTypes.array,]),//滑动进度条样式indicatorStyle: PropTypes.oneOfType([PropTypes.object,PropTypes.array,]),}static defaultProps = {containerStyle: {width: width },style: {},indicatorBgStyle:{width: 200,height: 20, backgroundColor: '#ddd'},indicatorStyle:{height:20,backgroundColor:'#000'},}constructor(props) {super(props);this.state = {//滑动偏移量scrollOffset: new Animated.Value(0),//ScrollView子布局宽度childWidth: this.props.containerStyle.width,//显示滑动进度部分条的长度barWidth: props.indicatorBgStyle.width / 2,};}UNSAFE_componentWillMount() {this.animatedEvent = Animated.event([{nativeEvent: {contentOffset: {x: this.state.scrollOffset }}}])}componentDidUpdate(prevProps, prevState) {//内容可滑动距离let scrollDistance = this.state.childWidth - this.props.containerStyle.widthif( scrollDistance > 0 && prevState.childWidth != this.state.childWidth){let _barWidth = (this.props.indicatorBgStyle.width * this.props.containerStyle.width) / this.state.childWidth;this.setState({barWidth: _barWidth,})//显示滑动进度部分的距左距离let leftDistance = this.props.indicatorBgStyle.width - _barWidth;const scrollOffset = this.state.scrollOffsetthis.marLeftAnimated = scrollOffset.interpolate({inputRange: [0, scrollDistance], //输入值区间为内容可滑动距离outputRange: [0, leftDistance], //映射输出区间为进度部分可改变距离extrapolate: 'clamp', //钳制输出值useNativeDriver: true,})}}render() {return (<View style={[styles.container,this.props.containerStyle]}><ScrollViewstyle={this.props.style}horizontal={true} //横向alwaysBounceVertical={false}alwaysBounceHorizontal={false}showsHorizontalScrollIndicator={false} //自定义滑动进度条,所以这里设置不显示scrollEventThrottle={0.1} //滑动监听调用频率onScroll={this.animatedEvent} //滑动监听事件,用来映射动画值scrollEnabled={this.state.childWidth - this.props.containerStyle.width>0 ? true : false }onContentSizeChange={(width,height)=>{if(this.state.childWidth != width){this.setState({childWidth: width })}}}>{this.props.children??<View style={{flexDirection: 'row', height: 200 }}><View style={{width: 300, backgroundColor: 'red' }} /><View style={{width: 300, backgroundColor: 'yellow' }} /><View style={{width: 300, backgroundColor: 'blue' }} /></View>}</ScrollView>{this.state.childWidth - this.props.containerStyle.width>0?<View style={[{alignSelf:'center'},this.props.indicatorBgStyle]}><Animated.Viewstyle={[this.props.indicatorStyle,{position: 'absolute',width: this.state.barWidth,top: 0,left: this.marLeftAnimated,}]}/></View>:null}</View>);};}const styles = StyleSheet.create({container: {flex: 1,},});

Scroll.js

import React, {Component } from 'react';import {StyleSheet, View,Dimensions,TouchableOpacity,Text,} from 'react-native';import IndicatorScrollView from '../../component/scroll/IndicatorScrollView';const {width, height } = Dimensions.get('window');const columnLimit = 4; //option列数量const rowLimit = 2; //option行数量// 编写UI组件export default class Scroll extends Component {constructor(props) {super(props);this.state = {};this.itemArr = [{name: '1'},{name: '2'},{name: '3'},{name: '4'},{name: '5'},{name: '6'},{name: '7'},{name: '8'},{name: '9'},{name: '10'},{name: '11'},{name: '12'}]}renderOption(){let size = (width-20)/columnLimit; //每个option的宽度let optionTotalArr = []; //存放所有option样式的数组//根据行数,声明用于存放每一行渲染内容的数组for( let i = 0; i < rowLimit; i++ ) optionTotalArr.push([])this.itemArr.map((item,index) => {let rowIndex = 0; //行标识if(index < columnLimit * rowLimit){//没超出一屏数量时,根据列数更新行标识rowIndex = parseInt(index / columnLimit)}else{//当超出一屏数量时,根据行数更新行标识rowIndex = index % rowLimit;}optionTotalArr[rowIndex].push(<TouchableOpacity key={index} activeOpacity={0.7}style={[styles.list_item,{width:size}]} onPress={()=>alert(item.name)}><View style={{width:size-20,backgroundColor:'#FFCC00',alignItems:'center',justifyContent:'center'}}><Text style={{fontSize:18, color:'#333',marginVertical:20}}>{item.name}</Text></View></TouchableOpacity>)})return(<Viewstyle={{flex:1,justifyContent:'center',paddingHorizontal:10}}>{optionTotalArr.map((item,index)=>{return <View key={index} style={{flexDirection:'row'}}>{item}</View>})}</View>)}render() {return (<View style={styles.container}><View style={{flex:1}}/><IndicatorScrollView containerStyle={styles.list_style}indicatorBgStyle={{marginBottom:10,borderRadius:2,width:40,height:4,backgroundColor:'#BFBFBF'}}indicatorStyle={{borderRadius:2,height:4,backgroundColor:'#CC0000'}}>{this.renderOption()}</IndicatorScrollView><View style={{flex:1}}/></View >);};}const styles = StyleSheet.create({container: {flex: 1,alignItems: 'center',backgroundColor: '#fff',},list_style:{flex: 1,width: width,backgroundColor:'#6699FF'},list_item:{marginVertical:20,justifyContent:'center',alignItems:'center',},});

注:本文为作者原创,转载请注明作者及出处。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。