玩Android Harmony next版,通过项目了解harmony项目快速搭建开发
玩Android 纯血鸿蒙版
最近利用一些休息时间,开发了纯血鸿蒙版的玩Android,代码已经提交到gitee,感兴趣的同学可以下载打包使用(文章同步发布到稀土掘金)
仓库地址
wan_android
项目介绍
结合v2版本的状态管理,使用MVVM模式,官方demo可参考:MVVM模式(状态管理V2)
- 支持浅色/深色模式(跟随系统)
- 支持语言切换(跟随系统)
- 支持沉浸式状态栏
- 通用的appbar
- 支持页面导航路由
- …
相关技术
一、浅色/深色模式
与android配置类似,在resources下创建dark目录,在目录下存放深色模式的颜色&图片等资源。其中base则是浅色使用的资源
二、语言切换
与android配置类似,在resources下创建en_US目录,在目录下存放en_US等资源。其中base则是默认使用的资源
三、沉浸式状态栏
目前官方提供两种方式支持沉浸式状态栏:
- 窗口全屏布局方案(项目中使用该方案)
// 1. 设置窗口全屏
windowStage.loadContent('pages/Index', (err) => {...let isLayoutFullScreen = true;windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {console.info('Succeeded in setting the window layout to full-screen mode.');}).catch((err: BusinessError) => {});});
});// 2. 在Page中通过设置padding避让
export struct BasePage {@Local statusBarHeight: number = 0@Local bottomNavHeight: number = 0aboutToAppear(): void {window.getLastWindow(getContext(this)).then((data) => {let avoidArea1 = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)this.statusBarHeight = avoidArea1.topRect.heightlet avoidArea2 = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)this.bottomNavHeight = avoidArea2.bottomRect.height}).catch((err: BusinessError) => {})}build() {Column() {if (this.showAppBar) {BassAppBar({appBarColor: this.appBarColor,title: this.titleName,backPress: () => {this.backPress()},appBarActionBuild: this.appBarActionBuild})}this.contentBuild()}.width("100%").height("100%").padding({bottom: this.getUIContext().px2vp(this.bottomNavHeight),top: this.showAppBar ? 0 : this.getUIContext().px2vp(this.statusBarHeight)}).backgroundColor($r('app.color.bg_container_color'))}
}
这里封装了BasePage,结合BaseAppBar,可轻松实现沉浸式状态栏的components
- 组件安全区方案(要注意延伸出去的组件是否与状态栏/导航栏相交)
// xxx.ets
@Entry
@Component
struct Example {build() {Column() {Row() {Text('Top Content').fontSize(40).textAlign(TextAlign.Center).width('100%')}.backgroundColor('#2786d9')// 设置顶部绘制延伸到状态栏.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])Row() {Text('Display Content 2').fontSize(30)}.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')Row() {Text('Display Content 3').fontSize(30)}.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')Row() {Text('Display Content 4').fontSize(30)}.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')Row() {Text('Display Content 5').fontSize(30)}.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')Row() {Text('Bottom Content').fontSize(40).textAlign(TextAlign.Center).width('100%')}.backgroundColor('#96dffa')// 设置底部绘制延伸到导航区域.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])}.width('100%').height('100%').alignItems(HorizontalAlign.Center).backgroundColor('#d5d5d5').justifyContent(FlexAlign.SpaceBetween)}
}
四、导航路由
HMRouter
比较好用,使用 @HMRouter
标签定义页面,绑定拦截器、生命周期及自定义转场动画
1、全局初始化
export default class EntryAbility extends UIAbility {onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {// 初始化路由管理器HMRouterMgr.init({ context: this.context })HMRouterMgr.openLog("DEBUG")}
}
2. 页面定义
@HMRouter({ pageUrl: AppRoutes.ArticlePage })
@ComponentV2
export default struct ArticlePage {......
}
3. 页面跳转
let commonBrowserModel: CommonBrowserModel = { title: item.title, link: item.link }
RouteManager.toPage({route: AppRoutes.ArticlePage,param: commonBrowserModel
})
4. 页面携带参数获取
async aboutToAppear(): Promise<void> {this.param = RouteManager.getCurrentParam<HotBuzzwordModel>()this.searchViewModel.loadSearchData(this.param?.name ?? "", this.currentPage)
}
项目中对HMRouterMgr进行了二次封装,避免繁琐的调用
export default interface RouteParam {navigationId?: stringroute: stringparam?: ESObjectonResult?: Callback<HMPopInfo>
}export class RouteManager {static toPage(routeParam: RouteParam) {HMRouterMgr.to(routeParam.route).withNavigation(routeParam.navigationId ?? AppRoutes.NavigationId).withParam(routeParam.param).onResult(routeParam.onResult).pushAsync()}static toPageAsync(routeParam: RouteParam): Promise<void> {return HMRouterMgr.to(routeParam.route).withNavigation(routeParam.navigationId ?? AppRoutes.NavigationId).withParam(routeParam.param).onResult(routeParam.onResult).pushAsync()}static back(pathInfo?: HMRouterPathInfo): void {HMRouterMgr.pop(pathInfo);}static backAsync(pathInfo?: HMRouterPathInfo): Promise<void> {return HMRouterMgr.popAsync(pathInfo);}static getCurrentParam<T>(type?: HMParamType): T | null {let param = HMRouterMgr.getCurrentParam(type)try {return param as T} catch (e) {return null}}
}
五、MVVM
@ObservedV2
export default class HomeViewModel {...// banner数据集@Trace banners: Array<BannerModel> = new Array<BannerModel>()async loadBannerData() {get<Array<BannerModel>>({url: "banner/json", response: (response) => {if (response.data != null && response.data.length > 0) {this.banners.push(...response.data)}}, error: (error: AxiosError) => {return Promise.reject(error)}})}...
}// page中使用
@ComponentV2
export struct Home {...// viewmodel@Local homeViewModel: HomeViewModel = new HomeViewModel()aboutToAppear(): void {// 加载数据this.homeViewModel.loadBannerData()}@BuilderheaderLayout() {ListItem() {// 使用数据HomeBanner({ bannerList: this.homeViewModel.banners })}}
}
六、其他功能
以下功能都进行了二次封装,放在common下
- 全局事件通知,可以使用系统提供的EventHub
- 本地缓存,继续腾讯的mmkv:mmkv
- 网络框架,使用axio:axio