From 99aaff687670a26c82c0dfa7b1e4a8438eb12e32 Mon Sep 17 00:00:00 2001 From: DevWiki Date: Tue, 2 Apr 2024 15:01:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20TitleBar=20=E5=92=8C=20Ind?= =?UTF-8?q?ex=20=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/ets/pages/Index.ets | 79 +++++++-- app/src/main/module.json5 | 5 + common_ui/Index.ets | 8 +- common_ui/src/main/ets/component/TitleBar.ets | 150 +++++++++++------- .../src/main/ets/component/web/WebScript.ets | 15 ++ .../src/main/ets/component/web/WebView.ets | 68 ++++++++ common_ui/src/main/ets/event/Emitter.ets | 110 +++++++++++++ common_ui/src/main/ets/event/EventKey.ets | 5 + common_ui/src/main/ets/utils/Calc.ets | 3 - common_ui/src/main/ets/utils/CommonRes.ets | 17 ++ common_ui/src/main/module.json5 | 7 +- .../main/resources/base/media/ic_refresh.svg | 1 + 12 files changed, 396 insertions(+), 72 deletions(-) create mode 100644 common_ui/src/main/ets/component/web/WebScript.ets create mode 100644 common_ui/src/main/ets/component/web/WebView.ets create mode 100644 common_ui/src/main/ets/event/Emitter.ets create mode 100644 common_ui/src/main/ets/event/EventKey.ets delete mode 100644 common_ui/src/main/ets/utils/Calc.ets create mode 100644 common_ui/src/main/ets/utils/CommonRes.ets create mode 100644 common_ui/src/main/resources/base/media/ic_refresh.svg diff --git a/app/src/main/ets/pages/Index.ets b/app/src/main/ets/pages/Index.ets index 374b9eb..7fdd1bb 100644 --- a/app/src/main/ets/pages/Index.ets +++ b/app/src/main/ets/pages/Index.ets @@ -1,24 +1,79 @@ -import { TitleBar } from '@devwiki/common_ui/src/main/ets/component/TitleBar'; -import { ComposeTitleBar } from '@ohos.arkui.advanced.ComposeTitleBar'; -import { EditableTitleBar } from '@ohos.arkui.advanced.EditableTitleBar'; -import { SelectTitleBar } from '@ohos.arkui.advanced.SelectTitleBar'; +import { WebView, ComponentConst, CommonRes, TitleBar, WebViewController } from '@devwiki/common_ui'; @Entry @Component struct Index { - @State message: string = 'Hello World'; - onLeftClicked(event: ClickEvent) { - this.message = "TitleBar LeftClicked"; + @State viewModel: IndexViewModel = new IndexViewModel(); + private webViewController?: IndexWebViewController; + + aboutToAppear(): void { + this.webViewController = new IndexWebViewController(this.viewModel); + } + + onTitleBarLeftClick(event: ClickEvent) { + this.getUIContext().getRouter().back(); + } + + onTitleBarRightClick(event: ClickEvent) { + this.webViewController?.refresh(); } build() { Column() { - TitleBar({onLeftClicked: this.onLeftClicked}).height(48); - Divider(); + RelativeContainer() { + TitleBar({ + title: this.viewModel.pageTitle, + onLeftClicked: this.onTitleBarLeftClick, + rightIcon: CommonRes.getIconRefresh(), + // 必须这么写 onTitleBarRightClick内部的代码才执行, + // 直接 onRightClicked: this.onTitleBarRightClick 这么写, 代码不执行 + onRightClicked: (event: ClickEvent) => { this.onTitleBarRightClick(event)}, + }) + .width('100%') + .alignRules({ + top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top }, + left: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.Start } + }).id("title_bar"); - Text(this.message); - } - .height('100%') + Divider().alignRules({ + top: { anchor: "title_bar", align: VerticalAlign.Bottom }, + left: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.Start } + }).width('100%').id("divider") + + WebView({ webUrl: this.viewModel?.webUrl, controller: this.webViewController }).width('100%') + .alignRules({ + top: { anchor: "divider", align: VerticalAlign.Bottom }, + left: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.Start }, + bottom: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom } + }).id("host_web") + }.width('100%').height('100%') + }.width('100%').height('100%') + } +} + +class IndexWebViewController extends WebViewController { + private viewModel: IndexViewModel; + + constructor(viewModel: IndexViewModel) { + super() + this.viewModel = viewModel; + } + + onPageEnd(title: string): void { + this.viewModel.pageTitle = title; + } + +} + +class IndexViewModel { + webUrl: string = "https://devwiki.net"; + pageTitle: ResourceStr = "ViewModel Title"; + + constructor() { + } + + onTitleChanged(title: string) { + this.pageTitle = title; } } \ No newline at end of file diff --git a/app/src/main/module.json5 b/app/src/main/module.json5 index 059ae35..19880b4 100644 --- a/app/src/main/module.json5 +++ b/app/src/main/module.json5 @@ -33,6 +33,11 @@ } ] } + ], + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET" + } ] } } \ No newline at end of file diff --git a/common_ui/Index.ets b/common_ui/Index.ets index d2c3242..275e0ad 100644 --- a/common_ui/Index.ets +++ b/common_ui/Index.ets @@ -1 +1,7 @@ -export { TitleBar } from './src/main/ets/component/TitleBar' \ No newline at end of file +export { TitleBar } from './src/main/ets/component/TitleBar' +export { ComponentConst } from './src/main/ets/component/ComponentConst' +export { WebViewEventKey } from './src/main/ets/event/EventKey' +export { Emitter } from './src/main/ets/event/Emitter' +export { WebView, WebViewController } from './src/main/ets/component/web/WebView' + +export { CommonRes } from './src/main/ets/utils/CommonRes' diff --git a/common_ui/src/main/ets/component/TitleBar.ets b/common_ui/src/main/ets/component/TitleBar.ets index d25c1e2..200d17a 100644 --- a/common_ui/src/main/ets/component/TitleBar.ets +++ b/common_ui/src/main/ets/component/TitleBar.ets @@ -1,81 +1,121 @@ import { ComponentConst } from './ComponentConst' +export enum TitleBarMenuType{ + /** + * 不显示 + */ + None = 0, + /** + * 显示Icon + */ + Icon = 1, + /** + * 显示文本 + */ + Text = 2 +} @Preview @Component export struct TitleBar { - @State barHeight: number = 48; - @State menuPadding: number = 8; + @Prop title: ResourceStr; - @State leftText: ResourceStr = "Back"; - @State leftTextVisible: Visibility = Visibility.Hidden; - @State leftIconVisible: Visibility = Visibility.Visible; + @Prop barHeight: number = 48; + @Prop menuPadding: number = 8; - @State rightText: ResourceStr = "Menu"; - @State rightTextVisible: Visibility = Visibility.Hidden; - @State rightIconVisible: Visibility = Visibility.Hidden; + @Prop leftText: ResourceStr = ""; + @Prop leftIcon: Resource = $r("app.media.ic_chevron_left"); + @Prop leftMenuType: TitleBarMenuType = TitleBarMenuType.Icon; - @State title: ResourceStr = "Title"; - @State titleTextAlign: TextAlign = TextAlign.Start; - @State titleVisible: Visibility = Visibility.Visible; + @Prop rightText: ResourceStr = ""; + @Prop rightIcon: Resource = $r("app.media.ic_close"); + @Prop rightMenuType: TitleBarMenuType = TitleBarMenuType.Icon; + + @Prop titleTextAlign: TextAlign = TextAlign.Start; + @Prop titleVisible: Visibility = Visibility.Visible; onLeftClicked?: Function; onRightClicked?: Function; + @Styles setWidthAndHeight() { + .height(this.barHeight).width(this.barHeight) + } + build() { Row() { RelativeContainer() { - Button() { - Stack() { - Text(this.leftText).textAlign(TextAlign.Center) - .width(this.barHeight).height(this.barHeight).textAlign(TextAlign.Center) - .visibility(this.leftTextVisible) - Image($r("app.media.ic_chevron_left")) - .width(this.barHeight).height(this.barHeight).padding(this.menuPadding) - .visibility(this.leftIconVisible) + if (this.leftMenuType != TitleBarMenuType.None) { + Button() { + if (this.leftMenuType == TitleBarMenuType.Icon) { + Image(this.leftIcon) + .setWidthAndHeight() + .padding(this.menuPadding) + } else { + Text(this.leftText) + .textAlign(TextAlign.Center) + .setWidthAndHeight() + } } - }.backgroundColor(0xFFFFFF).type(ButtonType.Normal).stateEffect(true) - .onClick((event) => { - this.onLeftClicked?.(event); - }).alignRules({ - top: {anchor: ComponentConst.ContainerId, align: VerticalAlign.Top}, - left: {anchor: ComponentConst.ContainerId, align: HorizontalAlign.Start}, - bottom: {anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom } - }) - .id("left_menu") - - Text(this.title).visibility(this.titleVisible).textAlign(this.titleTextAlign).margin({left: this.menuPadding}) + .setWidthAndHeight() + .backgroundColor(0xFFFFFF) + .type(ButtonType.Normal) + .stateEffect(true) + .onClick((event) => { + this.onLeftClicked?.(event); + }) .alignRules({ - top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top}, - left: { anchor: "left_menu", align: HorizontalAlign.End}, - right: { anchor: "right_menu", align: HorizontalAlign.Start}, - bottom: {anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom } - }).id("center_row") + top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top }, + left: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.Start }, + }).id("left_menu") + } - Stack(){ + if (this.titleVisible == Visibility.Visible) { + Text(this.title) + .textAlign(this.titleTextAlign) + .margin({ + left: this.leftMenuType != TitleBarMenuType.None ? this.barHeight : 0, + right: this.leftMenuType != TitleBarMenuType.None ? this.barHeight : 0 + }) + .padding({left: this.leftMenuType != TitleBarMenuType.None ? 0 : this.menuPadding}) + .alignRules({ + top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top }, + left: { + anchor: this.leftMenuType != TitleBarMenuType.None ? "left_menu" : ComponentConst.ContainerId, + align: HorizontalAlign.Start + }, + right: { + anchor: this.leftMenuType != TitleBarMenuType.None ? "right_menu" : ComponentConst.ContainerId, + align: HorizontalAlign.End + }, + bottom: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom } + }) + .id("title") + } + + if (this.rightMenuType != TitleBarMenuType.None) { Button() { - Image($r("app.media.ic_close")) - .width(this.barHeight).height(this.barHeight) - .padding(this.menuPadding) - }.backgroundColor(0xFFFFFF).type(ButtonType.Normal).stateEffect(true).visibility(this.rightIconVisible) + if (this.rightMenuType == TitleBarMenuType.Icon) { + Image(this.rightIcon) + .setWidthAndHeight().padding(this.menuPadding) + } else { + Text(this.rightText) + .textAlign(TextAlign.Center) + .setWidthAndHeight() + } + } + .setWidthAndHeight() + .backgroundColor(0xFFFFFF) + .type(ButtonType.Normal) + .stateEffect(true) .onClick((event) => { this.onRightClicked?.(event); - }); - - Button() { - Text(this.rightText).textAlign(TextAlign.Center) - .width(this.barHeight).height(this.barHeight).textAlign(TextAlign.Center) - }.backgroundColor(0xFFFFFF).type(ButtonType.Normal).stateEffect(true).visibility(this.rightTextVisible) - .onClick((event) => { - this.onRightClicked?.(event); - }); - }.alignRules({ - top: {anchor: ComponentConst.ContainerId, align: VerticalAlign.Top}, - right: {anchor: ComponentConst.ContainerId, align: HorizontalAlign.End}, - bottom: {anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom } - }) - .id("right_menu") + }) + .alignRules({ + top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top }, + right: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.End }, + }).id("right_menu") + } } .width('100%').height('100%') }.height(this.barHeight) diff --git a/common_ui/src/main/ets/component/web/WebScript.ets b/common_ui/src/main/ets/component/web/WebScript.ets new file mode 100644 index 0000000..cdf0cf5 --- /dev/null +++ b/common_ui/src/main/ets/component/web/WebScript.ets @@ -0,0 +1,15 @@ + +/** + * H5 调用 native的函数 + */ +export interface WebScriptCallback { + getCount(): number; +} + +/** + * native 调用 H5的函数 + */ +export interface WebScriptFunction { + + alert(message: string): void; +} \ No newline at end of file diff --git a/common_ui/src/main/ets/component/web/WebView.ets b/common_ui/src/main/ets/component/web/WebView.ets new file mode 100644 index 0000000..cb9d2f2 --- /dev/null +++ b/common_ui/src/main/ets/component/web/WebView.ets @@ -0,0 +1,68 @@ +import web_webview from '@ohos.web.webview'; +import { WebScriptCallback, WebScriptFunction } from './WebScript'; + +@Component +export struct WebView { + + private webviewController: web_webview.WebviewController = new web_webview.WebviewController(); + controller: WebViewController = new WebViewController(); + @Prop webUrl: string = ""; + + aboutToAppear(): void { + this.controller.setWebviewController(this.webviewController); + } + + build() { + Web({ src: this.webUrl, controller: this.webviewController }) + .javaScriptAccess(true)// 允许使用 js + .javaScriptProxy({ + object: null, + name: "", + methodList: [], + controller: this.webviewController + }) + .onPageEnd(() => { this.controller.onPageEnd(this.webviewController.getTitle()) }) + .width('100%') + .height('100%'); + } +} + +interface WebViewCallback { + onPageEnd(title: string): void; +} + + +export class WebViewController implements WebScriptCallback, WebScriptFunction, WebViewCallback { + + private webviewController?: web_webview.WebviewController + + constructor() { + } + + setWebviewController(webviewController: web_webview.WebviewController) { + this.webviewController = webviewController; + } + + refresh() { + this.webviewController?.refresh(); + } + + getCurrentUrl(): string { + return this.webviewController?.getUrl() ?? ""; + } + + getCount(): number { + return 0; + } + + alert(message: string): void { + this.webviewController?.runJavaScript(`window.alert(${message})`); + } + + loadUrl(url: string) { + this.webviewController?.loadUrl(url); + } + + onPageEnd(title: string) { + } +} \ No newline at end of file diff --git a/common_ui/src/main/ets/event/Emitter.ets b/common_ui/src/main/ets/event/Emitter.ets new file mode 100644 index 0000000..aaf7084 --- /dev/null +++ b/common_ui/src/main/ets/event/Emitter.ets @@ -0,0 +1,110 @@ + +export interface EmitterItem { + key: string + listener: Function +} + +type XYEmitterAllType = string | object | boolean | number | Object | Number | BigInt; + +export class Emitter { + private static instance: Emitter; + private static events: Map = new Map + private static viscosityData: Map = new Map + + public static getInstance(): Emitter { + if (!Emitter.instance) { + Emitter.instance = new Emitter(); + } + + return Emitter.instance; + } + + /** + * 添加监听 + * @param key + * @param listener + * @returns + */ + public on(key: string, listener: Function): EmitterItem { + let item: EmitterItem = {key: key, listener: listener}; + if (typeof listener !== "function") { + throw new TypeError('the listener not a function') + } + let items: EmitterItem[] | undefined = Emitter.events.get(key); + if (!items) { + items = [] + } + items.push(item) + Emitter.events.set(key, items) + if (Emitter.viscosityData.has(key)) { + let value = Emitter.viscosityData.get(key) + if (value == undefined) { + listener(); + } else { + listener(...value); + } + } + return item + } + + /** + * 取消监听 + * @param listener + */ + public off(listener: EmitterItem): void { + let items = Emitter.events.get(listener.key) + if (!items) { + return; + } + items = items.filter((it)=> { + return it !== listener; + }) + Emitter.events.set(listener.key, items); + } + + /** + * 发布普通消息 + * @param key + * @param args + */ + public emmit(key: string, ...args: T[]) { + let items = Emitter.events.get(key) + if (!items || items.length == 0) { + return; + } + + items.forEach((item: EmitterItem, index: number) => { + if (typeof item.listener === 'function') { + item.listener(...args) + } + }); + } + + /** + * 发布粘性消息 + * @param key + * @param args + */ + public emmitViscosity(key: string, ...args: T[]) { + this.emmit(key, ...args); + Emitter.viscosityData.set(key, args as XYEmitterAllType[]); + } + + /** + * 移除粘性消息 + * @param key + */ + public deleteViscosity(key: string) { + if (Emitter.viscosityData.has(key)) { + Emitter.viscosityData.delete(key) + } + } + + /** + * 删除所有监听 + */ + public removeAllListener() { + Emitter.events = new Map; + } + +} \ No newline at end of file diff --git a/common_ui/src/main/ets/event/EventKey.ets b/common_ui/src/main/ets/event/EventKey.ets new file mode 100644 index 0000000..2ad88d2 --- /dev/null +++ b/common_ui/src/main/ets/event/EventKey.ets @@ -0,0 +1,5 @@ +export class WebViewEventKey { + public static readonly TitleEvent: string = "web_view_event_title"; + public static readonly DarkModeChangedEvent: string = "web_view_event_dark_mode_changed"; + public static readonly UrlChangedEvent: string = "web_view_event_url_change_changed"; +} \ No newline at end of file diff --git a/common_ui/src/main/ets/utils/Calc.ets b/common_ui/src/main/ets/utils/Calc.ets deleted file mode 100644 index e6b7cf2..0000000 --- a/common_ui/src/main/ets/utils/Calc.ets +++ /dev/null @@ -1,3 +0,0 @@ -export function add(a:number, b:number) { - return a + b; -} \ No newline at end of file diff --git a/common_ui/src/main/ets/utils/CommonRes.ets b/common_ui/src/main/ets/utils/CommonRes.ets new file mode 100644 index 0000000..3487fe4 --- /dev/null +++ b/common_ui/src/main/ets/utils/CommonRes.ets @@ -0,0 +1,17 @@ + +export class CommonRes { + private constructor() { + } + + public static getIconRefresh(): Resource { + return $r("app.media.ic_refresh"); + } + + public static getIconBack(): Resource { + return $r("app.media.ic_chevron_left"); + } + + public static getIconClose(): Resource { + return $r("app.media.ic_close"); + } +} \ No newline at end of file diff --git a/common_ui/src/main/module.json5 b/common_ui/src/main/module.json5 index 5d4712e..cbfd5c7 100644 --- a/common_ui/src/main/module.json5 +++ b/common_ui/src/main/module.json5 @@ -9,6 +9,11 @@ "2in1" ], "deliveryWithInstall": true, - "pages": "$profile:main_pages" + "pages": "$profile:main_pages", + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET" + } + ] } } \ No newline at end of file diff --git a/common_ui/src/main/resources/base/media/ic_refresh.svg b/common_ui/src/main/resources/base/media/ic_refresh.svg new file mode 100644 index 0000000..5e1cb0a --- /dev/null +++ b/common_ui/src/main/resources/base/media/ic_refresh.svg @@ -0,0 +1 @@ +refresh \ No newline at end of file