【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)); // 將新線新增到陣列中 } } // 構建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"); // 設定背景顏色 } }