有同学留言,想要实现一个双滑块的进度条,安排!
实现思路:
1.实现双滑块滑动条,因此需要2个滑块,一个滑动条
2.使用Stack布局 ,左右放2个Circle作为滑块,实现2个滑块
3.如果想区分滑动区域和未滑动区域的颜色,需要将滑动条分为三部分,左边从小到大滑动区域,右边从大到小滑动区域,中间区域三部分,因此使用3个并列的Row拼接成一个滑动条
4.给2个滑块增加滑动事件,移动左边的滑块同时修改左边Row的宽度和背景色,移动右边的滑块修改右边的Row的宽度和背景色
5.需要给2个滑块增加边界限制,左边滑块滑动边界为滑动条左侧到右边滑块,右边滑块的滑动边界为滑动条右侧到左边滑块
6.通过计算滑动偏移占滑动条的比例计算滑动值
实现演示:

实现源码: 属性介绍可以看源码中的注释
kotlin
import { getScreenWidth } from '../utils/DisplayUtil';
@Entry
@ComponentV2
struct DoubleSlider {
//Slider value
@Local sliderMaxValue:number = 100
@Local sliderMinValue:number = 0
//最小值左边进度条宽度
@Local minValue: number = 0;
//最大值右边进度条宽度
@Local maxValue: number = 0;
//最小值进度条背景色
@Local minTrackColor: ResourceStr = '#1E90FF';
//中间进度条背景色
@Local middleTrackColor: ResourceStr = '#D3D3D3';
//最大值进度条背景色
@Local maxTrackColor: ResourceStr = '#1E90FF';
//滑块背景色
@Local thumbColor: ResourceStr = '#1E90FF';
//进度条高度
@Local trackHeight: number = 4;
//滑块大小
@Local thumbDiameter: number = 20;
//自定义滑动条的宽
@Local sliderWidth: number = 0;
//最小值滑块滑动偏移量
@Local minBlockOffset: number = 0;
//最大值滑块滑动偏移量
@Local maxBlockOffset: number = 0;
//最大滑块最右的X坐标
@Local maxBlockX: number = 0;
//最小滑块最左的X坐标
@Local minBlockX: number = 0;
//当前左滑块坐标
@Local currentMinBlockX:number = 0
//当前右滑块坐标
@Local currentMaxBlockX:number = 0
//滑动最小值 用于展示和使用
@Local currentMinValue:number = this.sliderMinValue;
//滑动最大值 用于展示和使用
@Local currentMaxValue:number = this.sliderMaxValue;
build() {
Column() {
// 标题
Text('双滑块滑动条')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 30 })
Row({space:10}){
Column() {
Text("Slider最小值")
Counter() {
Text(this.sliderMinValue.toString())
}
.onInc(() => {
this.sliderMinValue += 10;
})
.onDec(() => {
if (this.sliderMinValue > 10) {
this.sliderMinValue -= 10;
}
})
}
Column() {
Text("Slider最大值")
Counter() {
Text(this.sliderMaxValue.toString())
}
.onInc(() => {
this.sliderMaxValue += 10;
})
.onDec(() => {
if (this.sliderMaxValue > 10) {
this.sliderMaxValue -= 10;
}
})
}
Button('归位').onClick(()=>{
this.minBlockOffset=0
this.maxBlockOffset=0
this.minValue=0
this.maxValue=0
this.currentMinValue=this.sliderMinValue
this.currentMaxValue=this.sliderMaxValue
})
}
// 数值显示
Row() {
Text(`最小值:`+ this.currentMinValue)
.fontSize(16)
.width('50%')
.textAlign(TextAlign.Start)
Text(`最大值:`+this.currentMaxValue)
.fontSize(16)
.width('50%')
.textAlign(TextAlign.End)
}
.width('90%')
.margin({ bottom: 20 })
// 双滑块滑动条
Stack({ alignContent: Alignment.Center }) {
// 背景轨道
Row() {
// 左侧轨道(最小值左侧)
Row()
.backgroundColor(this.minTrackColor)
.height(this.trackHeight)
.width(`${this.minValue}%`)
// 中间轨道(最小值和最大值之间)
Row()
.backgroundColor(this.middleTrackColor)
.height(this.trackHeight)
// .width(`${100-this.maxValue-this.minValue}%`)
.layoutWeight(1)
// 右侧轨道(最大值右侧)
Row()
.backgroundColor(this.maxTrackColor)
.height(this.trackHeight)
.width(`${this.maxValue}%`)
}
.width('90%')
.height(this.trackHeight)
.onSizeChange((oldValue: SizeOptions, newValue: SizeOptions)=>{
this.sliderWidth = newValue.width as number
this.maxBlockX =getScreenWidth()-(getScreenWidth()-this.sliderWidth)/2
this.minBlockX = (getScreenWidth()-this.sliderWidth)/2
this.currentMaxBlockX = this.maxBlockX
console.info('----------------sliderWidth:'+this.sliderWidth+' maxBlockX:'+this.maxBlockX);
})
// 最小值滑块
Circle({width:this.thumbDiameter,height:this.thumbDiameter})
.fill(this.thumbColor)
.onTouch((event: TouchEvent)=>{
console.info('---------最小值滑块相当于屏幕X坐标:'+event.touches[0].windowX);
this.currentMinBlockX = event.touches[0].windowX
if(this.currentMinBlockX<this.minBlockX){
return
}
if (this.currentMinBlockX>=this.maxBlockX) {
return
}
if (this.currentMinBlockX> this.currentMaxBlockX) {
return
}
this.minBlockOffset = this.currentMinBlockX - this.minBlockX
this.minValue = this.minBlockOffset/this.sliderWidth*100
this.currentMinValue =this.calculateValue(this.minValue/100)+this.sliderMinValue
console.info('---------最小值滑块相当于屏幕X坐标:'+event.touches[0].windowX+
' 偏移: '+this.minBlockOffset+
' minValue: '+this.minValue
);
})
.offset({x:this.minBlockOffset-this.sliderWidth/2})
// 最大值滑块
Circle({width:this.thumbDiameter,height:this.thumbDiameter})
.fill(this.thumbColor)
.onTouch((event: TouchEvent)=>{
this.currentMaxBlockX = event.touches[0].windowX
if (this.currentMaxBlockX>this.maxBlockX) {
return
}
if (this.currentMaxBlockX< this.minBlockX) {
return
}
if (this.currentMaxBlockX<=this.currentMinBlockX) {
return
}
this.maxBlockOffset = event.touches[0].windowX - this.maxBlockX
this.maxValue = Math.abs(this.maxBlockOffset)/this.sliderWidth*100
this.currentMaxValue = this.calculateValue(1-Math.floor(this.maxValue)/100)+this.sliderMinValue
console.info('---------最大值滑块相当于屏幕X坐标:'+event.touches[0].windowX+
' maxValue: '+this.maxValue
);
})
.offset({x:this.sliderWidth/2+this.maxBlockOffset})
}
.width('100%')
.height(50)
.margin({ bottom: 50 })
}
.width('100%')
.height('100%')
.padding(10)
}
// 计算滑块位置对应的数值
calculateValue(percent: number): number {
// 取整
return Math.floor(percent*(this.sliderMaxValue-this.sliderMinValue));
}
}
补充:Stack布局设置的是居中,所以2各滑块默认都是居中显示,所以滑块的默认偏移量是滑动条宽度的一半,之后再根据滑动位置计算新的相对偏移量。
以上实现了一个简单的双滑块滑动条,后续可以优化封装成一个组件,对外暴露属性和接口,方便复用。 如果有用,记得点赞、转发、收藏、评论 感谢!