本文档记录了Harmony开发时的某些注意事项,以避免后来的同学浪费时间排查问题。
目录
1. 全屏按钮的写法
Row() {
Column() {
Button({ type: ButtonType.Normal }) {
Text("点我")
}
.backgroundColor(Color.Blue)
.borderRadius(12)
.width("100%")
.height(40)
/*
.margin({
left: 20,
right: 20
})*/
}
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.Red)
.padding({
left: 20,
right: 20
})
.width(200)
.height(200)
.alignSelf(ItemAlign.Center)
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.align(Alignment.Center)
2. 条件编译和?号表达式
struct Index {
@State flag: boolean = false
build() {
Row() {
Button("change it")
.onClick(() => {
this.flag = !this.flag
})
Text(this.flag ? "正确的" : "错误的") // 建议采用,而不是if .. else ..
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.align(Alignment.Center)
}
}
3. 自定义文本选择器
struct Index {
@State private pickerList: TextCascadePickerRangeContent[] = []
aboutToAppear(): void {
for (let i = 2000; i < 2024; i++) {
this.pickerList.push({
text: `${i}年`,
children: this.buildMonths()
})
}
}
buildMonths(): Array<TextCascadePickerRangeContent> {
let res: TextCascadePickerRangeContent[] = []
for (let i = 0; i < 12; i++) {
res.push({ text: `${i + 1}月` })
}
return res
}
build() {
Row() {
TextPicker({ range: this.pickerList, selected: undefined })
.onChange((value, index) => {
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.align(Alignment.Center)
}
}
4. getContext
/**
* Obtains the Context object associated with a component on the page.
*
* @param { Object } component - indicate the component on the page.
* @returns { Context }
* @syscap SystemCapability.ArkUI.ArkUI.Full
* @StageModelOnly
* @crossplatform
* @atomicservice
* @since 11
*/
declare function getContext(component?: Object): Context;
此方法可无参调用,Page未加载时返回undefined,需要显式传入Context。
5. export和import
模块间文件依赖规则,在Index.ets中导出!!!否则会有依赖问题。
// Module_A Index.ets
export { TeenLaunchTask } from './src/main/ets/core/TeenLaunchTask'
export { teenEnterHomeTrigger, PopviewTeenModeGuideDialogRegistry } from './src/main/ets/in_out/TeenModeGuideDialogPage'
// Module_B xxx.ets
import { TeenLaunchTask } from '@douyin/teen_main/Index';
6. Text换行失败
当你想写下面这样一个页面,图片+文字解释,你可能会写出下面的代码。
struct Index {
build() {
Row() {
Row()
.backgroundColor(Color.Blue)
.size({
width: 40,
height: 40
})
.margin({
right: 12
})
Row() {
Text("啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊")
}
}
.width("100%")
.height("100%")
.padding({
left: 16,
right: 16
})
.justifyContent(FlexAlign.Center)
.align(Alignment.Center)
}
}
运行效果如上图所示,看起来上述写法并无不妥,为什么显示异常呢?在ArkUI中,要指定显式宽高!!!那么上述的解决方式就有两种,一是,为Text指定宽度;二是,使用RelativeContainer对齐父布局边界。
struct Index {
build() {
RelativeContainer() {
Row()
.backgroundColor(Color.Blue)
.size({
width: 40,
height: 40
})
.margin({
right: 12
})
.id("box")
.alignItems(VerticalAlign.Center)
.alignRules({
left: { anchor: "__container__", align: HorizontalAlign.Start },
top: { anchor: "__container__", align: VerticalAlign.Top },
bottom: { anchor: "__container__", align: VerticalAlign.Bottom }
})
Row() {
Text("啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊")
}
.id("text")
.alignItems(VerticalAlign.Center)
.alignRules({
left: { anchor: "box", align: HorizontalAlign.End },
right: { anchor: "__container__", align: HorizontalAlign.End },
top: { anchor: "__container__", align: VerticalAlign.Top },
bottom: { anchor: "__container__", align: VerticalAlign.Bottom }
})
.margin({
left: 16
})
}
.width("100%")
.height("100%")
.padding({
left: 16,
right: 16
})
}
}
7. @CustomDialog
没有onPageShow(),只有aboutToAppear()回调。
8. CancelablePromise
目前在抖音分享的下载功能中使用,见ShareGalleryUtil#saveVidoe。
能够取消,且设置取消回调的Promise。
type CancelableExecution<T> = (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: Any) => void,
cancel: (reason?: Any) => void
) => void
enum State { PENDING, FULFILLED, REJECTED, CANCELED}
export class CancelablePromise<T> {
private _state: State = State.PENDING
private _reason: Any
private _cancels: Function[] = []
private _promise: Promise<T>
constructor(executor: CancelableExecution<T>) {
this._promise = new Promise<T>((resolve, reject) => {
executor(
(value) => {
if (this._state === State.PENDING) {
this._state = State.FULFILLED
resolve(value)
}
},
(reason: Any) => {
if (this._state === State.PENDING) {
this._state = State.REJECTED
reject(reason)
}
},
(reason: Any) => {
this.cancel(reason)
}
)
})
}
public then(onFulfilled?: (value: T) => T | PromiseLike<T>, onRejected?: (reason: Any) => T | PromiseLike<T>) {
return this._promise.then(onFulfilled, onRejected)
}
public catch(onRejected?: (reason: Any) => T | PromiseLike<T>) {
return this._promise.catch(onRejected)
}
public onCancel(handler: Function): CancelablePromise<T> {
if (this._state === State.CANCELED) {
handler(this._reason)
} else {
this._cancels.push(handler)
}
return this
}
public cancel(reason?: Any): void {
if (this._state === State.PENDING) {
this._state = State.CANCELED
this._reason = reason
for (let handler of this._cancels) {
handler(this._reason)
}
}
}
}
9. 保存图片到相册
相册的读写权限是受限的,需要ACL申请(另在module.json中声明),且只能使用特定的相册文件获取方法。
async saveImage(context: Context, pixmap: image.PixelMap): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
let permissionManager = new EzPermissionManager(context)
permissionManager.requestPermission('ohos.permission.WRITE_IMAGEVIDEO').then(async result => {
if (result.authResults[0] === 0) {
const imagePackerApi = image.createImagePacker()
let packOpts: image.PackingOption = { format: "image/jpeg", quality: 100 }
let helper = photoAccessHelper.getPhotoAccessHelper(context)
let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpeg')
let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
imagePackerApi.packToFile(pixmap, file.fd, packOpts).then(async () => {
await fs.close(file.fd)
resolve()
}).catch(async (error: BusinessError) => {
await fs.close(file.fd)
reject(error)
})
}
}).catch((error: BusinessError) => {
reject(error)
})
})
}
10. 保存视频到相册
你可能想下载一个链接对应的视频到相册,并且已经在抖音工程看到EzDownloadCreator(对官方request.downloadFile方法的封装),于是决定试一试,然后几个小时过去了…相册路径不支持…
聪明的你又想到可以使用抖音基建封装的EzNetwork,然而,它没有下载进度的回调。
最终,你决心使用官方http库自己写一个,同时考虑到能够优雅的取消下载请求。
于是便有了下面的http+CancelablePromise的方案。
saveVideo(
context: Context,
url: string,
onProgress?: (progress: number, totalBytes: number) => void
): CancelablePromise<void> {
const httpRequest = http.createHttp()
let promise = new CancelablePromise<void>(async (resolve, reject, cancel) => {
let permissionManager = new EzPermissionManager(context)
permissionManager.requestPermission('ohos.permission.WRITE_IMAGEVIDEO').then(async result => {
if (result.authResults[0] === 0) {
let helper = photoAccessHelper.getPhotoAccessHelper(context)
let uri = await helper.createAsset(photoAccessHelper.PhotoType.VIDEO, 'mp4')
let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
let buffers = new Array<ArrayBuffer>()
httpRequest.on('dataReceive', async (data: ArrayBuffer) => {
buffers.push(data)
})
httpRequest.on('dataEnd', async () => {
for (let data of buffers) {
await fs.write(file.fd, data)
}
await fs.close(file.fd)
resolve()
})
httpRequest.on('dataReceiveProgress', async (data: http.DataReceiveProgressInfo) => {
onProgress?.(data.receiveSize / data.totalSize, data.totalSize)
})
let streamInfo: http.HttpRequestOptions = {
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER
}
httpRequest.requestInStream(url, streamInfo).then(async (data: number) => {
this.closeHttpRequest(httpRequest)
}).catch(async (error: BusinessError) => {
await fs.close(file.fd)
reject(error)
})
}
})
})
promise.onCancel((reason: Any) => {
this.closeHttpRequest(httpRequest)
})
return promise
}
private closeHttpRequest(httpRequest: http.HttpRequest) {
httpRequest.off('dataReceive')
httpRequest.off('dataReceiveProgress')
httpRequest.off('dataEnd')
httpRequest.destroy()
}
11. 保存View截图
类似的还有一些方法,可见官方文档
componentSnapshot.createFromBuilder(() => {
this.snapshotView()
}).then((pixmap: image.PixelMap) => {
})
12. FreqMap
统计出现最多值的map,支持返回max key和max Value。
import { HashMap } from '@kit.ArkTS'
export class FreqMap<K> {
map?: HashMap<K, number>
_maxKey?: K
_maxNum: number
constructor() {
this.map = new HashMap<K, number>()
this._maxKey = undefined
this._maxNum = -1
}
put(key: K) {
let num = (this.map?.get(key) ?? 0) + 1
this.map?.set(key, num)
if (num > this._maxNum) {
this._maxKey = key
this._maxNum = num
}
}
maxKey(): K | undefined {
return this._maxKey
}
maxNum(): number {
return this._maxNum
}
}
13. Pixmap算色
核心是:BGRA的存储格式
下面是求一张图片出现最多的色,也即主色。
pixmapMaxColor(pixmap: image.PixelMap, scale: number = 1.0): Promise<number> {
return new Promise<number>(async (resolve, reject) => {
try {
pixmap.scaleSync(scale, scale)
let info = pixmap.getImageInfoSync()
let buffer: ArrayBuffer = new ArrayBuffer(info.size.width * info.size.height * 4)
pixmap.readPixelsToBufferSync(buffer)
let array: Uint32Array = new Uint32Array(buffer)
let freqMap = new FreqMap<number>()
for (let pix of array) {
let r = pix & 0xff
let g = (pix & 0xff00) >> 8
let b = (pix & 0xff0000) >> 16
let color = (r << 16) + (g << 8) + b
freqMap.put(color)
}
let maxColor = freqMap.maxKey()
if (maxColor == undefined) {
reject("max color is undefined")
} else {
resolve(maxColor)
}
} catch {
reject("pixmapMaxColor error")
}
})