初始化
在 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 |
|
disablePresetMenu | YES/NO,默認 NO | 是否隱藏膠囊 |
設置 userAgent
HRiverMini.setUserAgent(ua) // 會拼接在默認ua后面
更新小程序
HRiverMini.updateApp(appId)
預置小程序
在工程的
rawfile
下增加nebulaPreset
和nebulapresetinfo
目錄。其中nebulaPreset
目錄下放置內置的所有小程序包,文件名為對應的appid
。nebulapresetinfo
目錄下放置customnebulapreset.json
文件,文件內容就是內置小程序的 JSON 配置內容,JSON 格式和其他平臺稍有區別,只保留 data 下內容即可。示例如下:
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" }, ]
初始化之后調用
HRiverMini.loadPresetApp
。HRiverMini.loadPresetApp((appId: string) => { // 每安裝成功一個都會回調一次并返回appId hilog.debug(1, "MiniTag", "installPreset: " + appId) })
小程序信息管理
根據
appId
獲取小程序信息。返回的信息包括appId
、version
和title
。let result: ESObject = HRiverMini.getAppInfo('1122334455667788') if (result) { // result.appId 小程序id // result.version 小程序版本 // result.title 小程序名稱 hilog.debug(1, "MiniTag", `getAppInfo: ${result.appId} ${result.version}`) }
刪除小程序信息。
HRiverMini.deleteAppInfo('1122334455667788')
刪除所有小程序信息。
HRiverMini.deleteAllAppInfo()
切面事件攔截
通過 HRiverMini
的 registerPoint
注入攔截切面,針對部分事件做監聽和攔截。
/**
* 注冊攔截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 信息。操作步驟如下:
創建
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; } }
在入口的
EntryAbilityStage
的onCreate
中注冊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)
初始化小程序后確保在
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 包括
getCurrentLocation
和moveToLocation
,需要在華為地圖官網申請使用。獲取剪貼板內容需要 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。