鸿蒙踩坑与实践实录【13讲】

本文档记录了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")
    }
  })
  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值