写点什么

鸿蒙 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
用户头像

auhgnixgnahz

关注

还未添加个人签名 2018-07-10 加入

欢迎关注:HarmonyOS开发笔记

评论

发布
暂无评论
鸿蒙Next自定义双滑块滑动条实现方案_鸿蒙Next_auhgnixgnahz_InfoQ写作社区