行业知识
WanAndroid(鸿蒙版)开发的第一篇
前言
DevEco Studio版本:4.0.0.600
WanAndroid的API链接:玩Android 开放API-玩Android - wanandroid.com
为了兼容HarmonyOS,我这边以Arkts--API9为例进行实现
通过华为官网渠道目前下载的版本还是3.1的,这边提供下4.0版本下载的渠道:
DevEco Studio 4.0版本下载地址:DevEcoStudio 4.0版本下载
4.0版本的配套关系
注:一些基础知识点和默认配置项不做详细说明了,本文基于大家有一定的鸿蒙基础能力。有不明白的可以留言咨询
实现效果:
玩安卓演示视频
项目创建
为了兼容HarmonyOS(目前手机API版本只到9),compile SDK : 9
模块构建
根据项目功能模块划分了:HomeModule、ProjectModule、NavigationModule、MineModule四个功能模块,加上基础模块BaseLibrary和主模块entry组成项目的基础架构。
File-->new-->Module新建模块
其他几个模块依照上图为例依次创建,创建后如下图所示:
BaseLibrary资源链接:https://download.csdn.net/download/Abner_Crazy/88908326
项目页面实现
1、启动页面(SplashPage)
新建SplashPage页面:new-->Page创建页面,或者new-->ArkTS File创建文件(需要手动在main_pages.json中添加页面引用)
SplashPage详细代码:
import router from '@ohos.router'import LogUtils from '@app/BaseLibrary/src/main/ets/utils/LogUtils'@Entry@Componentstruct SplashPage { @State counter: number = 5 @State message: string = 'Wan Android' private intervalID: number = -1 build() { RelativeContainer() { Text(this.message) .fontSize(52) .fontWeight(FontWeight.Bold) .id('textContent') .alignRules({ center: { anchor: '__container__', align: VerticalAlign.Center }, middle: { anchor: '__container__', align: HorizontalAlign.Center } }) //倒计时 Text(`${this.counter}s后跳转`) .fontSize(15) .fontColor(Color.Gray) .id('textJump') .alignRules({ right: { anchor: '__container__', align: HorizontalAlign.End }, top: { anchor: '__container__', align: VerticalAlign.Top } }) .padding({ left: 10, right: 10, top: 5, bottom: 5 }) .margin({ top: 20, right: 20 }) .borderWidth(1) .borderRadius(5) .borderColor(Color.Gray) .onClick(() => { this.goIntent() }) } .width('100%') .height('100%') .onAppear(() => { LogUtils.info('222222222 显示') this.intervalID = setInterval(() => { this.counter = this.counter - 1 if (this.counter == 0) { clearInterval(this.intervalID) this.goIntent() } }, 1000); }) .onDisAppear(() => { LogUtils.info('222222222 隐藏') this.counter = 5 clearInterval(this.intervalID) }) } private goIntent(): void { let userLoginStatus = false //TODO:获取登录状态,后面实现 if (userLoginStatus) { router.replaceUrl({ url: 'pages/MainPage' }) } else { router.replaceUrl({ url: 'pages/LoginPage' }) } }} 2、登录页面(LoginPage)
LoginPage页面详细代码:
import { Constants, HttpManager, LoadingDialog, RequestMethod, UserManager } from "@app/BaseLibrary"import LogUtils from '@app/BaseLibrary/src/main/ets/utils/LogUtils'import { RegisterBean } from '../bean/RegisterBean'import promptAction from '@ohos.promptAction'import { LoginBean } from '../bean/LoginBean'import router from '@ohos.router'const TAG = 'LoginPage--- ';@Entry@Componentstruct LoginPage { @State username: string = "" //用户名 @State password: string = "" //密码 hideJump: boolean = router.getParams()?.['hideJump'] build() { RelativeContainer() { Image($r("app.media.ic_back")) .width(36) .height(32) .id('imageBack') .margin(20) .alignRules({ left: { anchor: '__container__', align: HorizontalAlign.Start }, top: { anchor: '__container__', align: VerticalAlign.Top } }) .onClick(() => { router.back() }) if (!this.hideJump) { Text('跳过') .fontSize(15) .fontColor(Color.Gray) .id('textJump') .alignRules({ right: { anchor: '__container__', align: HorizontalAlign.End }, top: { anchor: '__container__', align: VerticalAlign.Top } }) .padding({ left: 10, right: 10, top: 5, bottom: 5 }) .margin({ top: 20, right: 20 }) .borderWidth(1) .borderRadius(5) .borderColor(Color.Gray) .onClick(() => { router.clear() router.replaceUrl({ url: 'pages/MainPage' }) }) } Text('登录') .fontSize(50) .fontColor(Color.Black) .fontWeight(FontWeight.Bold) .id('textLogin') .margin({ top: 30 }) .alignRules({ middle: { anchor: '__container__', align: HorizontalAlign.Center }, top: { anchor: 'imageBack', align: VerticalAlign.Bottom } }) //用户名和密码 Column() { Row() { Image($r('app.media.username')) .width(32) .height(32) TextInput({ placeholder: '请输入用户名' }) .width(300) .margin({ left: 16 }) .onChange((value: string) => { this.username = value }) } Row() { Image($r('app.media.password')) .width(32) .height(32) TextInput({ placeholder: '请输入密码' }) .type(InputType.Password) .width(300) .margin({ left: 16 }) .onChange((value: string) => { this.password = value }) }.margin({ top: 20 }) }.id('columnLogin') .margin({ top: 80 }) .alignRules({ middle: { anchor: '__container__', align: HorizontalAlign.Center }, top: { anchor: 'textLogin', align: VerticalAlign.Bottom } }) Button("注册") .width(340) .height(60) .onClick(() => { this.registerData() }) .id('buttonLogin') .margin({ bottom: 80 }) .alignRules({ middle: { anchor: '__container__', align: HorizontalAlign.Center }, bottom: { anchor: '__container__', align: VerticalAlign.Bottom } }) Button("登录") .width(340) .height(60) .onClick(() => { this.dialogController.open() this.loginData() }) .id('buttonRegister') .backgroundColor(Color.Pink) .margin({ bottom: 40 }) .alignRules({ middle: { anchor: '__container__', align: HorizontalAlign.Center }, bottom: { anchor: 'buttonLogin', align: VerticalAlign.Top } }) } .width('100%') .height('100%') } /** * 登录请求 */ private loginData(): void { if (this.username.trim().length == 0 || this.password.trim().length == 0) { promptAction.showToast({ message: "用户名或密码不能为空" }) return } HttpManager.getInstance() .request({ method: RequestMethod.POST, header: { "Content-Type": "application/json" }, url: `https://www.wanandroid.com/user/login?username=${this.username}&password=${this.password}`, //wanAndroid的API:登录 }) .then((result: LoginBean) => { this.dialogController.close() LogUtils.info(TAG, "content encodeURIComponent: " + encodeURIComponent(this.username) + " result:" + JSON.stringify(result)) if (result.errorCode == 0) { AppStorage.SetOrCreate(Constants.APPSTORAGE_ISLOGIN, true) AppStorage.SetOrCreate(Constants.APPSTORAGE_USERNAME, encodeURIComponent(this.username)) AppStorage.SetOrCreate(Constants.APPSTORAGE_PASSWORD, this.password) AppStorage.SetOrCreate(Constants.APPSTORAGE_TOKEN_PASS, this.cookiesMatch(JSON.stringify(result.header))) promptAction.showToast({ message: "登录成功" }) UserManager.setLogIn(true) router.clear() router.replaceUrl({ url: 'pages/MainPage' }) } else { promptAction.showToast({ message: result.errorMsg }) } }) .catch((error) => { this.dialogController.close() promptAction.showToast({ message: "登录失败" }) LogUtils.info(TAG, "error: " + JSON.stringify(error)) }) } /** * 注册请求 */ private registerData(): void { if (this.username.trim().length == 0 || this.password.trim().length == 0) { promptAction.showToast({ message: "用户名或密码不能为空" }) return } HttpManager.getInstance() .request({ method: RequestMethod.POST, header: { "Content-Type": "application/json" }, url: `https://www.wanandroid.com/user/register?username=${this.username}&password=${this.password}&repassword=${this.password}`, //wanAndroid的API:注册 }) .then((result: RegisterBean) => { LogUtils.info(TAG, "result: " + JSON.stringify(result)) if (result.errorCode == 0) { promptAction.showToast({ message: "注册成功" }) } else { promptAction.showToast({ message: result.errorMsg }) } }) .catch((error) => { promptAction.showToast({ message: "注册失败" }) LogUtils.info(TAG, "error: " + JSON.stringify(error)) }) } /** * cookies正则匹配,获取token_pass * @returns */ private cookiesMatch(header: string): string { LogUtils.info(TAG, " cookiesMatch header: " + header) let content = header.match(/token_pass_wanandroid_com=(\S*);/)[1] LogUtils.info(TAG, " cookiesMatch content: " + content) return content } private dialogController = new CustomDialogController({ builder: LoadingDialog({ content: '登录中...' }), customStyle: true, alignment: DialogAlignment.Center, // 可设置dialog的对齐方式,设定显示在底部或中间等,默认为底部显示 })} 登录界面用到的HttpManager网络请求参考之前的文章:鸿蒙自定义Http网络访问组件-CSDN博客
登录成功后通过AppStorage保存登录状态:APPSTORAGE_ISLOGIN,用户名:APPSTORAGE_USERNAME(注:需要执行encodeURIComponent(this.username)进行编码,解决部分特殊用户名乱码问题),密码:APPSTORAGE_PASSWORD,用户登录后的token:APPSTORAGE_TOKEN_PASS(执行 cookiesMatch方法进行正则匹配)
AppStorage.SetOrCreate(Constants.APPSTORAGE_ISLOGIN, true)AppStorage.SetOrCreate(Constants.APPSTORAGE_USERNAME, encodeURIComponent(this.username))AppStorage.SetOrCreate(Constants.APPSTORAGE_PASSWORD, this.password)AppStorage.SetOrCreate(Constants.APPSTORAGE_TOKEN_PASS, this.cookiesMatch(JSON.stringify(result.header)))
3、主页面(MainPage)
MainPage页面详细代码:
import { Constants } from "@app/BaseLibrary"import LogUtils from "@app/BaseLibrary/src/main/ets/utils/LogUtils"import { MainTitleBar } from '../widget/MainTitleBar';@Entry@Componentstruct MainPage { private tabsController: TabsController = new TabsController(); @State currentTabIndex: number = Constants.HOME_TAB_INDEX; @State title: string = '首页' @Builder TabBuilder(title: string, index: number, normalIcon: Resource, selectIcon: Resource) { Column() { Image(this.currentTabIndex == index ? selectIcon : normalIcon) .width(25) .height(25) Text(title) .margin({ top: 4 }) .fontSize("10fp") .fontColor(this.currentTabIndex == index ? "#1296db" : "#999999") } .justifyContent(FlexAlign.Center) .height(56) .width('100%') .onClick(() => { this.currentTabIndex = index; this.tabsController.changeIndex(this.currentTabIndex) }) } build() { Column() { MainTitleBar({ title: this.title, showSearch: this.currentTabIndex == 0 }) Tabs({ barPosition: BarPosition.End, controller: this.tabsController, }) { // 首页 TabContent() { // HomePage() } .padding({ left: 12, right: 12 }) .tabBar(this.TabBuilder(Constants.HOME_TITLE, Constants.HOME_TAB_INDEX , $r('app.media.ic_bottom_normal_home'), $r('app.media.ic_bottom_select_home'))) // 项目 TabContent() { // ProjectPage() } .tabBar(this.TabBuilder(Constants.PROJECT_TITLE, Constants.PROJECT_TAB_INDEX , $r('app.media.ic_bottom_normal_project'), $r('app.media.ic_bottom_select_project'))) // 导航 TabContent() { // NavigationPage() } .padding({ left: 12, right: 12 }) .tabBar(this.TabBuilder(Constants.NAVIGATION_TITLE, Constants.NAVIGATION_TAB_INDEX , $r('app.media.ic_bottom_normal_navigation'), $r('app.media.ic_bottom_select_navigation'))) //我的 TabContent() { // MinePage() } .tabBar(this.TabBuilder(Constants.MINE_TITLE, Constants.MINE_TAB_INDEX , $r('app.media.ic_bottom_normal_mine'), $r('app.media.ic_bottom_select_mine'))) } .width('100%') .height('100%') .flexShrink(1) .scrollable(false) .animationDuration(100) .barHeight(56) .backgroundColor($r('app.color.bar_backgroundColor')) .barMode(BarMode.Fixed) .onChange((index: number) => { this.title = this.getTitle(index) this.currentTabIndex = index; }) }.width('100%') .height('100%') .backgroundColor(Color.White) } private getTitle(index: number): string { let titleList = [Constants.HOME_TITLE, Constants.PROJECT_TITLE, Constants.NAVIGATION_TITLE, Constants.MINE_TITLE] LogUtils.info("3333333333333333 titleList: " + titleList[index]) return titleList[index] }} MainTitleBar详细代码:
import router from '@ohos.router'@Preview@Componentexport struct MainTitleBar { @Prop title: string @Prop showSearch: boolean build() { RelativeContainer() { Text(this.title) .fontSize(24) .fontColor(Color.White) .fontWeight(FontWeight.Bold) .id("textTitle") .alignRules({ center: { anchor: '__container__', align: VerticalAlign.Center }, middle: { anchor: '__container__', align: HorizontalAlign.Center } }) Image($r('app.media.ic_search')) .width(25) .height(25) .id("imageSearch") .margin({ right: 16 }) .alignRules({ center: { anchor: '__container__', align: VerticalAlign.Center }, right: { anchor: '__container__', align: HorizontalAlign.End } }) .onClick(() => { router.pushUrl({url:'pages/search/SearchPage'}) }) .visibility(this.showSearch ? Visibility.Visible : Visibility.None) } .backgroundColor('#1296db') .width('100%') .height(56) }} 4、界面效果