优化 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 { 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;
}
}

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 { 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'
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() {
if (this.leftMenuType != TitleBarMenuType.None) {
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.Icon) {
Image(this.leftIcon)
.setWidthAndHeight()
.padding(this.menuPadding)
} else {
Text(this.leftText)
.textAlign(TextAlign.Center)
.setWidthAndHeight()
}
}.backgroundColor(0xFFFFFF).type(ButtonType.Normal).stateEffect(true)
}
.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})
.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(){
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)
.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 }
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
})
.id("right_menu")
.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() {
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%')
}.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"
],
"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