HarmonyOS 鸿蒙Next中分享一个自定义的图片滑动验证控件的实现

HarmonyOS 鸿蒙Next中分享一个自定义的图片滑动验证控件的实现 我们在功能开发的过程中,难免遇到图片验证码的需求,比如说登录、抢票等场景。通常的方案一般有两种:

  1. 获取云端图片和滑块,云端验证正确性。
  2. 端侧静态图片,完成滑动端侧验证。

下面主要介绍下第二种方案的实现:

先看效果图:

  • 准备一张验证码图片
  • 生成拼图块路径(固定一种形态,带凸起/凹陷)
function generatePuzzlePath() {
  const s = this.puzzleSize;
  const r = 10; // 圆角
  const tab = 12; // 凸起/凹陷宽度
  // 形状:上边有凸起,右边有凹陷,下边直线,左边直线
  this.puzzlePath = `
    M ${r} 0
    L ${s / 2 - tab / 2} 0
    Q ${s / 2} -${tab} ${s / 2 + tab / 2} 0
    L ${s - r} 0
    Q ${s} 0 ${s} ${r}
    L ${s} ${s / 2 - tab / 2}
    Q ${s + tab} ${s / 2} ${s} ${s / 2 + tab / 2}
    L ${s} ${s - r}
    Q ${s} ${s} ${s - r} ${s}
    L ${r} ${s}
    Q 0 ${s} 0 ${s - r}
    L 0 ${r}
    Q 0 0 ${r} 0 Z
  `;
}
  • 使用代码 随机生成拼图块位置,参考代码:
function generateRandomPuzzlePosition() {
  const margin = 8;
  const minX = margin;
  const maxX = this.containerWidth - this.puzzleSize - margin;
  const minY = margin;
  const maxY = this.containerHeight - this.puzzleSize - margin;
  this.puzzleX = Math.floor(Math.random() * (maxX - minX + 1)) + minX;
  this.puzzleY = Math.floor(Math.random() * (maxY - minY + 1)) + minY;
  this.sliderPosition = 0;
  this.isVerified = false;
}
  • 滑动条滑动
Slider({
  value: this.sliderPosition,
  min: 0,
  max: this.containerWidth - this.puzzleSize,
  step: 1
})
  .width(this.containerWidth)
  .onChange((value: number, mode: SliderChangeMode) => {
    this.sliderPosition = value;
    if (mode == SliderChangeMode.End) {
      this.verifyPosition();
    }
  })
  • 验证滑块是否对齐
function verifyPosition() {
  if (Math.abs(this.sliderPosition - this.puzzleX) <= this.tolerance) {
    this.isVerified = true;
  } else {
    this.isVerified = false;
  }
}

示例代码逻辑:

function build() {
  Column() {
    // 背景图片和拼图缺口
    Stack() {
      // 背景图片
      Image(this.backgroundImage1)
        .width(this.containerWidth)
        .height(this.containerHeight)
        .objectFit(ImageFit.Cover)
        .borderRadius(12)

      // 只在拼图块位置有效时渲染缺口和滑块
      if (this.puzzleX > 0 && this.puzzleY > 0) {
        // 拼图缺口阴影
        Path()
          .width(this.puzzleSize)
          .height(this.puzzleSize)
          .commands(this.puzzlePath)
          .fill('#80FFFFFF')
          .shadow({
            radius: 6,
            color: '#00000040',
            offsetX: 2,
            offsetY: 2
          })
          .position({ x: this.puzzleX, y: this.puzzleY })

        // 滑块
        Stack() {
          // 滑块图片内容
          Image(this.backgroundImage1)
            .width(this.containerWidth)
            .height(this.containerHeight)
            .objectFit(ImageFit.Cover)
            .translate({
              x: this.containerWidth / 2 - this.puzzleX - this.getUIContext().px2vp(this.puzzleSize) / 2,
              y: this.containerHeight / 2 - this.puzzleY - this.getUIContext().px2vp(this.puzzleSize) / 2
            })// 只显示拼图块区域
            .clip(new Path({
              commands: this.puzzlePath
            }).position({ x: this.puzzleX, y: this.puzzleY }))

          // 滑块白色边框
          Path()
            .width(this.getUIContext().px2vp(this.puzzleSize))
            .height(this.getUIContext().px2vp(this.puzzleSize))
            .commands(this.puzzlePath)
            .fill('rgba(0,0,0,0)')
            .stroke('#ffffff')
            .strokeWidth(2)
        }
        .width(this.getUIContext().px2vp(this.puzzleSize))
        .height(this.getUIContext().px2vp(this.puzzleSize))
        .position({ x: this.sliderPosition, y: this.puzzleY })
        .gesture(
          PanGesture()
            .onActionUpdate((event: GestureEvent) => {
              const newPosition = this.sliderPosition + event.offsetX;
              this.sliderPosition = Math.max(0, Math.min(newPosition, this.containerWidth - this.puzzleSize));
            })
            .onActionEnd(() => {
              this.verifyPosition();
            })
        )
      }
    }
    .width(this.containerWidth)
    .height(this.containerHeight)
    .margin({ bottom: 24 })

    // 滑动条
    Slider({
      value: this.sliderPosition,
      min: 0,
      max: this.containerWidth - this.puzzleSize,
      step: 1
    })
      .width(this.containerWidth)
      .onChange((value: number, mode: SliderChangeMode) => {
        this.sliderPosition = value;
        if (mode == SliderChangeMode.End) {
          this.verifyPosition();
        }
      })

    // 验证结果
    if (this.isVerified) {
      Text('✅ 验证成功!')
        .fontSize(18)
        .fontColor('#4CAF50')
        .margin({ top: 16 })
    } else {
      Text('向右滑动滑块完成拼图')
        .fontSize(16)
        .fontColor('#666666')
        .margin({ top: 16 })
    }
  }
  .width('100%')
  .alignItems(HorizontalAlign.Center)
}

更多关于HarmonyOS 鸿蒙Next中分享一个自定义的图片滑动验证控件的实现的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

可以的

更多关于HarmonyOS 鸿蒙Next中分享一个自定义的图片滑动验证控件的实现的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在鸿蒙Next中实现图片滑动验证控件:

  1. 使用@Component创建自定义组件
  2. 主要结构:
    • 背景图Image组件
    • 滑块Button组件
    • 缺口Image组件(带透明区域)

关键实现:

  1. 监听滑块的touch事件处理拖动逻辑
  2. 使用Canvas绘制验证缺口图案
  3. 校验逻辑:
    if(Math.abs(sliderPos - targetPos) < threshold) {
      // 验证通过
    }
    

注意点:

  1. 使用@Prop实现滑块位置数据绑定
  2. 通过@State管理验证状态
  3. 使用animateTo实现平滑动画效果

完整实现需包含随机位置生成、成功/失败回调等功能。

这是一个很不错的HarmonyOS Next端侧图片滑动验证实现方案。我来补充几点技术细节:

  1. 关于Path路径生成:
  • 使用SVG路径命令(M,L,Q等)构建拼图形状
  • 通过调整tab参数可以改变凸起/凹陷的宽度
  • 圆角半径r控制拼图块的圆滑度
  1. 性能优化建议:
  • 使用clip裁剪图片只显示拼图区域,避免绘制完整图片
  • 对于静态验证场景,可以预生成多个拼图路径缓存
  • 使用px2vp进行单位转换保证不同设备显示一致
  1. 验证逻辑改进:
  • 可以增加二次验证,要求用户在指定范围内滑动
  • 加入防抖处理,避免快速滑动时的误判
  • 可记录错误次数,超过阈值后刷新验证码
  1. 安全增强:
  • 建议对验证结果加入时间戳校验
  • 可以结合设备指纹增加验证安全性
  • 重要场景建议还是使用云端验证方案

整体实现利用了HarmonyOS的Path、Clip、Gesture等核心能力,代码结构清晰,是一个实用的验证组件实现方案。

回到顶部