鸿蒙系统的自定义手势密码功能实现

简述:在最经开发鸿蒙app的过程中,需要做一个手势密码登录页面,虽然鸿蒙官网上提供了相应的组件,但是并不能满足我们的业务需求,所有我们只能自己自定义控件。先来看看我自己自定义手势密码页面的效果,大体满足了实际的业务需求。

先看下效果图

具体的实现步骤如下:

1. 确定ui容器的选择

手势密码一般都是九宫格的方式排列,很明显这里最优的选择容器是RelativeContainer

这个容器就相当于Android的RelativeLayout,用法也很相似,不清楚的朋友可以自行去官网查看

用法,这里就不一一讲解了。

大致的布局模型图如下

一般情况下手势密码都是9个点,但是不排除有二班的情况,为了防止设置的的4*4或者5*5的情况出现,所以我在布局的地方采用了一种更灵活的方式,逻辑可能有些许绕,不过静下心来也是很容易看懂的,直接上代码:


ForEach(this.ViewArr, (item: ChildViewModel, index: number) => {
  GestureLockView({
    clickImage: this.clickImage,
    defaultImage: this.defaultImage,
    mGestureLockViewWidth: this.mGestureLockViewWidth,
    mMarginBetweenLockView: this.mMarginBetweenLockView,
    childViewModel: item

  })
    .alignRules({
      top: {
        //如果是第一行,则以自身为基础,如果不是第一行,为在上一行元素的底部
        anchor: (index < this.mCount ? '__container__' : ('view_' + (index - this.mCount))),
        //如果是第一行则头部为VerticalAlign.Top,否则头部与前一行尾部对齐
        align: (index < this.mCount ? VerticalAlign.Top : VerticalAlign.Bottom)
      },
      //如果是第一列则为以自身为基础,如果不是,则以前一个元素为基础,在他的右侧
      left: {
        anchor: (index % this.mCount === 0 ? '__container__' : ('view_' + (index - 1))),
        //如果是第一列那么头对应HorizontalAlign.Start,否则头部就与前面一列的尾部对齐
        align: (index % this.mCount === 0 ? HorizontalAlign.Start : HorizontalAlign.End)
      }

    })
    .margin({
      left: this.mMarginBetweenLockView,
      top: this.mMarginBetweenLockView,
      right: this.mMarginBetweenLockView,
      bottom: this.mMarginBetweenLockView,
    })

    .id(item.viewId)
    .onAreaChange((oldValue: Area, newValue: Area) => {
      item.left = newValue.position.x?.valueOf() as number;
      item.top = newValue.position.y?.valueOf() as number;
      item.right = item.left + (newValue.width.valueOf() as number)
      item.bottom = item.top + (newValue.height.valueOf() as number)
      item.centX = ((item.left + item.right) / 2.0) + ""
      item.centY = ((item.top + item.bottom) / 2.0) + ""
    })

  ;

})

注释也详细,这里就不讲解了

2.自定义画布和path组件的摆放

通过上面的步骤我们实现了手势密码的布局,接下来我们需要实现手指与屏幕交互的功能(画连接线)。这里需要通过画布和path组件来实现来实现,首先我们需定义一个画布组件

Canvas(this.canvasContext)
  .width(this.mWidth)
  .height(this.mWidth)
  .id("Canvas")

拿到画布组件后,需要将画布组件盖在RelativeContainer上,紧着这我们需要获取一个path组件,然后将path组件盖在画布组件上面

Line()
  .startPoint([this.startPointX, this.startPointY])
  .endPoint([this.endPointX, this.endPointY])
  .strokeWidth(px2vp(5))
  .stroke(this.lineColor)
  .id("line")

这样我们所有的组件摆放动作就完成了。

3.实现手指与屏幕的交互

我们想要在屏幕上的画布上画线,我们必须要直到我们所画直线的起点坐标和终点坐标,鸿蒙系统给我们提供了组件的触摸事件,我们只需要从触摸事件的回调函数中获取手指点击屏幕的坐标值,然后再将这两个值作为path的起点。

那么怎么实现两个按钮之间的连线呢?这就需要通过画布来实现了,这里我熟悉两个特别重要的api,一个是moveTo,一个是LineTo,这两个api和Android中path中的moveTo,pathTo含义基本一样。moveTo(x,y)的意思是将路径的起点设置为这个函数的x,y中,我们需要画的路劲就需要这个x,y为起点,LineTo这个函数的意思是设计此path的终点,并且将此终点设置为下一段path的起点。熟悉了这两个重要的api,我们就可以接着就可以干活了

当手指触碰到第一个按钮的时候,记下按钮的圆心的坐标值,然后将它通过画布设置到moveTo(x,y)函数中,当手指滑动到第二手势密码按钮时,我们再将第二个手势密码的按钮的坐标传入到LineTo(x,y)中,这就完成了两个按钮之间的连线,当手指触碰到第三个按钮时,我们将第三个按钮的坐标的值设置到LineTo(x,y)中,这样就实现了第二个点和第三个点相连。以此类推,这样就实现了手势密码之间各个按钮之间的连线。

当然这这只是实现了各个按钮之间的连线,我们经常使用手势密码的时候,会看到一个引导线,也就是我们经常说的方向线。这个功能的实现我们需要用到Line组件了。

同样当手指触碰到第一个按钮的时候,记下按钮的圆心的坐标值,然后将此坐标值设置到Line组件的startPoint,当手指触碰的实时坐标传入到Line的endPoint中,这样就实现了引导线的功能。

还是直接上代码吧。

.onTouch((event: TouchEvent) => {
  let x: number = ((event.touches[0].x) < 0 ? 0 : (event.touches[0].x))
  x = (x > this.mWidth ? this.mWidth : x)
  let y: number = ((event.touches[0].y) < 0 ? 0 : (event.touches[0].y))
  y = (y > this.mWidth ? this.mWidth : y)
  switch (event.type) {
  //按下
    case TouchType.Down:
      this.reset();
      this.canvasContext.beginPath()
      this.canvasContext.lineWidth = px2vp(5)
      this.canvasContext.strokeStyle = this.lineColor
      break;
  //移动
    case TouchType.Move:
    //获取当前的点击的ChildViewModel
      let childViewModel: ChildViewModel | undefined = this.getChildIdByPos(x, y, this.ViewArr)
      if (childViewModel != undefined) {
        let passWord: number = childViewModel.password;
        this.ViewArr[childViewModel.indexView].isClick = true;
        if (!this.passwordArr.includes(passWord)) {

          this.passwordArr.push(passWord)
          this.mLastPathX = childViewModel.left / 2 + childViewModel.right / 2;
          this.mLastPathY = childViewModel.top / 2 + childViewModel.bottom / 2;

          // 设置引线的起点坐标
          this.startPointX = this.mLastPathX;
          this.startPointY = this.mLastPathY;

          if (this.passwordArr.length == 1) // 当前添加为第一个
          {

            this.canvasContext.moveTo(this.mLastPathX, this.mLastPathY);
          } else
          // 非第一个,将两者使用线连上
          {
            this.canvasContext.lineTo(this.mLastPathX, this.mLastPathY);

            this.canvasContext.stroke()
          }
        }
      }
    //设置引导线的终点坐标
      this.endPointX = (this.startPointX == 0 ? 0 : x);
      this.endPointY = (this.startPointY == 0 ? 0 : y);
      break;
  //抬起
    case TouchType.Up:
    //设置引导线的终点坐标
      this.endPointX = (this.startPointX == 0 ? 0 : this.mLastPathX);
      this.endPointY = (this.startPointY == 0 ? 0 : this.mLastPathY);

      if (this.passwordArr.length < this.minLength) {
        if (this.passwordArr.length > 0) {
          if (this.callBack != undefined) {
            this.callBack(ResultCode.fail, "至少连接" + this.minLength + "个点");
          }

        }
        this.reset();
      } else {
        let passWord: string = "";
        for (let passwordArrElement of this.passwordArr) {
          passWord += passwordArrElement;
        }
        if (this.callBack != undefined) {
          this.callBack(ResultCode.success, passWord);
        }

      }
      break;
  }

至此自定义手势密码控件的大体功能就实现了,如有错误欢迎各位大佬指点

源码地址:myCode: 鸿蒙系统自定义手势密码的实现

鸿蒙系统实现自定义分段进度条通常涉及自定义视图组件,你可以通过创建一个继承自`HarmonyOS::View`的类,并重写绘制方法来自定义样式。下面是一个简化的步骤: 1. 创建新组件:首先,在`src/com/example/myapp/customviews`目录下,新建一个名为`SegmentedProgressBar.h`和`SegmentedProgressBar.cpp`的文件,定义一个新的类。 ```cpp // SegmentedProgressBar.h class SegmentedProgressBar : public View { public: SegmentedProgressBar(const sptr<LayoutParams>& layoutParams); virtual ~SegmentedProgressBar(); // ...其他属性和方法... void setSegments(std::vector<int> segments); // 设置分段数据 private: // 绘制方法 void onDraw(DrawContext* drawContext) override; // ...其他成员变量... }; ``` 2. 实现绘图逻辑:在`onDraw()`方法中,根据`setSegments()`传递的数据绘制各个部分的进度条。 ```cpp // SegmentedProgressBar.cpp void SegmentedProgressBar::setSegments(std::vector<int> segments) { m_segments = segments; } void SegmentedProgressBar::onDraw(DrawContext* drawContext) { for (int i = 0; i < m_segments.size(); ++i) { int progress = static_cast<float>(m_segments[i]) / 100.0f * getMeasuredWidth(); // 使用drawContext绘制每个分段的矩形,并填充相应颜色 drawContext->fillRect(Rect(Point(getLeft(), getTop()), Point(progress, getHeight())), getProgressColor(i)); } } ``` 3. 在UI布局中使用自定义组件:在需要的地方使用`SegmentedProgressBar`替代标准的进度条控件,并配置所需的分段数据。 ```xml <com.example.myapp.SegmentedProgressBar android:id="@+id/custom_progress_bar" layout_width="match_parent" layout_height="wrap_content" app:segments="50,70,90" /> ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值