玩Android Harmony next版,通过项目了解harmony项目快速搭建开发

玩Android 纯血鸿蒙版

最近利用一些休息时间,开发了纯血鸿蒙版的玩Android,代码已经提交到gitee,感兴趣的同学可以下载打包使用(文章同步发布到稀土掘金)

仓库地址

wan_android

项目介绍

结合v2版本的状态管理,使用MVVM模式,官方demo可参考:MVVM模式(状态管理V2)

  1. 支持浅色/深色模式(跟随系统)
  2. 支持语言切换(跟随系统)
  3. 支持沉浸式状态栏
  4. 通用的appbar
  5. 支持页面导航路由
  6. ...

相关技术

一、浅色/深色模式

与android配置类似,在resources下创建dark目录,在目录下存放深色模式的颜色&图片等资源。其中base则是浅色使用的资源

二、语言切换

与android配置类似,在resources下创建en_US目录,在目录下存放en_US等资源。其中base则是默认使用的资源

三、沉浸式状态栏

目前官方提供两种方式支持沉浸式状态栏:

  1. 窗口全屏布局方案(项目中使用该方案)

    // 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 = 0

    aboutToAppear(): void {
    window.getLastWindow(getContext(this)).then((data) => {
    let avoidArea1 = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
    this.statusBarHeight = avoidArea1.topRect.height
    let 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

  1. 组件安全区方案(要注意延伸出去的组件是否与状态栏/导航栏相交)

    // 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?: string
  route: string
  param?: ESObject
  onResult?: 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()
  }

  @Builder
  headerLayout() {
    ListItem() {
      // 使用数据
      HomeBanner({ bannerList: this.homeViewModel.banners })
    }
  }
}

六、其他功能

以下功能都进行了二次封装,放在common下

  1. 全局事件通知,可以使用系统提供的EventHub
  2. 本地缓存,继续腾讯的mmkv:mmkv
  3. 网络框架,使用axio:axio
相关推荐
shaominjin1233 小时前
Android 中 RecyclerView 与 ListView 的深度对比:从设计到实践
android
vocal3 小时前
【我的AOSP第一课】AOSP 下载、编译与运行
android
鸿蒙小白龙4 小时前
openharmony之分布式购物车开发实战
分布式·harmonyos·鸿蒙·鸿蒙系统·open harmony
鸿蒙小白龙4 小时前
openharmony之分布式相机开发:预览\拍照\编辑\同步\删除\分享教程
分布式·harmonyos·鸿蒙·鸿蒙系统·open harmony
Lei活在当下4 小时前
【业务场景架构实战】8. 订单状态流转在 UI 端的呈现设计
android·设计模式·架构
小趴菜82274 小时前
Android中加载unity aar包实现方案
android·unity·游戏引擎
qq_252924194 小时前
PHP 8.0+ 现代Web开发实战指南 引
android·前端·php
Jeled4 小时前
Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比
android·前端·缓存·kotlin·android studio·android jetpack
安卓开发者7 小时前
鸿蒙NEXT鼠标光标开发完全指南
华为·计算机外设·harmonyos