一、UI
1、底部菜单栏
Tabs({ barPosition: BarPosition.End }){
ForEach(this.tabbars,(item: TabbarItem,index: number) =>{
TabContent(){
if(index == 0){
MainHome()
} else {
MineHome()
}
}
.tabBar(this.TabBuilder(item,index))
})
}
.scrollable(false)
.animationDuration(0)
.width('100%')
.height('100%')
.onChange((index: number)=>{
this.currentIndex = index
})
Tabs 组件实现底部菜单栏切换。
TabContent 组件内搭建其具体菜单下的页面,比如:首页或者个人中心。
scrollable 属性禁止滑动切换菜单页面。
animationDuration 属性设置切换菜单动画时长,如不需要动画可设置为 0
tabBar 自定义底部菜单 item 的自定义组件,可接受 @Builder 修饰的方法实现更灵活布局,通用为上图片,下文字样式。
onChange 菜单栏切换事件监听,可用状态属性控制其他相关展示。
2、顶部导航栏
Navigation(this.pathStack){
}
.height('100%')
.width('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
.hideTitleBar(true)
Navigation 导航容器
NavPathStack 负责导航控制对象,入栈、出栈行为及参数传入与接收
expandSafeArea 设置手机屏幕的安全区域延伸,一般手机都会有安全区域,比如刘海屏、灵动岛之类的机型,也就是应用内的绘制都是在屏幕的安全域内进行的,设置 SafeAreaEdge.TOP 表面导航栏(其他视图)的背景颜色可延伸至最顶部的安全区域,布局组件不会因为安全区域被延伸而发生改变。
hideTitleBar 设置导航栏状态为隐藏
主要介绍一下页面的入栈、出栈行为,实现分为几步:
(1)在工程的 pages 文件夹下创建页面。
(2)在工程的 resources/base/profile 文件夹下创建 router_map.json,里面添加对页面的注册,这里以登录页面为例,
{
"routerMap":[
{
"name": "Login",
"pageSourceFile": "src/main/ets/pages/Login/index.ets",
"buildFunction": "LoginBuilder"
}
]
}
name 导航 NavPathStack 对象需要跳转的页面的别名。
pageSourceFile 页面的路径。
buildFunction 这里需要传一个实现构建当前页面的构建方法的方法名,可以在页面内初始化一个构建方法,下面是登录的页面。
@Builder
export function LoginBuilder() {
Login()
}
@Component
struct Login {
build() {
}
}
(3)将 router_map.json 注册在 module.json5 里
"routerMap":"$profile:router_map"
(4)跳转的实现
NavPathStack 实例化对象调用 pushPath 或者 pushPathByName 方法即可。
pushPath 可通过 name 参数来进行指定目标页面的跳转。param 可在跳转中传入下一个页面需要的参数。
pushPathByName 除了上面的功能外,还可以传入一个 pop 回调函数,当下一个页面的某些行为完成后需要返回上一个页面并携带参数时, pop 回调函数会得到响应并获取到参数内容。
NavPathStack 对象调用 pop 或者 popToIndex(参数传-1可返回根视图) 方法即可实现页面的后退的功能。
3、点击空白区域键盘回收
通过 FocusController 对象的 clearFocus 方法实现取消键盘第一响应者。
normalFocusController : FocusController = new FocusController()
为组件添加点击事件,实现键盘回收
.onClick(()=>{
this.normalFocusController.clearFocus()
})
4、自定义弹窗
可对页面进行 @CustomDialog 声明,实现弹窗操作。
@CustomDialog
export default struct SystemNoticeDialog{
controller?: CustomDialogController = new CustomDialogController({
builder: this.dialogBuild,
cancel: ()=>{
},
customStyle: true,
alignment: DialogAlignment.Center
})
private dismissDialogAction(){
this.noticeModel = null
this.controller?.close()
}
@Builder dialogBuild(){
//自定义组件内容
}
build() {}
}
builder 自定义弹窗的构建组件方法
customStyle 开启自定义样式
alignment 弹窗展示内容的对齐方式,通常为中心位置。
5、自定义子窗口
应用的进入与展示依赖一个窗口,这里认为它是主窗口,与此同时还可以创建一个子窗口,在子窗口下可以更为灵活的展示相关的业务逻辑,比如音乐播放器的全局的悬浮球。
WindowManager.getWindowStage().createSubWindow(subWindowName,(err,subWindow)=> {
if(!err.code){
const saveModel = AppStorageV2.connect(ToastModel, () => new ToastModel())!
// 默认需要定时消失
if(saveModel&&saveModel.toastType != ToastType.ToastProgress){
setTimeout(()=>{
subWindow.destroyWindow()
},saveModel.duration)
}
WindowManager.getWindowStage().getMainWindow().then((mainWindow)=>{
let screenWidth = mainWindow.getWindowProperties().windowRect.width
let screenHeight = mainWindow.getWindowProperties().windowRect.height
subWindow.setUIContent(page)
subWindow.resize(screenWidth,screenHeight)
subWindow.setWindowTouchable(saveModel.modal)
subWindow.showWindow((err)=>{
if(!err.code){
try {
subWindow.setWindowBackgroundColor('#00000000')
} catch (e) {
}
}
})
})
}
})
WindowManager.getWindowStage().createSubWindow 创建子窗口
WindowManager.getWindowStage().getMainWindow() 获取主窗口信息,这里主要是用来为设置子窗口建立坐标参考
subWindow.setUIContent 设置子窗口展示页面的内容,这里的参数是一个页面路径。
subWindow.resize 设置子窗口的尺寸。
subWindow.setWindowTouchable 设置子窗口是否允许手势穿透,把事件传给下面的主窗口。
subWindow.destroyWindow 销毁子窗口
6、Web组件 与 JS 交互
class LocalWebBridgeManager{
normalDataModel: NormalDataModel<string> | null = null
constructor() {
}
setNormalDataModel(normalDataModel: NormalDataModel<string> | null) {
this.normalDataModel = normalDataModel
}
getHtmlBody(): string{
return this.normalDataModel?.data ?? ''
}
}
@Component
struct NormalWebView{
@State navigationModel: WebNavigationModel | null = null
@State normalDataModel: NormalDataModel<string> | null = null
localWebBridgeManager: LocalWebBridgeManager = new LocalWebBridgeManager()
webController: WebviewController = new webview.WebviewController()
build() {
NavDestination(){
Web({src: this.navigationModel?.src,controller: this.webController})
.javaScriptAccess(true)
.javaScriptProxy({
object: this.localWebBridgeManager,
name: "localWebBridgeManager",
methodList: ["getHtmlBody"],
controller: this.webController,
})
.width('100%')
.height('100%')
}
.title(this.navigationModel?.title)
.onReady(async (c: NavDestinationContext)=>{
})
.onShown(()=>{
})
.onHidden(()=>{
})
}
}
重点代码 Web 组件的 javaScriptProxy 桥接绑定参数
object 传入的是鸿蒙原生的实例对象。
name html页面的 js 环境中关联的 对象名称。
methodList 映射的方法列表。
controller webview.WebviewController 类型的web控制器管理对象。
写一个本地网页 local.html 放在 resources/rawfile 下,注意:这里的文件必须按照要求进行放置,否则工程内无法访问到。
<script>
document.getElementById('contain').innerHTML = localWebBridgeManager.getHtmlBody()
</script>
localWebBridgeManager.getHtmlBody() 即为鸿蒙原生对象进行的映射方法执行,可以通过原生进行其他的操作,然后把结果返回给 html 页面,这种方式相对于 iOS / android 的 js 交互简洁了不少。
7、Scroller 滚动监听
scroller: Scroller = new Scroller()
获取 Scroll 组件的滚动位置,用来处理一些UI变化。
Scroll(this.scroller){
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.onScrollStop(()=>{
this.currentShowIndicatorIndex = this.scroller.currentOffset().xOffset > 70 ? 1 : 0
})
onScrollStop 监听滚动停止事件,通过 scroller 拿到滚动的位置信息。
8、主动退出应用
应用需要在同意用户协议的时候才能进入使用,如果拒绝这个时候可以用下面的方法进行主动退出
onWindowStageCreate(windowStage: window.WindowStage): void {
//注册退出APP事件回调
this.context.terminateSelf()
}
二、状态
1、页面状态
@State 修饰属性,状态改变后相关绑定的页面会进行刷新。像 Vue 的 ref 和 React 的 setState 用法一致,关注处理数据即可。
@prop 父给子传值。
@Link 父子组件间的双向绑定,场景:封装自定义输入框,编辑的后的内容统一由父组件处理。
2、应用状态
AppStorageV2 应用内存数据存储。将需要同步的数据放在内存中,修改属性后,绑定数据的组件刷新,类似于 sessionStorage 的存储方式。
以前面说到的 NavPathStack 页面导航对象为例,将它缓存到应用缓存里,其他地方均可用下面的方法获取,执行 push/pop等页面操作。
AppStorageV2.connect(NavPathStack,()=>new NavPathStack())!.pop(true)
PersistenceV2 本地持久化存储,应用运行期间,储存的数据变化后,绑定数据的组件刷新,类似于 localStorage 的存储方式。
PersistenceV2.connect(SystemDataModel,()=>new SystemDataModel())
上面的两种状态存储方式的存值和取值相同。
三、常用第三方
总结部分开发用到的小点,希望能帮助到大家[抱拳][抱拳][抱拳]。