# 实现Slider进度和滑块解耦

#### 问题现象

如何实现类似Slider组件，且要求滑块和进度解耦。

#### 背景知识

*   Slider：滑动条组件，通常用于快速调节设置值，如音量调节、亮度调节等应用场景。
*   DataPanel：数据面板组件，用于将多个数据占比情况使用占比图进行展示。

#### 解决方案

为了实现滑块和进度解耦，在堆叠容器中通过DataPanel组件实现进度效果，并使用Circle组件模拟可拖动的滑块。

*   在DataPanel组件中通过values属性来设置进度条的值，并绑定点击事件来更新滑块的位置selectValue。
    
    ```
    // 使用DataPanel模拟进度条
    DataPanel({ values: this.valueArr, max: 1000, type: DataPanelType.Line })
      .width(300)
      .height(10)
      .borderRadius('50%')
      // DataPanel点击事件，将滑块重置于点击位置
      .onClick((event: ClickEvent) => {
        // 判断点击位置是否超过进度条长度，+8为滑块半径，并重新计算赋值selectValue
        if ((event.x + 8) * 1000 / 300 > 1000) {
          this.selectValue = 1000;
        } else {
          this.selectValue = (event.x + 8) * 1000 / 300;
        }
      })
    ```
    
*   滑块Circle组件的位置与变量selectValue绑定，并在滑动手势中更新滑块位置。为了防止滑块脱离进度，对两侧边界进行判断。
    
    ```
    // 模拟滑块
    Circle({ width: 16, height: 16 })
      .fill('#fff')
      .borderRadius('50%')
      // 滑块的位置为selectValue在总体的占比乘以300，减8防止滑块溢出边界
      .position({ left: (this.selectValue) / 1000 * 300 - 8, top: 0 })
      .shadow({ radius: 10, color: Color.Gray })
      .gesture(
        PanGesture({ direction: PanDirection.Horizontal, distance: 1 })
          .onActionStart(() => {
            // 初始化滑块左边界坐标
            this.lastOffsetX = 0;
          })
          .onActionUpdate((even) => {
            // 在滑动手势更新回调中，通过手势坐标减去上次左边界坐标计算偏移距离，通过偏移距离在总长度（300）占比乘以1000更新selectValue
            this.selectValue = this.selectValue + (even.offsetX - this.lastOffsetX) / 300 * 1000;
            // 对滑块左边界进行判断
            if (this.selectValue < 0) {
              this.selectValue = 0;
              // 对滑块的右边界进行判断
            } else if (this.selectValue > 1000) {
              this.selectValue = 1000;
            }
            // 更新滑块左边界坐标
            this.lastOffsetX = even.offsetX;
          })
      )
    ```
    

通过上述DataPanel组件和Circle组件实现了类似Slider组件的功能，并且滑块和进度解耦。

```
@Entry
@Component
struct SliderDecouple {
  // 滑块位置值
  @State selectValue: number = 120;
  @State valueArr: number[] = [120];
  lastOffsetX: number = 0;

  build() {
    Column({ space: 10 }) {
      Stack() {
        // 使用DataPanel模拟进度条
        DataPanel({ values: this.valueArr, max: 1000, type: DataPanelType.Line })
          .width(300)
          .height(10)
          .borderRadius('50%')
          // DataPanel点击事件，将滑块重置于点击位置
          .onClick((event: ClickEvent) => {
            // 判断点击位置是否超过进度条长度，+8为滑块半径，并重新计算赋值selectValue
            if ((event.x + 8) * 1000 / 300 > 1000) {
              this.selectValue = 1000;
            } else {
              this.selectValue = (event.x + 8) * 1000 / 300;
            }
          })

        // 模拟滑块
        Circle({ width: 16, height: 16 })
          .fill('#fff')
          .borderRadius('50%')
          // 滑块的位置为selectValue在总体的占比乘以300，减8防止滑块溢出边界
          .position({ left: (this.selectValue) / 1000 * 300 - 8, top: 0 })
          .shadow({ radius: 10, color: Color.Gray })
          .gesture(
            PanGesture({ direction: PanDirection.Horizontal, distance: 1 })
              .onActionStart(() => {
                // 初始化滑块左边界坐标
                this.lastOffsetX = 0;
              })
              .onActionUpdate((even) => {
                // 在滑动手势更新回调中，通过手势坐标减去上次左边界坐标计算偏移距离，通过偏移距离在总长度（300）占比乘以1000更新selectValue
                this.selectValue = this.selectValue + (even.offsetX - this.lastOffsetX) / 300 * 1000;
                // 对滑块左边界进行判断
                if (this.selectValue < 0) {
                  this.selectValue = 0;
                  // 对滑块的右边界进行判断
                } else if (this.selectValue > 1000) {
                  this.selectValue = 1000;
                }
                // 更新滑块左边界坐标
                this.lastOffsetX = even.offsetX;
              })
          )
      }

      // 模拟进度条变化
      Button('add')
        .onClick(() => {
          // 模拟每次进度条增加50
          this.valueArr[0] = this.valueArr[0] + 50;
          // 进度条不能超越滑块
          if (this.valueArr[0] > this.selectValue) {
            this.valueArr[0] = this.selectValue;
          } else if (this.valueArr[0] < 0) {
            this.valueArr[0] = 0;
          }
        })
      Text('进度：' + Math.round((this.valueArr[0]) / 10) + '%')
    }
    .alignItems(HorizontalAlign.Center)
    .height('100%')
    .width(400)
  }
}
```