鸿蒙Next自定义双滑块滑动条实现方案

有同学留言,想要实现一个双滑块的进度条,安排!

实现思路

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各滑块默认都是居中显示,所以滑块的默认偏移量是滑动条宽度的一半,之后再根据滑动位置计算新的相对偏移量。

以上实现了一个简单的双滑块滑动条,后续可以优化封装成一个组件,对外暴露属性和接口,方便复用。 如果有用,记得点赞、转发、收藏、评论 感谢!

相关推荐
三掌柜66627 分钟前
HarmonyOS开发:显示图片功能详解
华为·harmonyos
二蛋和他的大花27 分钟前
HarmonyOS运动语音开发:如何让运动开始时的语音播报更温暖
华为·harmonyos
空潭冷月1 小时前
HarmonyOS5:鸿蒙旅游类应用开发实践
harmonyos
空潭冷月1 小时前
HarmonyOS5:鸿蒙体育类应用开发实践
harmonyos
全栈若城1 小时前
71.[HarmonyOS NEXT 实战案例十] 电子书网格布局(上)
harmonyos
全栈若城1 小时前
85.[HarmonyOS NEXT 实战案例十七] 设置选项列表网格布局(下)
harmonyos
全栈若城1 小时前
73.[HarmonyOS NEXT 实战案例十一] 智能家居控制面板网格布局(上)
harmonyos
半路下车6 小时前
【Harmony OS 5】HarmonyOS应用测试指南
设计模式·harmonyos
SunFlower016 小时前
Harmony-websocket(一)
harmonyos