通過React-Native實現(xiàn)自定義橫向滑動進度條的 ScrollView組件
概要
本篇文章概述了通過React-Native實現(xiàn)一個允許自定義橫向滑動進度條的ScrollView組件。
需求
開發(fā)一個首頁擺放菜單入口的ScrollView可滑動組件(類似某淘首頁上的菜單效果),允許自定義橫向滑動進度條,且內(nèi)部渲染的菜單內(nèi)容支持自定義展示的行數(shù)和列數(shù),在內(nèi)容超出屏幕后,渲染順序為縱向由上至下依次排列。
Animated 動畫
ScrollView 滑動組件
自定義滑動進度條
確定參數(shù)
首先,讓我們確定一下自定義滑動進度條需要哪些參數(shù)來支持:
- 初始位置時,確定顯示進度的條的寬度(barWidth)
- 滑動進度,以此來確定上面這個條的位置現(xiàn)在應(yīng)該到哪里了(marLeftAnimated)
計算參數(shù)
1.想要確定顯示進度的條的寬度(barWidth),那么必須先知道三個值:
- ScrollView總寬度(containerStyle傳入)
- 進度條背景的寬度(indicatorBgStyle傳入)
- ScrollView內(nèi)部內(nèi)容總寬度(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], //輸入值區(qū)間為內(nèi)容可滑動距離
outputRange: [0, leftDistance], //映射輸出區(qū)間為進度部分可改變距離
extrapolate: 'clamp', //鉗制輸出值
useNativeDriver: true,
})滑動進度條的實現(xiàn)
通過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事件獲取滑動偏移量,然后通過偏移量改變動畫的值,這里我就不多說了,不明白的可以看我上一篇文章。
首頁定制菜單
確定參數(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.沒超出屏幕時,確定渲染行的方式如下:
if(index < columnLimit * rowLimit){
//沒超出一屏數(shù)量時,根據(jù)列數(shù)更新行標識
rowIndex = parseInt(index / columnLimit)
}2.超出屏幕時,確定渲染行的方式如下:
//當超出一屏數(shù)量時,根據(jù)行數(shù)更新行標識 rowIndex = index % rowLimit;
遍歷輸出
根據(jù)行數(shù),遍歷存放計算后的行內(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及滑動進度條的全部區(qū)域
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) {
//內(nèi)容可滑動距離
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], //輸入值區(qū)間為內(nèi)容可滑動距離
outputRange: [0, leftDistance], //映射輸出區(qū)間為進度部分可改變距離
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} //自定義滑動進度條,所以這里設(shè)置不顯示
scrollEventThrottle={0.1} //滑動監(jiān)聽調(diào)用頻率
onScroll={this.animatedEvent} //滑動監(jiān)聽事件,用來映射動畫值
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; //每個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; //行標識
if(index < columnLimit * rowLimit){
//沒超出一屏數(shù)量時,根據(jù)列數(shù)更新行標識
rowIndex = parseInt(index / columnLimit)
}else{
//當超出一屏數(shù)量時,根據(jù)行數(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實現(xiàn)自定義橫向滑動進度條的 ScrollView組件的文章就介紹到這了,更多相關(guān)React Native橫向滑動進度條內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于react-router-dom路由入門教程
這篇文章主要介紹了關(guān)于react-router-dom路由入門教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
高性能React開發(fā)React Server Components詳解
ReactServerComponents通過服務(wù)器端渲染、自動代碼分割等技術(shù),實現(xiàn)了高性能的React開發(fā),它解決了客戶端數(shù)據(jù)請求鏈式延遲、敏感數(shù)據(jù)暴露風(fēng)險等問題,提供了更好的用戶體驗和安全性,本文介紹高性能React開發(fā)React Server Components詳解,感興趣的朋友一起看看吧2025-03-03
React Fragment 和空標簽(<></>)用法及區(qū)別詳解
本文詳細介紹了React中的Fragment和空標簽的使用,包括它們的區(qū)別、使用場景、性能考慮以及最佳實踐,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2025-01-01

