鸿蒙RangeSeekBar或Dual Slider效果实现

57 阅读6分钟

请问鸿蒙有没有双向可滑动的控件来选择区间的呀

**已解决

找了一圈api只有找到Slider这个 请问有没有解决办法大佬们求助cke_356.png

点赞

收藏

2

回复

14

分享

举报

浏览602 发布于2024-08-06 16:54湖南

全部评论

最多点赞

最新发布

最早发布

醉心晴抒

|

**采纳答复

**

弄了个差不多的,仅供参考:

import { MeasureText } from '@kit.ArkUI'

@Component
export struct DualSliders {
  /**
   * 圆形滑块的大小
   */
  circleSize = 25
  paddingLeftRight = 15
  /**
   * 整个组件的宽度
   */
  componentWidth = 0
  /**
   * 滑轨的总宽度
   */
  sliderWidth = 0
  /**
   * 左边圆形滑块的X轴偏移量
   */
  @State leftOffsetX: number = 0
  /**
   * 右边圆形滑块的X轴偏移量
   */
  @State rightOffsetX: number = 0
  /**
   * 左边圆形滑块的最终位置
   */
  @State leftPositionX: number = 0
  /**
   * 右边圆形滑块的最终位置
   */
  @State rightPositionX: number = 0
  /**
   * 左边进度条的宽度
   */
  @State leftLineWidth: number = 0
  /**
   * 中间进度条的宽度
   */
  @State middleLineWidth: number = 0
  /**
   * 右边进度条的宽度
   */
  @State rightLineWidth: number = 0
  /**
   * 刻度0的位置
   */
  @State positionX0: number = 0
  /**
   * 刻度30的位置
   */
  @State positionX30: number = 0
  /**
   * 刻度60的位置
   */
  @State positionX60: number = 0
  /**
   * 刻度90的位置
   */
  @State positionX90: number = 0
  /**
   * 刻度100的位置
   */
  @State positionX100: number = 0
  /**
   * 左边圆形滑块滑动时展示的进度值
   */
  @Link leftValue: string
  /**
   * 右边圆形滑块滑动时展示的进度值
   */
  @Link rightValue: string
  /**
   * 左边圆形滑块是否在拖拽
   */
  @State isLeftCircleDragging: boolean = false
  /**
   * 右边圆形滑块是否在拖拽
   */
  @State isRightCircleDragging: boolean = false
  /**
   * 左边进度值文本的X轴偏移量
   */
  @State leftValueOffsetX: number = 0
  /**
   * 右边进度值文本的X轴偏移量
   */
  @State rightValueOffsetX: number = 0
  /**
   * 滑块滑动时进度值文本的宽度
   */
  progressTextWidth = 40
  leftDefValue = 30
  rightDefValue = 60

  initComponents() {
    this.sliderWidth = this.componentWidth - 2 * this.paddingLeftRight
    let leftValue = parseInt(this.leftValue)
    let rightValue = parseInt(this.rightValue)
    let minValue = this.circleSize / this.sliderWidth * 100
    let isSetInvalid =
      Number.isNaN(leftValue) || Number.isNaN(rightValue) || leftValue < 0 || rightValue > 100 ||
        rightValue < minValue || leftValue + minValue > rightValue
    if (isSetInvalid) {
      // 无效设置,使用默认值
      leftValue = this.leftDefValue
      rightValue = this.rightDefValue
      this.leftValue = `${this.leftDefValue}`
      this.rightValue = `${this.rightDefValue}`
    }
    this.leftOffsetX = leftValue / 100 * this.sliderWidth - this.circleSize / 2
    this.leftPositionX = this.leftOffsetX
    this.rightOffsetX = rightValue / 100 * this.sliderWidth - this.circleSize / 2
    this.rightPositionX = this.rightOffsetX

    this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
    this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2

    this.leftLineWidth = this.leftPositionX + this.circleSize / 2
    this.middleLineWidth = this.rightPositionX - this.leftPositionX
    this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2

    let width0 = px2vp(MeasureText.measureText({ textContent: '0' }))
    let width30 = px2vp(MeasureText.measureText({ textContent: '30' }))
    let width60 = px2vp(MeasureText.measureText({ textContent: '60' }))
    let width90 = px2vp(MeasureText.measureText({ textContent: '90' }))
    let width100 = px2vp(MeasureText.measureText({ textContent: '100' }))
    this.positionX0 = this.paddingLeftRight - width0 / 2
    this.positionX30 = this.paddingLeftRight + this.sliderWidth * 0.3 - width30 / 2
    this.positionX60 = this.paddingLeftRight + this.sliderWidth * 0.6 - width60 / 2
    this.positionX90 = this.paddingLeftRight + this.sliderWidth * 0.9 - width90 / 2
    this.positionX100 = this.paddingLeftRight + this.sliderWidth - width100 / 2
  }

  build() {
    RelativeContainer() {
      Row() {
        Text('0').fontColor(Color.Grey).position({ x: this.positionX0 })
        Text('30').fontColor(Color.Green).position({ x: this.positionX30 })
        Text('60').fontColor(Color.Grey).position({ x: this.positionX60 })
        Text('90').fontColor(Color.Grey).position({ x: this.positionX90 })
        Text('100').fontColor(Color.Grey).position({ x: this.positionX100 })
      }.id('numbers')

      Row() {
        Divider().width(this.leftLineWidth).backgroundColor(Color.Grey).height(3)
        Divider().width(this.middleLineWidth).backgroundColor(Color.Blue).height(3)
        Divider().width(this.rightLineWidth).backgroundColor(Color.Grey).height(3)
      }.id('line')
      .alignRules({ center: { anchor: 'left_circle', align: VerticalAlign.Center } })

      Circle()
        .width(this.circleSize)
        .height(this.circleSize)
        .fill(Color.White)
        .stroke(Color.Gray)
        .id('left_circle')
        .alignRules({ top: { anchor: 'numbers', align: VerticalAlign.Bottom } })
        .margin({ top: 20 })
        .translate({ x: this.leftOffsetX })
        .gesture(PanGesture({ direction: PanDirection.Horizontal })
          .onActionStart(_ => this.isLeftCircleDragging = true)
          .onActionUpdate(event => {
            if (event) {
              let offsetX = this.leftPositionX + event.offsetX
              if (offsetX < -this.circleSize / 2) {
                this.leftOffsetX = -this.circleSize / 2
              } else if (offsetX > this.rightPositionX - this.circleSize) {
                this.leftOffsetX = this.rightPositionX - this.circleSize
              } else {
                this.leftOffsetX = offsetX
              }
              this.leftLineWidth = this.leftOffsetX + this.circleSize / 2
              this.middleLineWidth = this.rightPositionX - this.leftOffsetX
              this.leftValue = (this.leftLineWidth / this.sliderWidth * 100).toFixed()
              this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
            }
          }).onActionEnd(() => {
            this.leftPositionX = this.leftOffsetX
            this.isLeftCircleDragging = false
          }))

      Circle()
        .width(this.circleSize)
        .height(this.circleSize)
        .stroke(Color.Gray)
        .fill(Color.White)
        .id('right_circle')
        .alignRules({ top: { anchor: 'left_circle', align: VerticalAlign.Top } })
        .translate({ x: this.rightOffsetX })
        .gesture(PanGesture({ direction: PanDirection.Horizontal })
          .onActionStart(_ => this.isRightCircleDragging = true)
          .onActionUpdate(event => {
            if (event) {
              let offsetX = this.rightPositionX + event.offsetX
              if (offsetX < this.leftOffsetX + this.circleSize) {
                this.rightOffsetX = this.leftPositionX + this.circleSize
              } else if (offsetX > this.sliderWidth - this.circleSize / 2) {
                this.rightOffsetX = this.sliderWidth - this.circleSize / 2
              } else {
                this.rightOffsetX = offsetX
              }
              this.middleLineWidth = this.rightOffsetX - this.leftPositionX
              this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2
              this.rightValue = ((this.leftLineWidth + this.middleLineWidth) / this.sliderWidth * 100).toFixed()
              this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
            }
          }).onActionEnd(() => {
            this.rightPositionX = this.rightOffsetX
            this.isRightCircleDragging = false
          }))

      Text(this.leftValue)
        .id('left_value')
        .backgroundColor(Color.Orange)
        .translate({ x: this.leftValueOffsetX })
        .width(this.progressTextWidth)
        .textAlign(TextAlign.Center)
        .alignRules({ bottom: { anchor: 'left_circle', align: VerticalAlign.Top } })
        .visibility(this.isLeftCircleDragging ? Visibility.Visible : Visibility.Hidden)
        .margin({ bottom: 2 })

      Text(this.rightValue)
        .backgroundColor(Color.Orange)
        .id('right_value')
        .width(this.progressTextWidth)
        .textAlign(TextAlign.Center)
        .translate({ x: this.rightValueOffsetX })
        .alignRules({ bottom: { anchor: 'right_circle', align: VerticalAlign.Top } })
        .visibility(this.isRightCircleDragging ? Visibility.Visible : Visibility.Hidden)
        .margin({ bottom: 2 })
    }
    .width('100%')
    .padding({ left: this.paddingLeftRight, right: this.paddingLeftRight })
    .height('auto')
    .onAreaChange((_, newArea) => {
      this.componentWidth = newArea.width as number
      this.initComponents()
    })
  }
}

可以这么用:

@Entry
@Component
struct Index {
  @State leftValue: string = '20'
  @State rightValue: string = '50'
  
  build() {
    Column() {
      DualSliders({ leftValue: $leftValue, rightValue: $rightValue })
    }
    .width('100%')
    .height('100%')
  }
}

希望能满足你的要求,效果大致如下:

import { MeasureText } from '@kit.ArkUI'

@Component
export struct DualSliders {
  /**
   * 左边圆形滑块的X轴偏移量
   */
  @State leftOffsetX: number = 0
  /**
   * 右边圆形滑块的X轴偏移量
   */
  @State rightOffsetX: number = 0
  /**
   * 左边圆形滑块的最终位置
   */
  @State leftPositionX: number = 0
  /**
   * 右边圆形滑块的最终位置
   */
  @State rightPositionX: number = 0
  /**
   * 左边进度条的宽度
   */
  @State leftLineWidth: number = 0
  /**
   * 中间进度条的宽度
   */
  @State middleLineWidth: number = 0
  /**
   * 右边进度条的宽度
   */
  @State rightLineWidth: number = 0
  /**
   * 上方刻度值文本的位置
   */
  @State scaleValuesPosition: number[] = []
  /**
   * 左边圆形滑块滑动时展示的进度值
   */
  @Link leftValue: number
  /**
   * 右边圆形滑块滑动时展示的进度值
   */
  @Link rightValue: number
  /**
   * 左边圆形滑块是否在拖拽
   */
  @State isLeftCircleDragging: boolean = false
  /**
   * 右边圆形滑块是否在拖拽
   */
  @State isRightCircleDragging: boolean = false
  /**
   * 左边进度值文本的X轴偏移量
   */
  @State leftValueOffsetX: number = 0
  /**
   * 右边进度值文本的X轴偏移量
   */
  @State rightValueOffsetX: number = 0
  /**
   * 滑轨两边未选中颜色
   */
  sliderNormalColor: ResourceColor = Color.Grey
  /**
   * 滑轨中间选中颜色
   */
  sliderSelectColor: ResourceColor = Color.Blue
  /**
   * 滑轨高度
   */
  sliderHeight: Length = 3
  /**
   * 圆形滑块的大小
   */
  circleSize = 25
  /**
   * 左右padding
   */
  paddingLeftRight = 15
  /**
   * 滑块滑动时进度值文本的宽度
   */
  progressTextWidth = 40
  /**
   * 需要显示的刻度值文本数组,从小到大依次排列
   */
  scaleValues: number[] = [0, 30, 60, 90, 100]
  maxValue: number = 100
  minValue: number = 0
  range = this.maxValue - this.minValue
  /**
   * 组件大小初次确定时才进行初始化
   */
  isInitFinish = false
  /**
   * 上方刻度显示的粒度,调用的是浮点数的toFixed(),整数值
   */
  fractionDigits = 0
  /**
   * 整个组件的宽度
   */
  componentWidth = 0
  /**
   * 滑轨的总宽度
   */
  sliderWidth = 0
  /**
   * 上方刻度值文本颜色
   */
  scaleTextColors: ResourceColor[] = [Color.Grey, Color.Grey, Color.Grey, Color.Grey, Color.Grey]

  useDefaultValues() {
    this.leftValue = 30
    this.rightValue = 60
    this.maxValue = 100
    this.minValue = 0
    this.range = this.maxValue - this.minValue
    this.scaleValues = [0, 30, 60, 90, 100]
    this.fractionDigits = 0
    this.scaleTextColors = [Color.Grey, Color.Grey, Color.Grey, Color.Grey, Color.Grey]
  }

  initComponents() {
    if (this.isInitFinish) {
      return
    }
    this.isInitFinish = true
    this.sliderWidth = this.componentWidth - 2 * this.paddingLeftRight
    this.range = this.maxValue - this.minValue
    let minRange = this.circleSize / this.sliderWidth * this.range
    let isSetInvalid = this.leftValue < this.minValue || this.rightValue > this.maxValue ||
      this.rightValue < minRange || this.leftValue + minRange > this.rightValue
    if (isSetInvalid) {
      // 无效设置,使用默认值
      this.useDefaultValues()
    }
    this.leftOffsetX = (this.leftValue - this.minValue) / this.range * this.sliderWidth - this.circleSize / 2
    this.leftPositionX = this.leftOffsetX
    this.rightOffsetX = (this.rightValue - this.minValue) / this.range * this.sliderWidth - this.circleSize / 2
    this.rightPositionX = this.rightOffsetX

    this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
    this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2

    this.leftLineWidth = this.leftPositionX + this.circleSize / 2
    this.middleLineWidth = this.rightPositionX - this.leftPositionX
    this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2

    this.scaleValues.forEach(scale => {
      let width = px2vp(MeasureText.measureText({ textContent: `${scale}` }))
      let position = this.paddingLeftRight + this.sliderWidth * (scale - this.minValue) / this.range - width / 2
      this.scaleValuesPosition.push(position)
    })
  }

  build() {
    RelativeContainer() {
      Row() {
        ForEach(this.scaleValuesPosition, (item: number, index: number) => {
          Text(`${this.scaleValues[index]}`).fontColor(this.scaleTextColors[index]).position({ x: item })
        })
      }.id('numbers')

      Row() {
        Divider().width(this.leftLineWidth).backgroundColor(this.sliderNormalColor).height(this.sliderHeight)
        Divider().width(this.middleLineWidth).backgroundColor(this.sliderSelectColor).height(this.sliderHeight)
        Divider().width(this.rightLineWidth).backgroundColor(this.sliderNormalColor).height(this.sliderHeight)
      }.id('line')
      .alignRules({ center: { anchor: 'left_circle', align: VerticalAlign.Center } })

      Circle()
        .width(this.circleSize)
        .height(this.circleSize)
        .fill(Color.White)
        .stroke(Color.Gray)
        .id('left_circle')
        .alignRules({ top: { anchor: 'numbers', align: VerticalAlign.Bottom } })
        .margin({ top: 20 })
        .translate({ x: this.leftOffsetX })
        .gesture(PanGesture({ direction: PanDirection.Horizontal })
          .onActionStart(_ => this.isLeftCircleDragging = true)
          .onActionUpdate(event => {
            if (event) {
              let offsetX = this.leftPositionX + event.offsetX
              if (offsetX < -this.circleSize / 2) {
                this.leftOffsetX = -this.circleSize / 2
              } else if (offsetX > this.rightPositionX - this.circleSize) {
                this.leftOffsetX = this.rightPositionX - this.circleSize
              } else {
                this.leftOffsetX = offsetX
              }
              this.leftLineWidth = this.leftOffsetX + this.circleSize / 2
              this.middleLineWidth = this.rightPositionX - this.leftOffsetX
              this.leftValue = this.leftLineWidth / this.sliderWidth * this.range + this.minValue
              this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
            }
          }).onActionEnd(() => {
            this.leftPositionX = this.leftOffsetX
            this.isLeftCircleDragging = false
          }))

      Circle()
        .width(this.circleSize)
        .height(this.circleSize)
        .stroke(Color.Gray)
        .fill(Color.White)
        .id('right_circle')
        .alignRules({ top: { anchor: 'left_circle', align: VerticalAlign.Top } })
        .translate({ x: this.rightOffsetX })
        .gesture(PanGesture({ direction: PanDirection.Horizontal })
          .onActionStart(_ => this.isRightCircleDragging = true)
          .onActionUpdate(event => {
            if (event) {
              let offsetX = this.rightPositionX + event.offsetX
              if (offsetX < this.leftOffsetX + this.circleSize) {
                this.rightOffsetX = this.leftPositionX + this.circleSize
              } else if (offsetX > this.sliderWidth - this.circleSize / 2) {
                this.rightOffsetX = this.sliderWidth - this.circleSize / 2
              } else {
                this.rightOffsetX = offsetX
              }
              this.middleLineWidth = this.rightOffsetX - this.leftPositionX
              this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2
              this.rightValue =
                (this.leftLineWidth + this.middleLineWidth) / this.sliderWidth * this.range + this.minValue
              this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
            }
          }).onActionEnd(() => {
            this.rightPositionX = this.rightOffsetX
            this.isRightCircleDragging = false
          }))

      Text(this.leftValue.toFixed(this.fractionDigits))
        .id('left_value')
        .backgroundColor(Color.Orange)
        .translate({ x: this.leftValueOffsetX })
        .width(this.progressTextWidth)
        .textAlign(TextAlign.Center)
        .alignRules({ bottom: { anchor: 'left_circle', align: VerticalAlign.Top } })
        .visibility(this.isLeftCircleDragging ? Visibility.Visible : Visibility.Hidden)
        .margin({ bottom: 2 })

      Text(this.rightValue.toFixed(this.fractionDigits))
        .backgroundColor(Color.Orange)
        .id('right_value')
        .width(this.progressTextWidth)
        .textAlign(TextAlign.Center)
        .translate({ x: this.rightValueOffsetX })
        .alignRules({ bottom: { anchor: 'right_circle', align: VerticalAlign.Top } })
        .visibility(this.isRightCircleDragging ? Visibility.Visible : Visibility.Hidden)
        .margin({ bottom: 2 })
    }
    .width('100%')
    .padding({ left: this.paddingLeftRight, right: this.paddingLeftRight })
    .height('auto')
    .onAreaChange((_, newArea) => {
      this.componentWidth = newArea.width as number
      this.initComponents()
    })
  }
}

使用方式:

@Entry
@Component
struct Index {
  @State leftValue: number = 40
  @State rightValue: number = 70
  @State leftValue0: number = 2
  @State rightValue0: number = 5
  @State leftValue1: number = 0.3
  @State rightValue1: number = 0.6
  @State leftValue2: number = 18
  @State rightValue2: number = 50
  @State leftValue3: number = 18
  @State rightValue3: number = 50

  build() {
    Column({ space: 15 }) {
      DualSliders({
        leftValue: this.leftValue,
        rightValue: this.rightValue
      })

      DualSliders({
        leftValue: this.leftValue0,
        rightValue: this.rightValue0,
        scaleValues: [0, 2, 4, 6, 8, 10],
        scaleTextColors: [Color.Grey, Color.Orange, Color.Brown, Color.Blue, Color.Red],
        sliderNormalColor: Color.Pink,
        sliderSelectColor: Color.Red,
        minValue: 0,
        maxValue: 10,
        fractionDigits: 1
      })

      DualSliders({
        leftValue: this.leftValue1,
        rightValue: this.rightValue1,
        scaleValues: [0, 0.2, 0.4, 0.6, 0.8, 1],
        minValue: 0,
        maxValue: 1,
        fractionDigits: 2
      })

      DualSliders({
        leftValue: this.leftValue2,
        rightValue: this.rightValue2,
        scaleValues: [18, 50],
        minValue: 18,
        maxValue: 50,
        fractionDigits: 0
      })

      DualSliders({
        leftValue: this.leftValue3,
        rightValue: this.rightValue3,
        scaleValues: [18, 50],
        minValue: 10,
        maxValue: 80,
        fractionDigits: 0
      })
    }
    .width('100%')
    .height('100%')
    .padding({ top: 50 })
  }
}

1

3

3楼编辑于2024-08-09 10:00 来自天津

  • 方木

    **

    大佬 可是设置每步走多少吗? 类似于slider的step

    2024-08-09 09:21 来自北京

  • 醉心晴抒回复方木

    **

    参考链接:developer.huawei.com/consumer/cn…

    2024-08-09 15:07 来自天津

  • 哈哈

    **

    请问下如何设置两个滑块可以重叠呢,默认的值为0和0,滑动时也允许重叠

    2024-11-12 09:56 来自北京

**

大佬,请问该怎样限制选中区域呢,例如选中区域最小是10,就不可以再缩小了

4楼回复于2025-02-10 10:58 来自上海

番茄猫

**

 0 0 我好像没见过这样的组件

自己写个组件好了 0 0 

1楼回复于2024-08-06 17:39 来自上海

转载自:developer.huawei.com/consumer/cn…