日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

使用 SDK

初始化

在 mPaaS 框架初始化完成之后,初始化 HRiverMini

HRiverMini.init()
重要

使用 SDK 前必須初始化 mPaaS 框架,并設置 userId 用于后續的預覽/真機調試。

mPaaS 框架初始化可參考 初始化 mPaaS,示例如下:

import AbilityStage from '@ohos.app.ability.AbilityStage';
import { MPFramework } from '@mpaas/framework';

export default class ModuleEntry extends AbilityStage {
  async onCreate() {
    MPFramework.create(this.context);
    
    MPFramework.instance.userId = 'MPTestCase'
  }
}

打開小程序

小程序的頁面路由基于 Navigation,需要在 Navigation 頁面的 aboutToAppear() 添加以下配置:

aboutToAppear() {
  HRiverMini.notifyNavigationCreate(this.context, this.pageInfos)
}

添加 Navigation:

@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()
@Builder
  PageMap(name: string, navPageIntent: Map<string, Object>) {
    AppPage(name, navPageIntent);
  }

build() {
  Navigation(this.pageInfos) {
      Column() {
      ...
      }
      .height('100%')
      .width('100%')
  }.navDestination(this.PageMap);
}

啟動小程序 API:

let startParams = new Map<string, Object>() // 啟動參數
HRiverMini.startApp("2023112713520001", startParams)

啟動小程序并跳轉到指定頁面:

let startParams = new Map<string, Object>()
startParams.set("page", "/page/component/view/view");
HRiverMini.startApp("2020012000000001", startParams)

注冊自定義 API

HRiverMini.init() 初始化之前調用以下 API:

HRiverMini.registerExtension(()=> {
      import('@mpaas/hriverminidemo/src/main/ets/component/CustomExtension')
      import('@mpaas/hriverminidemo/src/main/ets/component/CustomExtension1')
      import('@mpaas/hriverminidemo/src/main/ets/component/CustomExtension2')
    })

import 即動態引入自定義 API 的 Extension。CustomExtension 的實現如下:

import { ApiBaseExtension,
  BridgeCallback,
  defineJSAPIClass, ExtensionParameter,
  MyExtHubContext,
  registerJSAPI, required } from '@alipay/exthub'

@defineJSAPIClass(():ApiBaseExtension => {return new CustomExtension()})
export class CustomExtension extends ApiBaseExtension {
  /**
   * 自定義Api: customApi
   * @param param
   * @param context
   * @param callback
   */
  @registerJSAPI
  customApi(@required(ExtensionParameter.CallParameters) param: Record<string, Object>,
    @required(ExtensionParameter.MyExtHubContext) context: MyExtHubContext,
    @required(ExtensionParameter.BridgeCallback) callback: BridgeCallback) {
    // 參數從param讀取
    // 上下文如頁面context等從context中讀取
    // 回調給小程序使用callback

    // 調用成功后回調具體數據
    callback.sendSuccessResponse({
      data: 'apiSuccess'
    })
  }

  /**
   * 自定義Api: customApi2
   * @param param
   * @param context
   * @param callback
   */
  @registerJSAPI
  customApi2(@required(ExtensionParameter.CallParameters) param: Record<string, Object>,
    @required(ExtensionParameter.MyExtHubContext) context: MyExtHubContext,
    @required(ExtensionParameter.BridgeCallback) callback: BridgeCallback) {
    // 失敗的回調通知
    callback.sendErrorResponse(-1, "call failed msg")
  }
}

Native 通知小程序

import { EngineUtils, HRiverMiniEngine } from '@mpaas/hrivermini'

HRiverMiniEngine.sendToRender(EngineUtils.getPageFromContext(context)?.getRender() || null, 'event', {})

掃碼預覽/真機調試

方式一:直接調起掃碼。

let startParams = new Map<string, Object>()
HRiverMini.scan(getContext(this), startParams)

方式二:通過其他掃碼,打開掃碼結果。

let startParams = new Map<string, Object>()
HRiverMini.scanByUri(scanResult, startParams) // scanResult為掃碼結果的string

配置小程序包請求時間間隔

mPaaS 支持配置小程序包的請求時間間隔,可全局配置。

HRiverMini.setAsyncReqRate("{\"config\":{\"al\":\"3\",\"pr\":{\"4\":\"86400\",\"common\":\"864000\"},\"ur\":\"1800\",\"fpr\":{\"common\":\"3888000\"}},\"switch\":\"yes\"}")

其中 \"ur\":\"1800\" 是設置全局更新間隔的值,1800 為默認值,代表間隔時長,單位為秒,您可修改此值來設置您的全局小程序包請求間隔,范圍為 0 ~ 86400 秒(即 0 ~ 24 小時,0 代表無請求間隔限制)。

啟動參數

key

value

描述

query

示例:a=xx&c=xx

小程序傳參

page

示例:pages/twoPage/twoPage

指定打開的頁面并傳參

nbupdate

synctry、async,默認 async

  • synctry:強制更新版本

  • async: 不強制更新版本

disablePresetMenu

YES/NO,默認 NO

是否隱藏膠囊

設置 userAgent

HRiverMini.setUserAgent(ua) // 會拼接在默認ua后面

更新小程序

HRiverMini.updateApp(appId)

預置小程序

  1. 在工程的 rawfile 下增加 nebulaPresetnebulapresetinfo 目錄。其中 nebulaPreset 目錄下放置內置的所有小程序包,文件名為對應的 appidnebulapresetinfo 目錄下放置 customnebulapreset.json 文件,文件內容就是內置小程序的 JSON 配置內容,JSON 格式和其他平臺稍有區別,只保留 data 下內容即可。

    示例如下:

    image

    customnebulapreset.json 內容如下:

    [
    		{
    			"app_desc":"小程序示例",
    			"app_id":"9999999988888888",
    			"auto_install":1,
    			"extend_info":{
    				"launchParams":{
    					"enableTabBar":"NO",
    					"enableKeepAlive":"NO",
    					"enableDSL":"YES",
    					"nboffline":"sync",
    					"enableWK":"YES",
    					"page":"pages/index/index",
    					"tinyPubRes":"YES",
    					"enableJSC":"YES"
    				},
    				"usePresetPopmenu":"YES"
    			},
    			"fallback_base_url":"https://xxx.com/xxxx/nebula/fallback/",
    			"global_pack_url":"",
    			"installType":1,
    			"main_url":"/index.html#pages/index/index",
    			"name":"yttest",
    			"online":1,
    			"package_url":"https://xxx.com/xxx/nebula/9999999988888888_1.0.0.0.amr",
    			"patch":"",
    			"sub_url":"",
    			"version":"1.0.0.0",
    			"vhost":"https://9999999988888888.h5app.com"
    		},
        {
    			"app_desc":"小程序示例2",
    			"app_id":"9999999988888889",
    			"auto_install":1,
    			"extend_info":{
    				"launchParams":{
    					"enableTabBar":"NO",
    					"enableKeepAlive":"NO",
    					"enableDSL":"YES",
    					"nboffline":"sync",
    					"enableWK":"YES",
    					"page":"pages/index/index",
    					"tinyPubRes":"YES",
    					"enableJSC":"YES"
    				},
    				"usePresetPopmenu":"YES"
    			},
    			"fallback_base_url":"https://xxx.com/xxxx/nebula/fallback/",
    			"global_pack_url":"",
    			"installType":1,
    			"main_url":"/index.html#pages/index/index",
    			"name":"yttest",
    			"online":1,
    			"package_url":"https://xxx.com/xxx/nebula/9999999988888889_1.0.0.0.amr",
    			"patch":"",
    			"sub_url":"",
    			"version":"1.0.0.0",
    			"vhost":"https://9999999988888889.h5app.com"
    		},
    	]
  2. 初始化之后調用 HRiverMini.loadPresetApp

    HRiverMini.loadPresetApp((appId: string) => {
        // 每安裝成功一個都會回調一次并返回appId
        hilog.debug(1, "MiniTag", "installPreset: " + appId)
    })

小程序信息管理

  1. 根據 appId 獲取小程序信息。返回的信息包括 appId、versiontitle。

    let result: ESObject = HRiverMini.getAppInfo('1122334455667788')
    if (result) {
        // result.appId 小程序id
        // result.version 小程序版本
        // result.title 小程序名稱
        hilog.debug(1, "MiniTag", `getAppInfo: ${result.appId} ${result.version}`)
    }
  2. 刪除小程序信息。

    HRiverMini.deleteAppInfo('1122334455667788')
  3. 刪除所有小程序信息。

    HRiverMini.deleteAllAppInfo()

切面事件攔截

通過 HRiverMiniregisterPoint 注入攔截切面,針對部分事件做監聽和攔截。

/**
* 注冊攔截Point
* extensionName: 具體實現的Extension的name
* pointArr: 具體攔截事件的數組
**/
registerPoint(extensionName: string, pointArr: Array<string>)

返回事件攔截

事件名稱:CRV_PROTOCOL_XRiverPageBackIntercept

具體實現如下:

{ CRV_PROTOCOL_XRiverPageBackIntercept } from '@mpaas/xriverohos';

// PageInterceptExtension為自定義的切面實現,參考demo。 CRV_PROTOCOL_XRiverPageBackIntercept為返回事件的key
HRiverMini.registerPoint(PageInterceptExtension.name, [CRV_PROTOCOL_XRiverPageBackIntercept])

其中 PageInterceptExtension 實現如下:

import {
  CRV_PROTOCOL_XRiverPageBackIntercept,
  defineExtensionConstructor, Extension, ExtensionContext,
  Page,
  PageBackInterceptPoint } from '@mpaas/xriverohos';

@defineExtensionConstructor((): Extension => {
  return new PageInterceptExtension();
})

// 繼承Extension并且實現PageBackInterceptPoint
export class PageInterceptExtension extends Extension implements PageBackInterceptPoint{
  constructor() {
    super();
    //注冊CRV_PROTOCOL_XRiverPageBackIntercept的攔截事件的對應方法名,返回事件固定為interceptBackEvent
    this.registerProtocolFunction(CRV_PROTOCOL_XRiverPageBackIntercept, 'interceptBackEvent');
  }

  // 實現interceptBackEvent方法,返回true表示攔截,false表示不攔截。
  // 以下方法體實現為demo示例,實際是否攔截需要根據業務情況
  interceptBackEvent(context: ExtensionContext): boolean {
    let page = context.getCurrentNode() as Page
    if (page.isFirstPage()) {
      return true
    }

    return false
  }

}

自定義 UI

支持自定義標題欄、菜單欄、權限彈窗、小程序加載和錯誤頁。初始化通過設置 MYNavigationBarAdapter 實現類自定義標題欄、更多菜單彈窗、權限彈窗、小程序加載和錯誤頁。

TinyAdapterUtils.setProvider(MYNavigationBarAdapter.name, new DemoNavBarAdapter())

DemoNavBarAdapter.ets 實現如下:

import { CRVPage, MYNavigationBarAdapter } from '@mpaas/nebulaintegration';
import { TinyMenuState } from '@mpaas/xriverohos';
import { DemoMenuCustomDialog } from './DemoMenuCustomDialog';
import { DemoNavComponent } from './DemoNavComponent';

export class DemoNavBarAdapter extends MYNavigationBarAdapter {
  // 自定義菜單彈窗UI
  getMenuCustomDialog(): WrappedBuilder<[TinyMenuState, CustomDialogController, CRVPage]> {
    return wrapBuilder(customDialogBuilder);
  }

  // 自定義標題欄UI
  getNavBarComponent(): WrappedBuilder<[ESObject]> | undefined {
    return wrapBuilder(customNavComponentBuilder);
  }

  /** 自定義加載和錯誤頁
   * @param data: {appId: 小程序的appId, appInfo: loading的appInfo,參考EntryInfo, loadingProgress: 加載進度, errorCode: 錯誤碼, rightButtonState: 按鈕狀態數據}
   * @returns
   */
  getLoadingComponent(): WrappedBuilder<[ESObject]> | undefined {
    return wrapBuilder(customLoadingComponentBuilder);
  }

  /**
   * 自定義權限彈窗
   * @param component 頁面的component
   * @param dlgData {app: 小程序信息, scope: 權限的scope,例如scope.bluetooth ,icon: 小程序logo, title:小程序標題, desc: 彈窗內容,如:'使用你的藍牙'}
   * @param reject 拒絕的回調方法
   * @param agree  同意的回調方法
   * @returns true: 自定義彈窗; false: 使用默認彈窗
   */
  showPermissionDialog(component: Object, dlgData: ESObject, reject: Function, agree: Function): boolean {
    AUPanelManager.showPermissionFrom(component, {
      icon: dlgData.icon,
      title: dlgData.title,
      subTitle: '申請',
      content: dlgData.desc,
      subContent: '',
      checkbox: undefined,
      buttons: [
        {
          title: '拒絕', type: DialogButtonType.Cancel, action: (isChecked: boolean) => {
          reject()
        }
        },
        {
          title: '允許', type: DialogButtonType.Normal, action: () => {
          agree()
        }
        }
      ],
      info: new ObservedPermissionInfo()
    })
    return true;
  }
}

@Builder
export function customDialogBuilder(state: TinyMenuState, controller: CustomDialogController, page: CRVPage) {
  // 菜單彈窗UI實現
  DemoMenuCustomDialog({
    tinyMenuState: state,  // 菜單數據
    customDialogController: controller, // 自定義彈窗controller
    page: page // 小程序當前page,可以獲取小程序相關數據
  })
}

@Builder
export function customNavComponentBuilder(data: ESObject) {
  // 標題欄實現
  DemoNavComponent({
    needHideBackButton: data.needHideBackButton, // 是否顯示返回鍵
    navigationBarState: data.navigationBarState, // 標題欄的具體數據
    page: data.page // 小程序當前page,可以獲取小程序相關數據
  })
}


@Builder
export function customLoadingComponentBuilder(data: ESObject) {
  DemoLoadingComponent({
    appId: data.appId,
    appInfo: data.appInfo,
    loadingProgress: data.loadingProgress,
    errorCode: data.errorCode,
    rightButtonState: data.rightButtonState
  })
}

支持自定義標題欄

DemoNavComponent.ets 實現如下:

import { AUCustomIcon, AUIcon, IconFontKey } from '@mpaas/antui'
import { CRVPage } from '@mpaas/nebulaintegration'
import { CapsuleState, FrontColor, NavigationBarState, NavigationBarUtils } from '@mpaas/xriverohos'

@Component
export struct DemoNavComponent {

  page ?: CRVPage  // 當前page

  needHideBackButton: boolean = false; // 是否顯示返回鍵

  @ObjectLink navigationBarState: NavigationBarState // 標題欄數據

  aboutToAppear(): void {
  }

  aboutToDisappear() {
  }

  build() {
      Row() {
        // Back button
        if(!this.needHideBackButton) {
          Button({
            type: ButtonType.Normal,
            stateEffect: true
          }) {
            AUIcon({
              icon: IconFontKey.ICONFONT_BACK,
              fontSize: 22,
              fontColor: this.navigationBarState.backButtonIconColor
            })
              .margin({
                top: 11,
                right: 2,
                bottom: 11,
                left: 0
              })
              .onAreaChange((oldValue: Area, newValue: Area): void => {
                this.navigationBarState.backButtonIconArea = newValue
              })
          }
          .visibility((this.navigationBarState.backButtonVisibility === 0) ? Visibility.Visible :
          Visibility.None)
          .backgroundColor('#00000000')
          .margin({
            top: 0,
            right: 0,
            bottom: 0,
            left: 8
          })
          .onClick((event: ClickEvent) => {
            // 返回鍵點擊
            if (this.navigationBarState.backButtonOnClickListener !== undefined) {
              this.navigationBarState.backButtonOnClickListener(event)
            }
          })
          .onAreaChange((oldValue: Area, newValue: Area): void => {
            this.navigationBarState.backButtonInteractiveArea = newValue
          })
        }

        // Left close button
        Button({
          type: ButtonType.Normal,
          stateEffect: true
        }) {
          AUIcon({
            icon: IconFontKey.ICONFONT_AD_CLOSE,
            fontSize: 22,
            fontColor: this.navigationBarState.leftCloseButtonIconColor
          })
            .margin({ top: 11, right: 5, bottom: 11, left: 5 })
        }
        .visibility((this.navigationBarState.leftCloseButtonVisibility === 0) ? Visibility.Visible : Visibility.None)
        .backgroundColor('#00000000')
        .margin({ top: 0, right: 0, bottom: 0, left: 3 })
        .onClick((event) => {
          // 關閉按鈕
          if (this.navigationBarState.leftCloseButtonOnClickListener !== undefined) {
            this.navigationBarState.leftCloseButtonOnClickListener(event)
          }
        })

        // Home button
        Button({
          type: ButtonType.Normal,
          stateEffect: true
        }) {
          AUCustomIcon({
            text: "\ue67d",
            fontSrc: $rawfile('tiny_iconfont.ttf'),
            fontSize: 22,
            fontColor: this.navigationBarState.homeButtonIconColor
          })
            .margin({ top: 11, right: 2, bottom: 11, left: 2 })
            .onAreaChange((oldValue: Area, newValue: Area): void => {
              this.navigationBarState.homeButtonIconArea = newValue
            })
        }
        .visibility((this.navigationBarState.homeButtonVisibility === 0) ? Visibility.Visible : Visibility.None)
        .backgroundColor('#00000000')
        .margin({ top: 0, right: 0, bottom: 0, left: 8 })
        .onClick((event) => {
          // 回到首頁按鈕點擊
          if (this.navigationBarState.homeButtonOnClickListener !== undefined) {
            this.navigationBarState.homeButtonOnClickListener(event)
          }
        })
        .onAreaChange((oldValue: Area, newValue: Area): void => {
          this.navigationBarState.homeButtonInteractiveArea = newValue
        })

        // Title
        Row() {

          Column() {
            // Title text
            Text(this.navigationBarState.titleText)
              .visibility((this.navigationBarState.titleVisibility === 0) ? Visibility.Visible : Visibility.None)
              .fontSize(18)
              .fontStyle(FontStyle.Normal)
              .fontColor(this.navigationBarState.titleTextColor)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
                // .textOverflow({ overflow: TextOverflow.Clip })
              .maxLines(1)
              .onClick((clickEvent: ClickEvent): void => {
                // 標題點擊
                if (this.navigationBarState.titleOnClickListener !== undefined) {
                  this.navigationBarState.titleOnClickListener(clickEvent)
                }
              })
          }
          .alignItems(HorizontalAlign.Start)

        }
        .width(0)
        .layoutWeight(1)
        .margin({
          top: 0,
          right: 0,
          bottom: 0,
          left: ((this.navigationBarState.backButtonVisibility === 1)
            && (this.navigationBarState.leftCloseButtonVisibility === 1)
            && (this.navigationBarState.homeButtonVisibility === 1)) ? 16 : 6
        })

        // // Placeholder
        // Blank()
        //   .layoutWeight(1)

        // Right buttons
        if (this.navigationBarState.capsuleState.visibility === 0) { // WTF: Conditional rendering here is not working...
          // Capsule style
          RightButtonComponent({
            capsuleState: this.navigationBarState.capsuleState
          })
            .animation({ duration: 300 })
        }
      }
      .visibility((this.navigationBarState.visibility === 0) ? Visibility.Visible : Visibility.None)
      .width('100%')
      .height('100%')
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.Start)
      .backgroundColor(NavigationBarUtils.alterColorWithAlpha(
        this.navigationBarState.backgroundColor,
        this.navigationBarState.backgroundAlpha))
      .hitTestBehavior(this.navigationBarState.penetrable ? HitTestMode.Transparent : HitTestMode.Default)
      .borderStyle(BorderStyle.Solid)
      .borderWidth({
        top: 0,
        right: 0,
        bottom: (this.navigationBarState.bottomLineVisibility === 0) ? '1px' : 0,
        left: 0
      })
      .borderColor(NavigationBarUtils.alterColorWithAlpha(
        this.navigationBarState.bottomLineColor,
        this.navigationBarState.bottomLineAlpha))
    }

}

@Component
export struct RightButtonComponent {

  aboutToAppear(): void {
  }

  @ObjectLink capsuleState: CapsuleState

  build() {
    // Capsule with more and close buttons
    Row() {
      // More button
      AUIcon({
        icon: this.capsuleState.moreButtonIconfont as IconFontKey,
        fontSize: 22,
        fontColor: (this.capsuleState.frontColor === FrontColor.Black) ? '#FF333333' : '#FFFFFFFF'
      })
        .visibility((this.capsuleState.moreButtonVisibility === 0) ? Visibility.Visible : Visibility.None)
        .margin({
          top: '4vp',
          right: '11vp',
          bottom: '4vp',
          left: '11vp'
        })
        .onClick((clickEvent: ClickEvent) => {
          // 更多菜單點擊
          if (this.capsuleState.moreButtonOnClickListener !== undefined) {
            this.capsuleState.moreButtonOnClickListener(clickEvent)
          }
        })

      // Divider
      Row()
        .borderStyle(BorderStyle.Solid)
        .borderWidth('1px')
        .borderColor('#1A000000')
        .width('1px')
        .height('22vp')

      // Close button
      AUIcon({
        icon: this.capsuleState.closeButtonIconfont as IconFontKey,
        fontSize: 22,
        fontColor: (this.capsuleState.frontColor === FrontColor.Black) ? '#FF333333' : '#FFFFFFFF'
      })
        .visibility((this.capsuleState.closeButtonVisibility === 0) ? Visibility.Visible : Visibility.None)
        .margin({
          top: '4vp',
          right: '11vp',
          bottom: '4vp',
          left: '11vp'
        })
        .onClick((clickEvent: ClickEvent) => {
          // 關閉按鈕點擊
          if (this.capsuleState.closeButtonOnClickListener !== undefined) {
            this.capsuleState.closeButtonOnClickListener(clickEvent)
          }
        })
    }
    .visibility((this.capsuleState.visibility === 0) ? Visibility.Visible : Visibility.Hidden)
    .borderStyle(BorderStyle.Solid)
    .borderWidth('1px')
    .borderColor('#1A000000')
    .borderRadius('10000px')
    .backgroundColor(this.capsuleState.frontColor === FrontColor.White ? '#16000000' : '#00000000')
    .margin({
      top: '9vp',
      right: '4vp',
      bottom: '9vp',
      left: 0
    })
    .onAreaChange((oldValue: Area, newValue: Area): void => {
      this.capsuleState.capsuleArea = newValue
    })

  }

}

支持自定義菜單

DemoMenuCustomDialog.ets 實現如下:

import { TinyMenuButtonState, TinyMenuState } from '@mpaas/xriverohos'
import { window } from '@kit.ArkUI'
import { AUCustomIcon } from '@mpaas/antui'
import { CRVPage } from '@mpaas/nebulaintegration'

/**
 * The tiny menu dialog.
 */
@CustomDialog
export struct DemoMenuCustomDialog {

  @ObjectLink tinyMenuState: TinyMenuState

  customDialogController?: CustomDialogController

  @State isFullScreen: boolean = false

  @State paddingBottom: Length = 0

  page?: CRVPage

  aboutToAppear(): void {
    window.getLastWindow(getContext(this)).then((win: window.Window) => {
      this.isFullScreen = win.getWindowProperties().isLayoutFullScreen
      let area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)
      if (area.visible && area.bottomRect.height > 0) {
        this.paddingBottom = px2vp(area.bottomRect.height)
      }
    })
  }

  build() {
    Column() {
      Row() {
        Image(this.tinyMenuState.appIconImageUrl)
          .width((this.tinyMenuState.appIconImageUrl && this.tinyMenuState.appIconImageUrl.length > 0) ? '35vp' : '2vp')
          .height('35vp')
          .borderRadius('50vp')
          .borderWidth('0px')
          .margin({
            top: '18vp',
            right: '8vp',
            left: '16vp',
            bottom: '18vp',
          })

        Text(this.tinyMenuState.appName)
          .fontSize(16)
          .fontStyle(FontStyle.Normal)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
      }
      .width('100%')
      .alignItems(VerticalAlign.Center)

      // Divider
      Row() {
        Row()
          .width('100%')
          .height('1px')
          .backgroundColor('#cccccc')
      }
      .width('100%')
      .margin({
        top: 0,
        right: '16vp',
        bottom: 0,
        left: '16vp'
      })

      // Top tiny menu buttons
      Row() {
        ForEach(
          this.tinyMenuState.tinyMenuButtonStateArrayTop,
          (tinyMenuButtonState: TinyMenuButtonState, index: number) => {
            TinyMenuButtonComponent({
              tinyMenuButtonState: tinyMenuButtonState,
              menuButtonOnClickListener: new MenuButtonOnClickListener((mid: string): void => {
                // Close this dialog first
                this.customDialogController?.close()

                // Trigger click event logic
                if (this.tinyMenuState.menuButtonOnClickListener) {
                  this.tinyMenuState.menuButtonOnClickListener(mid)
                }
              })
            })
          })
      }
      .width('100%')
      .height('95vp')

      // Bottom tiny menu buttons
      Scroll(new Scroller()) {
        Row() {
          ForEach(
            this.tinyMenuState.tinyMenuButtonStateArrayBottom,
            (tinyMenuButtonState: TinyMenuButtonState, index: number) => {
              TinyMenuButtonComponent({
                tinyMenuButtonState: tinyMenuButtonState,
                menuButtonOnClickListener: new MenuButtonOnClickListener((mid: string): void => {
                  // Close this dialog first
                  this.customDialogController?.close()

                  // Trigger click event logic
                  if (this.tinyMenuState.menuButtonOnClickListener) {
                    this.tinyMenuState.menuButtonOnClickListener(mid)
                  }
                })
              })
            })
        }
        .height('95vp')
      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
      .align(Alignment.Start)

      Text('取消')
        .fontSize(18)
        .fontStyle(FontStyle.Normal)
        .fontColor('ff333333')
        .backgroundColor('#FFFFFF')
        .width('100%')
        .height('57vp')
        .textAlign(TextAlign.Center)
        .onClick((event: ClickEvent) => {
          this.customDialogController?.close()
        })

      Row()
        .width('100%')
        .height(this.isFullScreen ? this.paddingBottom : 0)
        .backgroundColor('#FFFFFF')
    }
    .width('100%')
    .backgroundColor('#fff5f4f3')
    .borderRadius({
      topLeft: '12vp',
      topRight: '12vp',
      bottomLeft: 0,
      bottomRight: 0
    })
  }

}

/**
 * Menu button in the tiny menu.
 */
@Component
struct TinyMenuButtonComponent {

  @ObjectLink tinyMenuButtonState: TinyMenuButtonState

  @ObjectLink menuButtonOnClickListener: MenuButtonOnClickListener

  build() {
    Column() {
      // Icon
      Row() {
        // Image
        Image(this.tinyMenuButtonState.image)
          .width('26vp')
          .height('26vp')
          .visibility(this.tinyMenuButtonState.image ? Visibility.Visible : Visibility.None)
          .objectFit(ImageFit.Contain)

        // Iconfont
        AUCustomIcon({
          text: this.tinyMenuButtonState.iconfont,
          fontSrc: $rawfile('tiny_iconfont.ttf'),
          fontSize: 26,
          fontColor: this.tinyMenuButtonState.iconfontColor
        })
          .visibility(this.tinyMenuButtonState.image ? Visibility.None : Visibility.Visible)
          .align(Alignment.Center)
      }
      .width('45vp')
      .height('45vp')
      .backgroundColor('#ffffff')
      .borderRadius('10vp')
      .borderStyle(BorderStyle.Solid)
      .borderWidth(0)
      .alignItems(VerticalAlign.Center)
      .justifyContent(FlexAlign.Center)

      // Title
      Text(this.tinyMenuButtonState.title)
        .fontColor('#333333')
        .fontSize('10vp')
        .margin({
          top: '2vp'
        })
        .maxLines(2)
        .ellipsisMode(EllipsisMode.END)
    }
    .width('65vp')
    .justifyContent(FlexAlign.Center)
    .onClick((clickEvent: ClickEvent): void => {
      if (this.menuButtonOnClickListener.onClickListener) {
        this.menuButtonOnClickListener.onClickListener(this.tinyMenuButtonState.mid)
      }
    })
  }

}

@Observed
class MenuButtonOnClickListener {

  public onClickListener: ((mid: string) => void) | undefined

  public constructor(onClickListener: ((mid: string) => void) | undefined) {
    this.onClickListener = onClickListener
  }

}

支持自定義加載和錯誤頁

DemoLoadingComponent.ets 實現參考如下:

import { EntryInfo, RightButtonState } from '@mpaas/xriverohos';

type AnimationStep = () => void;

@Component
export struct DemoLoadingComponent {
  @State rightButtonState: RightButtonState = new RightButtonState();
  appId?: string = '' // 小程序id
  @Prop appInfo?: EntryInfo; // 加載信息
  @Prop loadingProgress: number = 0; // 加載進度
  @Prop errorCode: number = 0; // 錯誤碼,非0表示錯誤
  
  @State progressRotateOptions: RotateOptions = {
    angle: 0,
    centerX: '50%',
    centerY: '50%',
  }
  private rotateAnimationStep1?: AnimationStep;
  private rotateAnimationStep2?: AnimationStep;
  private rotateAnimationStep3?: AnimationStep;

  build() {
      Column() {
        Row() {
          RightButtonComponent({
            rightButtonState: this.rightButtonState
          })
            .margin({
              top: 0,
              right: 12,
              bottom: 0,
              left: 0
            })
        }.width('100%').justifyContent(FlexAlign.End)

        Row().height('20%')
        Stack() {
          Progress({ value: this.loadingProgress, total: 100, type: ProgressType.Ring })
            .width(55)
            .height(55)
            .color(this.errorCode <= 0 ? 0x1677ff : 0xdddddd)
            .backgroundColor(0xdddddd)
            .style({ strokeWidth: 1 })
            .rotate(this.progressRotateOptions)
          Image(this.appInfo?.iconUrl).alt($r('app.media.loading_page_icon')).width(40).height(40).borderRadius(20)
        }

        Row().height(18)
        Text(this.appInfo?.title).fontColor(0x333333).fontSize(18).width('100%').textAlign(TextAlign.Center)

        if (this.errorCode > 0) {
          Row().height(21)
          Text('網絡不給力').fontColor(0x333333).fontSize(20).width('100%').textAlign(TextAlign.Center)

          Row().height(10)
          Text('請稍后再試').fontColor(0xaaaaaa).fontSize(14).width('100%').textAlign(TextAlign.Center)
        }
      }
      .width('100%')
  }

  aboutToAppear(): void {
    // rotate animation config
    this.rotateAnimationStep1 = this.generateAnimateStep(
      this.generateAnimateParam(() => {
        this.rotateAnimationStep2?.()
      }),
      120
    )

    this.rotateAnimationStep2 = this.generateAnimateStep(
      this.generateAnimateParam(() => {
        this.rotateAnimationStep3?.()
      }),
      240
    )

    this.rotateAnimationStep3 = this.generateAnimateStep(
      this.generateAnimateParam(() => {
        // reset rotateOptions
        this.progressRotateOptions = {
          angle: 0,
          centerX: '50%',
          centerY: '50%',
        }
        // repeat animation step
        if (this.errorCode <= 0) {
          this.rotateAnimationStep1?.()
        }
      }),
      360
    )

    setTimeout(() => {
      this.rotateAnimationStep1?.()
    }, 1000)
  }

  aboutToDisappear(): void {
  }

  private generateAnimateStep(value: AnimateParam, angle: number): AnimationStep {
    return () => {
      animateTo(value, () => {
        this.progressRotateOptions = {
          angle: angle,
          centerX: '50%',
          centerY: '50%',
        }
      })
    }
  }

  private generateAnimateParam(event: () => void): AnimateParam {
    return {
      duration: 300,
      tempo: 1,
      curve: Curve.Linear,
      iterations: 1,
      playMode: PlayMode.Normal,
      onFinish: () => {
        setTimeout(event,5);
      }
    }
  }
}

@Component
export struct RightButtonComponent {

  @ObjectLink rightButtonState: RightButtonState

  build() {
    Button({
      type: ButtonType.Normal,
      stateEffect:false
    }) {
    }
    .visibility(Visibility.Visible)
    .backgroundColor('#00000000')
    .onClick((event) => {
      if (this.rightButtonState.onClickListener !== undefined) {
        this.rightButtonState.onClickListener(event);
      }
    })
  }

}
import { AUDialogManager } from "@mpaas/antui";
import { PermissionAdapter, PermissionType } from "@mpaas/mpaas_permission_fortress";
import { common, Want } from "@kit.AbilityKit";

export class DemoPermissionAdapter extends PermissionAdapter {
  // type: 表示申請的權限類型,具體值參考文檔后續部分,close:回調方法,彈窗取消后需要調用,goToSettings參數:true表示跳轉設置頁面并消失 false表示直接消失
  showGuideDialog(type: PermissionType, close: (goToSettings: boolean) => void): boolean {
    // 自定義彈窗邏輯,以下只是示例
    AUDialogManager.showNotice({
      onClose: () => {
        close(false) // 回調
      },
      title: '權限申請 ' + type, // 標題,根據業務情況
      message: '',  // 彈窗內容
      buttons: [{
        title: $r('app.string.go_to_set'),
        action: () => {

          close(true) // 回調

          let context = getContext(this) as common.UIAbilityContext;
          let want: Want = {
            bundleName: 'com.huawei.hmos.settings',
            abilityName: 'com.huawei.hmos.settings.MainAbility',
            uri: 'application_info_entry',
            parameters: { pushParams: '' }
          };
          context.startAbility(want).then((data) => {
            console.info('前往設置授權頁面');
          })
        }
      }]
    })
    return true
  }
}

PermissionType 類型:

export declare enum PermissionType {
    None = 0,
    Location = 1, // 定位
    Location_Background = 2,
    Location_Approximately = 3,
    Camera = 4, // 相機
    MicroPhone = 5, // 錄音
    BlueTooth = 6, // 藍牙
    Health_Read = 7, // 健康數據
    Notification = 8, // 通知
    Pasteboard = 9, // 剪切板
    Calendar = 10, // 日歷
    Media = 11, // 多媒體
    Photo_Write = 12, // 相冊寫
    Photo = 13, // 相冊讀
    Contacts = 14, // 通訊錄
    Carrier = 15, // 運營商
    Vibrate = 16 // 震動
}

自定義權限引導彈窗

定位等系統權限被禁止后,下次調用相應功能會有引導彈窗提示,可以參考以下方式自定義引導彈窗。

啟動完成初始化之后設置自定義 provider

import { PermissionAdapter, PermissionAdapterUtils } from '@mpaas/mpaas_permission_fortress';
...
...


PermissionAdapterUtils.setProvider(PermissionAdapter.name, new DemoPermissionAdapter())

DemoPermissionAdapter.ets 實現如下:

import { AUDialogManager } from "@mpaas/antui";
import { PermissionAdapter, PermissionType } from "@mpaas/mpaas_permission_fortress";
import { common, Want } from "@kit.AbilityKit";

export class DemoPermissionAdapter extends PermissionAdapter {
  // type: 表示申請的權限類型,具體值參考文檔后續部分,close:回調方法,彈窗取消后需要調用,goToSettings參數:true表示跳轉設置頁面并消失 false表示直接消失
  showGuideDialog(type: PermissionType, close: (goToSettings: boolean) => void): boolean {
    // 自定義彈窗邏輯,以下只是示例
    AUDialogManager.showNotice({
      onClose: () => {
        close(false) // 回調
      },
      title: '權限申請 ' + type, // 標題,根據業務情況
      message: '',  // 彈窗內容
      buttons: [{
        title: $r('app.string.go_to_set'),
        action: () => {

          close(true) // 回調

          let context = getContext(this) as common.UIAbilityContext;
          let want: Want = {
            bundleName: 'com.huawei.hmos.settings',
            abilityName: 'com.huawei.hmos.settings.MainAbility',
            uri: 'application_info_entry',
            parameters: { pushParams: '' }
          };
          context.startAbility(want).then((data) => {
            console.info('前往設置授權頁面');
          })
        }
      }]
    })
    return true
  }
}

PermissionType 類型:

export declare enum PermissionType {
    None = 0,
    Location = 1, // 定位
    Location_Background = 2,
    Location_Approximately = 3,
    Camera = 4, // 相機
    MicroPhone = 5, // 錄音
    BlueTooth = 6, // 藍牙
    Health_Read = 7, // 健康數據
    Notification = 8, // 通知
    Pasteboard = 9, // 剪切板
    Calendar = 10, // 日歷
    Media = 11, // 多媒體
    Photo_Write = 12, // 相冊寫
    Photo = 13, // 相冊讀
    Contacts = 14, // 通訊錄
    Carrier = 15, // 運營商
    Vibrate = 16 // 震動
}

支持延遲初始化

隱私協議需求下,需要支持延遲初始化。延遲初始化需要在初始化時注入首頁 UIAbility 信息。操作步驟如下:

  1. 創建 HomeAbilityContext 類。

    import { common } from '@kit.AbilityKit';
    import { window } from '@kit.ArkUI';
    
    export class HomeAbilityContext {
      private mAbilityContext?: common.UIAbilityContext;
      private mNavPathStack?: NavPathStack;
      private mWindowStage?: window.WindowStage;
    
      constructor(abilityContext?: common.UIAbilityContext) {
        this.mAbilityContext = abilityContext;
      }
    
      setWindowStage(windowStage?: window.WindowStage) {
        this.mWindowStage = windowStage;
      }
    
      getWindowStage() {
        return this.mWindowStage;
      }
    
      setNavPathStack(navPathStack?: NavPathStack) {
        this.mNavPathStack = navPathStack;
        if (this.mAbilityContext) {
          this.mAbilityContext.eventHub.emit(navPathStack ? "FRAMEWORK_EVENT_NAV_PATH_STACK_ATTACH"
            : "FRAMEWORK_EVENT_NAV_PATH_STACK_DETACH");
        }
      }
    
      getNavPathStack(): NavPathStack | undefined {
        return this.mNavPathStack;
      }
    }
  2. 在入口的 EntryAbilityStageonCreate 中注冊 abilityLifecycle,并緩存 TopUIAbility

    let abilityLifecycleCallback = {
          onAbilityCreate(ability: UIAbility) {
            console.log(ability + ' onAbilityCreate.');
            setTopAbility(ability)
          },
    
          onAbilityDestroy(ability: UIAbility) {
            if (getTopAbility() === ability) {
              setTopAbility(undefined)
            }
    
          },
    
          onAbilityForeground(ability: UIAbility) {
            console.log(ability + ' onAbilityForeground.');
            setTopAbility(ability)
          },
    
          onAbilityBackground(ability: UIAbility) {
            console.log(ability + ' onAbilityBackground.');
          },
    
        } as AbilityLifecycleCallback;
        this.context.getApplicationContext().getApplicationContext().on("abilityLifecycle", abilityLifecycleCallback)
  3. 初始化小程序后確保在 HRiverMini.notifyNavigationCreate 之前注入首頁 context。

    let applicationManager: ESObject = Framework.microApplicationContext["applicationManger"]
        if (applicationManager) {
          applicationManager["mTopAbility"] = getTopAbility() // 獲取第二步緩存的TopUIAbility
          if (applicationManager["mAbilityInfoMap"]) {
            let map: Map<common.UIAbilityContext, ESObject> = applicationManager["mAbilityInfoMap"] as Map<common.UIAbilityContext, ESObject>
            let homeContext = new HomeAbilityContext(this.context)
            homeContext.setWindowStage(getWindowStage())
            map.set(this.context, homeContext)
          }
        }

當前版本組件的特殊說明

  • 地圖組件目前基于華為地圖,支持的 API 包括 getCurrentLocationmoveToLocation,需要在華為地圖官網申請使用。

  • 獲取剪貼板內容需要 App 申請 ohos.permission.READ_PASTEBOARD 權限,該權限為 ACL 權限。

當前版本不支持的組件和 API

  • 文件 API:獲取文件信息、獲取文件列表、移除文件、刪除文件

  • canvas2

  • 直播

  • 聯系人

  • 隱藏鍵盤

地圖組件支持情況

地圖組件支持的 API

  • latitude

  • longitude

  • scale

  • markers

  • polyline

  • circles

  • polygon

  • include-points

地圖組件不支持的 API

  • map 高級定制渲染:marker 的 customCallout 僅支持 type=2。

  • style 僅支持 type=3 的樣式渲染,其他 type 不支持。

  • onRegionChange 的 type 僅支持 end,不支持 start。