通過React-Native實(shí)現(xiàn)自定義橫向滑動(dòng)進(jìn)度條的 ScrollView組件
概要
本篇文章概述了通過React-Native實(shí)現(xiàn)一個(gè)允許自定義橫向滑動(dòng)進(jìn)度條的ScrollView組件。
需求
開發(fā)一個(gè)首頁(yè)擺放菜單入口的ScrollView可滑動(dòng)組件(類似某淘首頁(yè)上的菜單效果),允許自定義橫向滑動(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)部?jī)?nèi)容總寬度(childWidth,通過onContentSizeChange方法測(cè)量)
然后我們就可以進(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,定義絕對(duì)位置,將兩個(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)畫的值,這里我就不多說了,不明白的可以看我上一篇文章。
首頁(yè)定制菜單
確定參數(shù)
首先,讓我們確定一下實(shí)現(xiàn)首頁(yè)定制菜單需要哪些參數(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)度部分條的長(zhǎng)度
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)載請(qǐng)注明作者及出處。
到此這篇關(guān)于通過React-Native實(shí)現(xiàn)自定義橫向滑動(dòng)進(jìn)度條的 ScrollView組件的文章就介紹到這了,更多相關(guān)React Native橫向滑動(dòng)進(jìn)度條內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于react-router-dom路由入門教程
這篇文章主要介紹了關(guān)于react-router-dom路由入門教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(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ù)請(qǐng)求鏈?zhǔn)窖舆t、敏感數(shù)據(jù)暴露風(fēng)險(xiǎn)等問題,提供了更好的用戶體驗(yàn)和安全性,本文介紹高性能React開發(fā)React Server Components詳解,感興趣的朋友一起看看吧2025-03-03
Redux thunk中間件及執(zhí)行原理詳細(xì)分析
redux的核心概念其實(shí)很簡(jiǎn)單:將需要修改的state都存入到store里,發(fā)起一個(gè)action用來描述發(fā)生了什么,用reducers描述action如何改變state tree,這篇文章主要介紹了Redux thunk中間件及執(zhí)行原理分析2022-09-09
React Fragment 和空標(biāo)簽(<></>)用法及區(qū)別詳解
本文詳細(xì)介紹了React中的Fragment和空標(biāo)簽的使用,包括它們的區(qū)別、使用場(chǎng)景、性能考慮以及最佳實(shí)踐,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2025-01-01

