鸿蒙 Next 自定义双滑块滑动条实现方案
作者:auhgnixgnahz
- 2025-06-25 北京
本文字数:3653 字
阅读完需:约 12 分钟
实现思路:
1.实现双滑块滑动条,因此需要 2 个滑块,一个滑动条
2.使用 Stack 布局,左右放 2 个 Circle 作为滑块,实现 2 个滑块
3.如果想区分滑动区域和未滑动区域的颜色,需要将滑动条分为三部分,左边从小到大滑动区域,右边从大到小滑动区域,中间区域三部分,因此使用 3 个并列的 Row 拼接成一个滑动条
4.给 2 个滑块增加滑动事件,移动左边的滑块同时修改左边 Row 的宽度和背景色,移动右边的滑块修改右边的 Row 的宽度和背景色
5.需要给 2 个滑块增加边界限制,左边滑块滑动边界为滑动条左侧到右边滑块,右边滑块的滑动边界为滑动条右侧到左边滑块
6.通过计算滑动偏移占滑动条的比例计算滑动值
实现演示:

实现源码: 属性介绍可以看源码中的注释
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));
}
}
</this.minblockx){
复制代码
补充:Stack 布局设置的是居中,所以 2 各滑块默认都是居中显示,所以滑块的默认偏移量是滑动条宽度的一半,之后再根据滑动位置计算新的相对偏移量。
以上实现了一个简单的双滑块滑动条,后续可以优化封装成一个组件,对外暴露属性和接口,方便复用。
如果有用,记得点赞、转发、收藏、评论感谢!
划线
评论
复制
发布于: 刚刚阅读数: 2
版权声明: 本文为 InfoQ 作者【auhgnixgnahz】的原创文章。
原文链接:【http://xie.infoq.cn/article/7562a628d1392501056f390a8】。文章转载请联系作者。

auhgnixgnahz
关注
还未添加个人签名 2018-07-10 加入
欢迎关注:HarmonyOS开发笔记
评论