通过React-Native实现自定义横向滑动进度条的 ScrollView组件

 更新时间:2024年02月05日 11:36:09   作者:清风不暖人  
开发一个首页摆放菜单入口的ScrollView可滑动组件,允许自定义横向滑动进度条,且内部渲染的菜单内容支持自定义展示的行数和列数,在内容超出屏幕后,渲染顺序为纵向由上至下依次排列,对React Native横向滑动进度条相关知识感兴趣的朋友一起看看吧
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

概要

本篇文章概述了通过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.scrollOffset
    this.marLeftAnimated = scrollOffset.interpolate({
      inputRange: [0, scrollDistance],  //输入值区间为内容可滑动距离
      outputRange: [0, leftDistance],  //映射输出区间为进度部分可改变距离
      extrapolate: 'clamp',  //钳制输出值
      useNativeDriver: true,
    })

滑动进度条的实现

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

<View style={[{alignSelf:'center'},this.props.indicatorBgStyle]}>
      <Animated.View
        style={[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.width
    if( 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.scrollOffset
      this.marLeftAnimated = scrollOffset.interpolate({
        inputRange: [0, scrollDistance],  //输入值区间为内容可滑动距离
        outputRange: [0, leftDistance],  //映射输出区间为进度部分可改变距离
        extrapolate: 'clamp',  //钳制输出值
        useNativeDriver: true,
      })
    }
  }
  render() {
    return (
      <View style={[styles.container,this.props.containerStyle]}>
        <ScrollView
          style={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.View
              style={[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(
			<View
				style={{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',
	},
});

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

到此这篇关于通过React-Native实现自定义横向滑动进度条的 ScrollView组件的文章就介绍到这了,更多相关React Native横向滑动进度条内容请搜索程序员之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员之家!

相关文章

  • react项目引入scss的方法

    react项目引入scss的方法

    这篇文章主要介绍了react项目引入scss的方法,本文给大家介绍了React pwa的配置方法,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • react以create-react-app为基础创建项目

    react以create-react-app为基础创建项目

    这篇文章主要介绍了react以create-react-app为基础创建项目,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • React 组件转 Vue 组件的命令写法

    React 组件转 Vue 组件的命令写法

    本文先介绍两个框架的组件共性和不兼容的地方,再介绍react-to-vue的使用和原理,需要的朋友可以参考下
    2018-02-02
  • React中的权限组件设计问题小结

    React中的权限组件设计问题小结

    这篇文章主要介绍了React中的权限组件设计,整个过程也是遇到了很多问题,本文主要来做一下此次改造工作的总结,对React权限组件相关知识感兴趣的朋友一起看看吧
    2022-07-07
  • React项目中使用Redux的?react-redux

    React项目中使用Redux的?react-redux

    这篇文章主要介绍了React项目中使用Redux的?react-redux,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • React实现动效弹窗组件

    React实现动效弹窗组件

    最近在使用react开发项目,遇到这样一个需求实现一个带有动效的 React 弹窗组件,如果不考虑动效,很容易实现,接下来小编通过本文给大家介绍React实现动效弹窗组件的实现代码,一起看看吧
    2021-06-06
  • 一文搞懂redux在react中的初步用法

    一文搞懂redux在react中的初步用法

    Redux是JavaScript状态容器,提供可预测化的状态管理,今天通过本文给大家分享redux在react中使用及配置redux到react项目中的方法,感兴趣的朋友跟随小编一起看看吧
    2021-06-06
  • React hooks使用规则和作用

    React hooks使用规则和作用

    这篇文章主要介绍了react hooks实现原理,文中给大家介绍了useState dispatch 函数如何与其使用的 Function Component 进行绑定,节后实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • 使用React?SSR写Demo一学就会

    使用React?SSR写Demo一学就会

    这篇文章主要为大家介绍了使用React?SSR写Demo实现教程示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • React版本18.xx降低为17.xx的方法实现

    React版本18.xx降低为17.xx的方法实现

    由于现在react默认创建是18.xx版本,但是我们现在大多使用的还是17.xx或者更低的版本,于是要对react版本进行降级,本文主要介绍了React版本18.xx降低为17.xx的方法实现,感兴趣的可以了解一下
    2023-11-11

最新评论


http://www.vxiaotou.com