鸿蒙 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@ComponentV2struct 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开发笔记









评论