通過React-Native實(shí)現(xiàn)自定義橫向滑動(dòng)進(jìn)度條的 ScrollView組件
概要
本篇文章概述了通過React-Native實(shí)現(xiàn)一個(gè)允許自定義橫向滑動(dòng)進(jìn)度條的ScrollView組件。
需求
開發(fā)一個(gè)首頁擺放菜單入口的ScrollView可滑動(dòng)組件(類似某淘首頁上的菜單效果),允許自定義橫向滑動(dòng)進(jìn)度條,且內(nèi)部渲染的菜單內(nèi)容支持自定義展示的行數(shù)和列數(shù),在內(nèi)容超出屏幕后,渲染順序?yàn)榭v向由上至下依次排列。
Animated 動(dòng)畫
ScrollView 滑動(dòng)組件
自定義滑動(dòng)進(jìn)度條
確定參數(shù)
首先,讓我們確定一下自定義滑動(dòng)進(jìn)度條需要哪些參數(shù)來支持:
- 初始位置時(shí),確定顯示進(jìn)度的條的寬度(barWidth)
- 滑動(dòng)進(jìn)度,以此來確定上面這個(gè)條的位置現(xiàn)在應(yīng)該到哪里了(marLeftAnimated)
計(jì)算參數(shù)
1.想要確定顯示進(jìn)度的條的寬度(barWidth),那么必須先知道三個(gè)值:
- ScrollView總寬度(containerStyle傳入)
- 進(jìn)度條背景的寬度(indicatorBgStyle傳入)
- ScrollView內(nèi)部內(nèi)容總寬度(childWidth,通過onContentSizeChange方法測量)
然后我們就可以進(jìn)行如下計(jì)算,這樣得到的_barWidth就是顯示進(jìn)度的條的寬度(barWidth):
let _barWidth = (this.props.indicatorBgStyle.width * this.props.containerStyle.width) / this.state.childWidth;
2.想要確定顯示進(jìn)度的條的位置(marLeftAnimated),那么必須先知道兩個(gè)值:
- ScrollView可滑動(dòng)距離(scrollDistance)
- 進(jìn)度部分可滑動(dòng)距離(leftDistance)
然后我們就可以進(jìn)行如下定義,這樣得到的marLeftAnimated,輸出值即為進(jìn)度條的距左距離:
let scrollDistance = this.state.childWidth - this.props.containerStyle.width ... //顯示滑動(dòng)進(jìn)度部分的距左距離 let leftDistance = this.props.indicatorBgStyle.width - _barWidth; const scrollOffset = this.state.scrollOffset this.marLeftAnimated = scrollOffset.interpolate({ inputRange: [0, scrollDistance], //輸入值區(qū)間為內(nèi)容可滑動(dòng)距離 outputRange: [0, leftDistance], //映射輸出區(qū)間為進(jìn)度部分可改變距離 extrapolate: 'clamp', //鉗制輸出值 useNativeDriver: true, })
滑動(dòng)進(jìn)度條的實(shí)現(xiàn)
通過Animated.View,定義絕對位置,將兩個(gè)條在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事件獲取滑動(dòng)偏移量,然后通過偏移量改變動(dòng)畫的值,這里我就不多說了,不明白的可以看我上一篇文章。
首頁定制菜單
確定參數(shù)
首先,讓我們確定一下實(shí)現(xiàn)首頁定制菜單需要哪些參數(shù)來支持:
- 列數(shù)量(columnLimit)
- 行數(shù)量(rowLimit)
渲染方式
根據(jù)行列數(shù)量,決定每屏的菜單總數(shù)。根據(jù)行數(shù)量,決定渲染結(jié)果數(shù)組里有幾組,一行就是一組。
let optionTotalArr = []; //存放所有option樣式的數(shù)組 //根據(jù)行數(shù),聲明用于存放每一行渲染內(nèi)容的數(shù)組 for( let i = 0; i < rowLimit; i++ ) optionTotalArr.push([])
1.沒超出屏幕時(shí),確定渲染行的方式如下:
if(index < columnLimit * rowLimit){ //沒超出一屏數(shù)量時(shí),根據(jù)列數(shù)更新行標(biāo)識(shí) rowIndex = parseInt(index / columnLimit) }
2.超出屏幕時(shí),確定渲染行的方式如下:
//當(dāng)超出一屏數(shù)量時(shí),根據(jù)行數(shù)更新行標(biāo)識(shí) rowIndex = index % rowLimit;
遍歷輸出
根據(jù)行數(shù),遍歷存放計(jì)算后的行內(nèi)容數(shù)組。
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及滑動(dòng)進(jìn)度條的全部區(qū)域 containerStyle: PropTypes.oneOfType([ PropTypes.object, PropTypes.array, ]), //ScrollView的樣式 style: PropTypes.oneOfType([ PropTypes.object, PropTypes.array, ]), //滑動(dòng)進(jìn)度條底部樣式 indicatorBgStyle: PropTypes.oneOfType([ PropTypes.object, PropTypes.array, ]), //滑動(dòng)進(jìn)度條樣式 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 = { //滑動(dòng)偏移量 scrollOffset: new Animated.Value(0), //ScrollView子布局寬度 childWidth: this.props.containerStyle.width, //顯示滑動(dòng)進(jìn)度部分條的長度 barWidth: props.indicatorBgStyle.width / 2, }; } UNSAFE_componentWillMount() { this.animatedEvent = Animated.event( [{ nativeEvent: { contentOffset: { x: this.state.scrollOffset } } }] ) } componentDidUpdate(prevProps, prevState) { //內(nèi)容可滑動(dòng)距離 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, }) //顯示滑動(dòng)進(jìn)度部分的距左距離 let leftDistance = this.props.indicatorBgStyle.width - _barWidth; const scrollOffset = this.state.scrollOffset this.marLeftAnimated = scrollOffset.interpolate({ inputRange: [0, scrollDistance], //輸入值區(qū)間為內(nèi)容可滑動(dòng)距離 outputRange: [0, leftDistance], //映射輸出區(qū)間為進(jìn)度部分可改變距離 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} //自定義滑動(dòng)進(jìn)度條,所以這里設(shè)置不顯示 scrollEventThrottle={0.1} //滑動(dòng)監(jiān)聽調(diào)用頻率 onScroll={this.animatedEvent} //滑動(dòng)監(jiān)聽事件,用來映射動(dòng)畫值 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列數(shù)量 const rowLimit = 2; //option行數(shù)量 // 編寫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; //每個(gè)option的寬度 let optionTotalArr = []; //存放所有option樣式的數(shù)組 //根據(jù)行數(shù),聲明用于存放每一行渲染內(nèi)容的數(shù)組 for( let i = 0; i < rowLimit; i++ ) optionTotalArr.push([]) this.itemArr.map((item,index) => { let rowIndex = 0; //行標(biāo)識(shí) if(index < columnLimit * rowLimit){ //沒超出一屏數(shù)量時(shí),根據(jù)列數(shù)更新行標(biāo)識(shí) rowIndex = parseInt(index / columnLimit) }else{ //當(dāng)超出一屏數(shù)量時(shí),根據(jù)行數(shù)更新行標(biāo)識(shí) 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', }, });
注:本文為作者原創(chuàng),轉(zhuǎn)載請注明作者及出處。
到此這篇關(guān)于通過React-Native實(shí)現(xiàn)自定義橫向滑動(dòng)進(jìn)度條的 ScrollView組件的文章就介紹到這了,更多相關(guān)React Native橫向滑動(dòng)進(jìn)度條內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于react-router-dom路由入門教程
這篇文章主要介紹了關(guān)于react-router-dom路由入門教程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03使用React?SSR寫Demo一學(xué)就會(huì)
這篇文章主要為大家介紹了使用React?SSR寫Demo實(shí)現(xiàn)教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06高性能React開發(fā)React Server Components詳解
ReactServerComponents通過服務(wù)器端渲染、自動(dòng)代碼分割等技術(shù),實(shí)現(xiàn)了高性能的React開發(fā),它解決了客戶端數(shù)據(jù)請求鏈?zhǔn)窖舆t、敏感數(shù)據(jù)暴露風(fēng)險(xiǎn)等問題,提供了更好的用戶體驗(yàn)和安全性,本文介紹高性能React開發(fā)React Server Components詳解,感興趣的朋友一起看看吧2025-03-03Redux thunk中間件及執(zhí)行原理詳細(xì)分析
redux的核心概念其實(shí)很簡單:將需要修改的state都存入到store里,發(fā)起一個(gè)action用來描述發(fā)生了什么,用reducers描述action如何改變state tree,這篇文章主要介紹了Redux thunk中間件及執(zhí)行原理分析2022-09-09React Fragment 和空標(biāo)簽(<></>)用法及區(qū)別詳解
本文詳細(xì)介紹了React中的Fragment和空標(biāo)簽的使用,包括它們的區(qū)別、使用場景、性能考慮以及最佳實(shí)踐,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2025-01-01