【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));}
2. 刻度线编号显示
为了便于用户读取刻度,每隔一定数量的刻度线显示一个编号。这样可以减少视觉上的混乱,提高可读性。
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. 屏幕方向设置
确保应用在横屏模式下显示,因为直尺更适合横向使用。
window.getLastWindow(getContext()).then((windowClass) => {windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE);});
4. 容器高度和宽度计算
动态计算容器的高度和宽度,以适应不同设备的屏幕尺寸。
onCellWidthChanged() {this.maxRulerHeight = vp2px(this.containerWidth) / this.cellWidthInPixels / 10; }onContainerHeightChanged() {this.containerHeight = Math.max(this.containerHeight, 53); }
5. 拖动手势处理
通过手势操作,用户可以更直观地调整直尺的位置和高度,提高用户体验。
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. 计数器调整
通过计数器,用户可以微调每毫米对应的像素值和选中区的距离,从而更精确地使用直尺。
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. 区域变化监听
当容器的区域发生变化时,需要及时更新容器的宽度,以确保直尺的显示正确。
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;}); }
【完整代码】
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)); // 将新线添加到数组中}}// 构建UIbuild() {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"); // 设置背景颜色} }