鸿蒙-SeekBar

说来奇怪,鸿蒙官方并没有提供SeekBar实现,三方库也只有一个一年前的库,和常用方案也不贴合。

费劲实现了个,各位酌情取用。

GIF效果

代码

TypeScript 复制代码
/**
 * 滑动组件
 */
@ComponentV2
export struct SeekBar {
  // 最小
  @Param
  min: number = 0
  // 最大
  @Param
  max: number = 100
  // 当前
  @Param @Require
  value: number
  // value改变回调
  @Param @Require
  onChanged: (value: number) => void
  /**
   * 回调步伐
   * 满足步伐的数字会触发回调
   * 最大最小值时,不满足步伐也会触发
   */
  @Param
  step: number = 1
  // thumb大小
  @Param
  thumbSize: number = 25
  // thumb图片
  @Param
  thumbImage: Resource = $r('app.media.ic_offer_seek')
  // thumb是否展示
  @Param
  showThumb: boolean = true
  // 状态监听
  @Param
  onStatusChanged: (ing: boolean) => void = () => {
  }
  // 轨道颜色
  @Param
  trackColor: ResourceColor = '#F4F4F4'
  // 轨道高度
  @Param
  trackHeight: number = 10
  // 轨道圆角
  @Param
  trackRadius: number = 5
  // 进度颜色
  @Param
  progressColor: ResourceColor = $r('app.color.primary')
  // 进度高度
  @Param
  progressHeight: number = 10
  // 进度圆角
  @Param
  progressRadius: number = 5
  // 用户是否可拖动
  @Param
  canDrag: boolean = true
  // 宽度
  @Local
  allWidth: number = 0
  // thumb偏移
  @Local
  thumbOffset: number = 0
  // thumb缩放
  @Local
  thumbScale: number = 1
  // 进度条宽度
  @Local
  progressWidth: number = 0

  /**
   * 数据改变监听
   */
  @Monitor('allWidth','thumbWidth','min','max','value','thumbSize','showThumb')
  onValueChanged() {
    if (this.allWidth == 0) {
      return
    }
    let value = Math.min(this.max, Math.max(this.min, this.value))
    let offset = (this.allWidth - this.thumbSize) * (value - this.min) / (this.max - this.min)
    this.thumbOffset = offset ?? 0
    this.progressWidth = this.showThumb ? this.thumbOffset + (this.thumbSize / 2) :
      this.allWidth * (value - this.min) / (this.max - this.min)
  }

  build() {
    RelativeContainer() {
      Stack()
        .width('100%')
        .height(this.trackHeight)
        .backgroundColor(this.trackColor)
        .borderRadius(this.trackRadius)
        .alignRules({
          center: {
            anchor: '__container__',
            align: VerticalAlign.Center
          },
        })

      Stack()
        .height(this.progressHeight)
        .width(this.progressWidth)
        .borderRadius(this.progressRadius)
        .backgroundColor(this.progressColor)
        .alignRules({
          center: {
            anchor: '__container__',
            align: VerticalAlign.Center
          }
        })

      Image(this.thumbImage)
        .width(this.thumbSize)
        .height(this.thumbSize)
        .alignRules({
          center: {
            anchor: '__container__',
            align: VerticalAlign.Center
          },
        })
        .margin({ left: this.thumbOffset })
        .scale({
          x: this.thumbScale,
          y: this.thumbScale
        })
        .visibility(this.showThumb ? Visibility.Visible : Visibility.None)
    }
    .width('100%')
    .height(Math.max(this.progressHeight, this.trackHeight, this.showThumb ? this.thumbSize : 0))
    .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
      this.allWidth = (newValue.width as number) || 0
    })
    .onTouch((event) => {
      if (!this.canDrag) {
        return
      }

      let x = event.touches[0].x
      let offset = Math.min(Math.max(0, x), this.allWidth - this.thumbSize)
      let value =
        Math.round(this.min + (this.max - this.min) * offset / (this.allWidth - this.thumbSize))
      // 值没有改变,不触发回调
      if (value == this.value) {
        this.callBackStatus(event)
        return
      }
      if ((Math.abs(value - this.value) >= this.step && Math.abs(value - this.value) % this.step == 0)
        || value == this.min || value == this.max) {
        this.onChanged(value)
      }
      this.callBackStatus(event)
    })
  }

  private callBackStatus(event: TouchEvent) {
    if (event.type == TouchType.Down) {
      this.thumbScale = 1.2
      this.onStatusChanged(true)
    } else if (event.type == TouchType.Up) {
      this.thumbScale = 1
      this.onStatusChanged(false)
    }
  }
}

使用示例

TypeScript 复制代码
import { SeekBar } from '../component/SeekBar'

@Entry
@ComponentV2
struct SeekBarPage {
  @Local value1: number = 0
  @Local value2: number = 0

  build() {
    List() {
      ListItem() {
        Column() {
          Text('默认: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            }
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('隐藏 thumb: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            },
            showThumb: false
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('更换 thumb: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            },
            thumbImage: $r('app.media.app_icon')
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('颜色互换: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            },
            trackColor: '#006EFF',
            progressColor: '#F4F4F4'
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('步伐为10: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            },
            step: 10
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('不可拖动: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            },
            canDrag: false
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('宽椭圆形: ' + this.value1)
            .fontSize(16)
          SeekBar({
            value: this.value1,
            onChanged: (value: number) => {
              this.value1 = value
            },
            showThumb: false,
            progressHeight: 40,
            progressRadius: 20,
            trackHeight: 40,
            trackRadius: 20
          })
            .width(200)
            .margin({ top: 10 })
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

      ListItem() {
        Column() {
          Text('设置最大666,最小333: ' + this.value2)
            .fontSize(16)
          SeekBar({
            value: this.value2,
            onChanged: (value: number) => {
              this.value2 = value
            },
            max: 666,
            min: 333
          })
            .width('100%')
            .margin({ top: 10 })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ top: 5, bottom: 5 })
        .backgroundColor($r('sys.color.white'))
        .borderRadius(10)
        .padding(10)
      }

    }
    .padding(10)
    .height('100%')
    .width('100%')
    .backgroundColor('#F5F6F7')
  }

  onActionClick(text: string) {

  }
}

代码库

gitee.com/martin2wss/...

完活.

相关推荐
Van_captain1 天前
rn_for_openharmony常用组件_Breadcrumb面包屑
javascript·开源·harmonyos
御承扬1 天前
鸿蒙原生系列之动画效果(帧动画)
c++·harmonyos·动画效果·ndk ui·鸿蒙原生
行者961 天前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者961 天前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Padding 控件之空间呼吸艺术
flutter·ui·华为·harmonyos·鸿蒙系统
行者961 天前
Flutter到OpenHarmony:横竖屏自适应布局深度实践
flutter·harmonyos·鸿蒙