鸿蒙开发案例:直尺

【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" ); // 设置背景颜色 } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了