鸿蒙NEXT开发案例:直尺

zhongcx
发布于 2024-12-1 09:54
浏览
0收藏

鸿蒙NEXT开发案例:直尺-鸿蒙开发者社区

【1】引言
本文将通过一个具体的案例——创建一个横屏显示的直尺应用,来引导读者了解鸿蒙应用开发的基本流程和技术要点。
【2】环境准备
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【3】功能分析

  1. 刻度线生成
    生成直尺上的刻度线是直尺应用的基础。不同的刻度线有不同的高度,这有助于用户更准确地读取长度。
  for (let i = 0; i <= 15 * 10; i++) {
    let lineHeight: number = (i % 10 === 0) ? 90 : (i % 5 === 0) ? 60 : 45;
    this.rulerLines.push(new RulerLine(i, lineHeight));
  }
  • 1.
  • 2.
  • 3.
  • 4.
  1. 刻度线编号显示
    为了便于用户读取刻度,每隔一定数量的刻度线显示一个编号。这样可以减少视觉上的混乱,提高可读性。
class RulerLine {
  index: number;
  height: number;

  constructor(index: number, height: number) {
    this.index = index;
    this.height = height;
  }

  showNumber(): string {
    return this.index % 10 === 0 ? `${Math.floor(this.index / 10)}` : '';
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  1. 屏幕方向设置
    确保应用在横屏模式下显示,因为直尺更适合横向使用。
  window.getLastWindow(getContext()).then((windowClass) => {
    windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE);
  });
  • 1.
  • 2.
  • 3.
  1. 容器高度和宽度计算
    动态计算容器的高度和宽度,以适应不同设备的屏幕尺寸。
onCellWidthChanged() {
  this.maxRulerHeight = vp2px(this.containerWidth) / this.cellWidthInPixels / 10;
}

onContainerHeightChanged() {
  this.containerHeight = Math.max(this.containerHeight, 53);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  1. 拖动手势处理
    通过手势操作,用户可以更直观地调整直尺的位置和高度,提高用户体验。
Stack() {
  Circle({ height: 30, width: 30 })
    .fill("#019dfe")
    .stroke(Color.Transparent)
    .strokeWidth(3);

  Circle({ height: 40, width: 40 })
    .fill(Color.Transparent)
    .stroke("#019dfe")
    .strokeWidth(3);
}
.hitTestBehavior(HitTestMode.Block)
.padding(20)
.alignRules({
  center: { anchor: "__container__", align: VerticalAlign.Center },
  middle: { anchor: "__container__", align: HorizontalAlign.Start }
})
.gesture(PanGesture({
  fingers: 1,
  direction: PanDirection.Horizontal,
  distance: 1
}).onActionUpdate((event: GestureEvent) => {
  this.leftOffsetX = this.currentPositionX + event.offsetX / 2;
  this.containerHeight = this.originalContainerHeight - event.offsetX;
}).onActionEnd(() => {
  this.currentPositionX = this.leftOffsetX;
  this.originalContainerHeight = this.containerHeight;
}));

Stack() {
  Circle({ height: 30, width: 30 })
    .fill("#019dfe")
    .stroke(Color.Transparent)
    .strokeWidth(3);

  Circle({ height: 40, width: 40 })
    .fill(Color.Transparent)
    .stroke("#019dfe")
    .strokeWidth(3);
}
.hitTestBehavior(HitTestMode.Block)
.padding(20)
.alignRules({
  center: { anchor: "__container__", align: VerticalAlign.Center },
  middle: { anchor: "__container__", align: HorizontalAlign.End }
})
.gesture(PanGesture({
  fingers: 1,
  direction: PanDirection.Horizontal,
  distance: 1
}).onActionUpdate((event: GestureEvent) => {
  this.leftOffsetX = this.currentPositionX + event.offsetX / 2;
  this.containerHeight = this.originalContainerHeight + event.offsetX;
}).onActionEnd(() => {
  this.currentPositionX = this.leftOffsetX;
  this.originalContainerHeight = this.containerHeight;
}));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  1. 计数器调整
    通过计数器,用户可以微调每毫米对应的像素值和选中区的距离,从而更精确地使用直尺。
Counter() {
  Text(`选中区距离:${this.maxRulerHeight.toFixed(2)}厘米`).fancy();
}
.foregroundColor(Color.White)
.width(300)
.onInc(() => {
  this.containerHeight = px2vp(vp2px(this.containerHeight) + this.cellWidthInPixels / 10);
})
.onDec(() => {
  this.containerHeight = px2vp(vp2px(this.containerHeight) - this.cellWidthInPixels / 10);
});

Counter() {
  Text(`每毫米间距:${this.cellWidthInPixels.toFixed(2)}px`).fancy();
}
.foregroundColor(Color.White)
.width(300)
.onInc(() => {
  this.cellWidthInPixels += 0.01;
})
.onDec(() => {
  this.cellWidthInPixels = Math.max(0.01, this.cellWidthInPixels - 0.01);
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  1. 区域变化监听
    当容器的区域发生变化时,需要及时更新容器的宽度,以确保直尺的显示正确。
RelativeContainer() {
  Rect()
    .fill("#80019dfe")
    .borderColor("#019dfe")
    .borderWidth({ left: 1, right: 1 })
    .clip(true)
    .width("100%")
    .height("100%")
    .onAreaChange((oldArea: Area, newArea: Area) => {
      this.containerWidth = newArea.width as number;
    });
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

【完整代码】

import { window } from '@kit.ArkUI'; // 导入窗口相关的API
import { deviceInfo } from '@kit.BasicServicesKit'; // 导入设备信息相关的API

// 定义直尺线类
class RulerLine {
  index: number; // 线的索引
  height: number; // 线的高度

  constructor(index: number, height: number) {
    this.index = index; // 初始化索引
    this.height = height; // 初始化高度
  }

  // 显示线的编号
  showNumber(): string {
    return this.index % 10 === 0 ? `${Math.floor(this.index / 10)}` : ''; // 每10个线显示一个编号
  }
}

// 扩展文本样式
@Extend(Text)
function fancy() {
  .fontColor("#019dfe") // 设置字体颜色
  .fontSize(20); // 设置字体大小
}

// 定义直尺组件
@Entry
@Component
struct RulerComponent {
  @State maxRulerHeight: number = 0; // 最大直尺高度
  @State @Watch('onCellWidthChanged') cellWidthInPixels: number = 17.28; // 每毫米对应的像素
  @State textWidth: number = 80; // 文本宽度
  @State rulerLines: RulerLine[] = []; // 直尺线数组
  @State leftOffsetX: number = -300; // 左侧偏移
  @State currentPositionX: number = -300; // 当前X位置
  @State @Watch('onContainerHeightChanged') containerHeight: number = 53; // 容器高度
  @State originalContainerHeight: number = 53; // 原始容器高度
  @State @Watch('onCellWidthChanged') containerWidth: number = 0; // 容器宽度

  // 处理单元格宽度变化
  onCellWidthChanged() {
    this.maxRulerHeight = vp2px(this.containerWidth) / this.cellWidthInPixels / 10; // 更新最大直尺高度
  }

  // 处理容器高度变化
  onContainerHeightChanged() {
    this.containerHeight = Math.max(this.containerHeight, 53); // 确保容器高度不小于53
  }

  // 组件即将出现时
  aboutToAppear(): void {
    // 设置当前应用为横屏显示
    window.getLastWindow(getContext()).then((windowClass) => {
      windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE); // 设置为横屏
    });

    // 初始化直尺线
    for (let i = 0; i <= 15 * 10; i++) {
      let lineHeight: number = (i % 10 === 0) ? 90 : (i % 5 === 0) ? 60 : 45; // 根据索引设置线的高度
      this.rulerLines.push(new RulerLine(i, lineHeight)); // 将新线添加到数组中
    }
  }

  // 构建UI
  build() {
    Column() { // 创建一个列布局
      Stack() { // 创建一个堆叠布局
        Stack() { // 创建另一个堆叠布局
          ForEach(this.rulerLines, (line: RulerLine, index: number) => { // 遍历直尺线数组
            Line()// 创建一条线
              .width(1)// 设置线宽
              .height(`${line.height}px`)// 设置线高
              .backgroundColor(Color.White)// 设置线的背景颜色
              .margin({ left: `${this.cellWidthInPixels * index}px` }); // 设置线的左边距
            Text(line.showNumber())// 显示线的编号
              .fontColor(Color.White)// 设置字体颜色
              .fontSize(18)// 设置字体大小
              .width(`${this.textWidth}px`)// 设置文本宽度
              .height(`${this.textWidth}px`)// 设置文本高度
              .textAlign(TextAlign.Center)// 设置文本对齐方式
              .margin({
                left: `${this.cellWidthInPixels * index - this.textWidth / 2}px`,
                top: `${line.height}px`
              }); // 设置文本位置
          });
        }.width('100%').height('100%').align(Alignment.TopStart); // 设置堆叠布局的宽高和对齐方式

        Column({ space: 15 }) { // 创建一个列布局,设置间距
          Text(`当前设备:${deviceInfo.marketName}`).fancy(); // 显示当前设备名称
          Counter() { // 创建一个计数器
            Text(`选中区距离:${this.maxRulerHeight.toFixed(2)}厘米`).fancy(); // 显示选中区距离
          }
          .foregroundColor(Color.White) // 设置计数器字体颜色
          .width(300) // 设置计数器宽度
          .onInc(() => { // 增加计数器时的处理
            this.containerHeight = px2vp(vp2px(this.containerHeight) + this.cellWidthInPixels / 10); // 更新容器高度
          })
          .onDec(() => { // 减少计数器时的处理
            this.containerHeight = px2vp(vp2px(this.containerHeight) - this.cellWidthInPixels / 10); // 更新容器高度
          });

          Counter() { // 创建另一个计数器
            Text(`每毫米间距:${this.cellWidthInPixels.toFixed(2)}px`).fancy(); // 显示每毫米间距
          }
          .foregroundColor(Color.White) // 设置计数器字体颜色
          .width(300) // 设置计数器宽度
          .onInc(() => { // 增加计数器时的处理
            this.cellWidthInPixels += 0.01; // 增加每毫米间距
          })
          .onDec(() => { // 减少计数器时的处理
            this.cellWidthInPixels = Math.max(0.01, this.cellWidthInPixels - 0.01); // 减少每毫米间距,确保不小于0.01
          });
        }

        RelativeContainer() { // 创建一个相对布局容器
          Rect()// 创建一个矩形
            .fill("#80019dfe")// 设置填充颜色
            .borderColor("#019dfe")// 设置边框颜色
            .borderWidth({ left: 1, right: 1 })// 设置边框宽度
            .clip(true)// 启用裁剪
            .width("100%")// 设置宽度为100%
            .height("100%")// 设置高度为100%
            .onAreaChange((oldArea: Area, newArea: Area) => { // 处理区域变化
              this.containerWidth = newArea.width as number; // 更新容器宽度
            });

          Stack() { // 创建一个堆叠布局
            Circle({ height: 30, width: 30 })// 创建一个圆形
              .fill("#019dfe")// 设置填充颜色
              .stroke(Color.Transparent)// 设置边框颜色为透明
              .strokeWidth(3); // 设置边框宽度
            Circle({ height: 40, width: 40 })// 创建另一个圆形
              .fill(Color.Transparent)// 设置填充颜色为透明
              .stroke("#019dfe")// 设置边框颜色
              .strokeWidth(3); // 设置边框宽度
          }
          .hitTestBehavior(HitTestMode.Block) // 设置碰撞检测行为
          .padding(20) // 设置内边距
          .alignRules({
            // 设置对齐规则
            center: { anchor: "__container__", align: VerticalAlign.Center }, // 垂直居中
            middle: { anchor: "__container__", align: HorizontalAlign.Start } // 左对齐
          })
          .gesture(PanGesture({
            // 左侧拖动手势
            fingers: 1, // 单指拖动
            direction: PanDirection.Horizontal, // 水平拖动
            distance: 1 // 最小拖动距离
          }).onActionUpdate((event: GestureEvent) => { // 拖动更新时的处理
            this.leftOffsetX = this.currentPositionX + event.offsetX / 2; // 更新左侧偏移
            this.containerHeight = this.originalContainerHeight - event.offsetX; // 更新容器高度
          }).onActionEnd(() => { // 拖动结束时的处理
            this.currentPositionX = this.leftOffsetX; // 更新位置
            this.originalContainerHeight = this.containerHeight; // 更新原始高度
          }));

          Stack() { // 创建另一个堆叠布局
            Circle({ height: 30, width: 30 })// 创建一个圆形
              .fill("#019dfe")// 设置填充颜色
              .stroke(Color.Transparent)// 设置边框颜色为透明
              .strokeWidth(3); // 设置边框宽度
            Circle({ height: 40, width: 40 })// 创建另一个圆
              .fill(Color.Transparent)// 设置填充颜色为透明
              .stroke("#019dfe")// 设置边框颜色
              .strokeWidth(3); // 设置边框宽度
          }
          .hitTestBehavior(HitTestMode.Block) // 设置碰撞检测行为
          .padding(20) // 设置内边距
          .alignRules({
            // 设置对齐规则
            center: { anchor: "__container__", align: VerticalAlign.Center }, // 垂直居中
            middle: { anchor: "__container__", align: HorizontalAlign.End } // 右对齐
          })
          .gesture(PanGesture({
            // 右侧拖动手势
            fingers: 1, // 单指拖动
            direction: PanDirection.Horizontal, // 水平拖动
            distance: 1 // 最小拖动距离
          }).onActionUpdate((event: GestureEvent) => { // 拖动更新时的处理
            this.leftOffsetX = this.currentPositionX + event.offsetX / 2; // 更新左侧偏移
            this.containerHeight = this.originalContainerHeight + event.offsetX; // 更新容器高度
          }).onActionEnd(() => { // 拖动结束时的处理
            this.currentPositionX = this.leftOffsetX; // 更新位置
            this.originalContainerHeight = this.containerHeight; // 更新原始高度
          }));
        }
        .width(this.containerHeight) // 设置宽度
        .height("100%") // 设置高度
        .translate({ x: this.leftOffsetX }) // 使用左侧偏移
        .gesture(PanGesture({
          // 左侧拖动手势
          fingers: 1, // 单指拖动
          direction: PanDirection.Horizontal, // 水平拖动
          distance: 1 // 最小拖动距离
        }).onActionUpdate((event: GestureEvent) => { // 拖动更新时的处理
          if (event) {
            this.leftOffsetX = this.currentPositionX + event.offsetX; // 更新左侧偏移
          }
        }).onActionEnd(() => { // 拖动结束时的处理
          this.currentPositionX = this.leftOffsetX; // 更新位置
        }));
      }
    }.height('100%').width('100%') // 设置高度和宽度
    .padding({ left: 30, right: 10 }) // 设置内边距
    .backgroundColor("#181b22"); // 设置背景颜色
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.

分类
标签
收藏
回复
举报


回复
    相关推荐