前言
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
@Component
struct SplashPage {@State counter: number = 5@State message: string = 'Wan Android'private intervalID: number = -1build() {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 - 1if (this.counter == 0) {clearInterval(this.intervalID)this.goIntent()}}, 1000);}).onDisAppear(() => {LogUtils.info('222222222 隐藏')this.counter = 5clearInterval(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
@Component
struct 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<LoginBean>({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<RegisterBean>({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
@Component
struct MainPage {private tabsController: TabsController = new TabsController();@State currentTabIndex: number = Constants.HOME_TAB_INDEX;@State title: string = '首页'@BuilderTabBuilder(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
@Component
export struct MainTitleBar {@Prop title: string@Prop showSearch: booleanbuild() {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、界面效果