优化 TitleBar 和 Index 页面

This commit is contained in:
DevWiki 2024-04-02 15:01:03 +08:00
parent c0854269a9
commit 99aaff6876
12 changed files with 396 additions and 72 deletions

View File

@ -1,24 +1,79 @@
import { TitleBar } from '@devwiki/common_ui/src/main/ets/component/TitleBar'; import { WebView, ComponentConst, CommonRes, TitleBar, WebViewController } from '@devwiki/common_ui';
import { ComposeTitleBar } from '@ohos.arkui.advanced.ComposeTitleBar';
import { EditableTitleBar } from '@ohos.arkui.advanced.EditableTitleBar';
import { SelectTitleBar } from '@ohos.arkui.advanced.SelectTitleBar';
@Entry @Entry
@Component @Component
struct Index { struct Index {
@State message: string = 'Hello World';
onLeftClicked(event: ClickEvent) { @State viewModel: IndexViewModel = new IndexViewModel();
this.message = "TitleBar LeftClicked"; 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() { build() {
Column() { Column() {
TitleBar({onLeftClicked: this.onLeftClicked}).height(48); RelativeContainer() {
Divider(); 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); Divider().alignRules({
} top: { anchor: "title_bar", align: VerticalAlign.Bottom },
.height('100%') 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;
} }
} }

View File

@ -33,6 +33,11 @@
} }
] ]
} }
],
"requestPermissions": [
{
"name" : "ohos.permission.INTERNET"
}
] ]
} }
} }

View File

@ -1 +1,7 @@
export { TitleBar } from './src/main/ets/component/TitleBar' 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'

View File

@ -1,81 +1,121 @@
import { ComponentConst } from './ComponentConst' import { ComponentConst } from './ComponentConst'
export enum TitleBarMenuType{
/**
* 不显示
*/
None = 0,
/**
* 显示Icon
*/
Icon = 1,
/**
* 显示文本
*/
Text = 2
}
@Preview @Preview
@Component @Component
export struct TitleBar { export struct TitleBar {
@State barHeight: number = 48; @Prop title: ResourceStr;
@State menuPadding: number = 8;
@State leftText: ResourceStr = "Back"; @Prop barHeight: number = 48;
@State leftTextVisible: Visibility = Visibility.Hidden; @Prop menuPadding: number = 8;
@State leftIconVisible: Visibility = Visibility.Visible;
@State rightText: ResourceStr = "Menu"; @Prop leftText: ResourceStr = "";
@State rightTextVisible: Visibility = Visibility.Hidden; @Prop leftIcon: Resource = $r("app.media.ic_chevron_left");
@State rightIconVisible: Visibility = Visibility.Hidden; @Prop leftMenuType: TitleBarMenuType = TitleBarMenuType.Icon;
@State title: ResourceStr = "Title"; @Prop rightText: ResourceStr = "";
@State titleTextAlign: TextAlign = TextAlign.Start; @Prop rightIcon: Resource = $r("app.media.ic_close");
@State titleVisible: Visibility = Visibility.Visible; @Prop rightMenuType: TitleBarMenuType = TitleBarMenuType.Icon;
@Prop titleTextAlign: TextAlign = TextAlign.Start;
@Prop titleVisible: Visibility = Visibility.Visible;
onLeftClicked?: Function; onLeftClicked?: Function;
onRightClicked?: Function; onRightClicked?: Function;
@Styles setWidthAndHeight() {
.height(this.barHeight).width(this.barHeight)
}
build() { build() {
Row() { Row() {
RelativeContainer() { RelativeContainer() {
if (this.leftMenuType != TitleBarMenuType.None) {
Button() { Button() {
Stack() { if (this.leftMenuType == TitleBarMenuType.Icon) {
Text(this.leftText).textAlign(TextAlign.Center) Image(this.leftIcon)
.width(this.barHeight).height(this.barHeight).textAlign(TextAlign.Center) .setWidthAndHeight()
.visibility(this.leftTextVisible) .padding(this.menuPadding)
Image($r("app.media.ic_chevron_left")) } else {
.width(this.barHeight).height(this.barHeight).padding(this.menuPadding) Text(this.leftText)
.visibility(this.leftIconVisible) .textAlign(TextAlign.Center)
.setWidthAndHeight()
} }
}.backgroundColor(0xFFFFFF).type(ButtonType.Normal).stateEffect(true) }
.setWidthAndHeight()
.backgroundColor(0xFFFFFF)
.type(ButtonType.Normal)
.stateEffect(true)
.onClick((event) => { .onClick((event) => {
this.onLeftClicked?.(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})
.alignRules({ .alignRules({
top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top }, top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top },
left: { anchor: "left_menu", align: HorizontalAlign.End}, left: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.Start },
right: { anchor: "right_menu", align: HorizontalAlign.Start}, }).id("left_menu")
bottom: {anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom } }
}).id("center_row")
Stack(){ if (this.titleVisible == Visibility.Visible) {
Button() { Text(this.title)
Image($r("app.media.ic_close")) .textAlign(this.titleTextAlign)
.width(this.barHeight).height(this.barHeight) .margin({
.padding(this.menuPadding) left: this.leftMenuType != TitleBarMenuType.None ? this.barHeight : 0,
}.backgroundColor(0xFFFFFF).type(ButtonType.Normal).stateEffect(true).visibility(this.rightIconVisible) right: this.leftMenuType != TitleBarMenuType.None ? this.barHeight : 0
.onClick((event) => { })
this.onRightClicked?.(event); .padding({left: this.leftMenuType != TitleBarMenuType.None ? 0 : this.menuPadding})
}); .alignRules({
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 }, top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top },
right: {anchor: ComponentConst.ContainerId, align: HorizontalAlign.End}, 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 } bottom: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Bottom }
}) })
.id("right_menu") .id("title")
}
if (this.rightMenuType != TitleBarMenuType.None) {
Button() {
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);
})
.alignRules({
top: { anchor: ComponentConst.ContainerId, align: VerticalAlign.Top },
right: { anchor: ComponentConst.ContainerId, align: HorizontalAlign.End },
}).id("right_menu")
}
} }
.width('100%').height('100%') .width('100%').height('100%')
}.height(this.barHeight) }.height(this.barHeight)

View File

@ -0,0 +1,15 @@
/**
* H5 调用 native的函数
*/
export interface WebScriptCallback {
getCount(): number;
}
/**
* native 调用 H5的函数
*/
export interface WebScriptFunction {
alert(message: string): void;
}

View File

@ -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) {
}
}

View File

@ -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<string, EmitterItem[]> = new Map
private static viscosityData: Map<string, XYEmitterAllType[]> = 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<T>(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<T>(key: string, ...args: T[]) {
this.emmit(key, ...args);
Emitter.viscosityData.set(key, args as XYEmitterAllType[]);
}
/**
* 移除粘性消息
* @param key
*/
public deleteViscosity<T>(key: string) {
if (Emitter.viscosityData.has(key)) {
Emitter.viscosityData.delete(key)
}
}
/**
* 删除所有监听
*/
public removeAllListener() {
Emitter.events = new Map;
}
}

View File

@ -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";
}

View File

@ -1,3 +0,0 @@
export function add(a:number, b:number) {
return a + b;
}

View File

@ -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");
}
}

View File

@ -9,6 +9,11 @@
"2in1" "2in1"
], ],
"deliveryWithInstall": true, "deliveryWithInstall": true,
"pages": "$profile:main_pages" "pages": "$profile:main_pages",
"requestPermissions": [
{
"name" : "ohos.permission.INTERNET"
}
]
} }
} }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>refresh</title><path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z" /></svg>

After

Width:  |  Height:  |  Size: 311 B