鸿蒙开发案例:直尺

【1】引言(完整代码在最后面)

本文将通过一个具体的案例——创建一个横屏显示的直尺应用,来引导读者了解鸿蒙应用开发的基本流程和技术要点。

【2】环境准备

电脑系统:windows 10

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

工程版本:API 12

真机:Mate 60 Pro

语言:ArkTS、ArkUI

【3】功能分析

1. 刻度线生成

生成直尺上的刻度线是直尺应用的基础。不同的刻度线有不同的高度,这有助于用户更准确地读取长度。

1
2
3
4
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));
}

2. 刻度线编号显示

为了便于用户读取刻度,每隔一定数量的刻度线显示一个编号。这样可以减少视觉上的混乱,提高可读性。

1
2
3
4
5
6
7
8
9
10
11
12
13
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)}` : '';
  }
}

3. 屏幕方向设置

确保应用在横屏模式下显示,因为直尺更适合横向使用。

1
2
3
window.getLastWindow(getContext()).then((windowClass) => {
  windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE);
});

4. 容器高度和宽度计算

动态计算容器的高度和宽度,以适应不同设备的屏幕尺寸。

1
2
3
4
5
6
7
onCellWidthChanged() {
  this.maxRulerHeight = vp2px(this.containerWidth) / this.cellWidthInPixels / 10;
}
 
onContainerHeightChanged() {
  this.containerHeight = Math.max(this.containerHeight, 53);
}

5. 拖动手势处理

通过手势操作,用户可以更直观地调整直尺的位置和高度,提高用户体验。

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
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;
}));

6. 计数器调整

通过计数器,用户可以微调每毫米对应的像素值和选中区的距离,从而更精确地使用直尺。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
});

7. 区域变化监听

当容器的区域发生变化时,需要及时更新容器的宽度,以确保直尺的显示正确。

1
2
3
4
5
6
7
8
9
10
11
12
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
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
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"); // 设置背景颜色
  }
}

  

posted @   zhongcx  阅读(429)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示