在鸿蒙系统开发的浪潮中,ArkTS语言凭借其高效、便捷的特性,成为了开发者们的热门选择。本文将详细介绍一个基于华为鸿蒙系统(HarmonyOS)使用ArkTS语言开发的实战项目——掌盟项目。该项目包含丰富的功能和实用的开发技巧,适合有一定ArkTS基础的开发者学习参考。
本项目主要展示了一个基于鸿蒙系统的应用开发实战案例。项目中展示的数据皆为抓包获取,仅用于项目练手,无商业行为。以下是项目的整体截图:
该目录存放通用代码,包括网络请求 networking
和 webview
相关代码。网络请求部分在后续会详细介绍,而 webview
则用于加载网页内容。
EntryAbility.ets
可以设置项目入口。例如,将原入口 pages/index
改为自定义入口 pages/Tabbar/TabsPage
,代码如下:
windowStage.loadContent('pages/Tabbar/TabsPage', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
这是自定义的存放项目本地图片的目录。引用方式如下:
Image('Images/home/[email protected]')
也可以将图片放在系统图片目录 src/main/resources/base/media
下,引用方式为:
Image($r('app.media.home_search'))
该目录存放项目页面代码,其中 home
、mall
和 mine
是三个 tabbar
的控制器。tabbar
下的 TabsPage.ets
是项目自定义的入口。项目默认的入口页面是 Index.ets
,即入口为 pages/index
,如果要使用华为模拟器运行项目,需要在 EntryAbility.ets
中修改入口配置为 pages/tabbar/TabsPage
。
可以在该文件中设置项目名称(包括中文名和英文名),示例如下:
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "掌上英雄联盟"
}
]
}
该目录可以设置项目图标、启动图等资源。
TabsPage
页面一般作为项目的启动页面,使用 Tabs
控件,里面只可以使用子组件 TabContent
,并且子组件 TabContent
可以自定义每个 tabBar
,即 TabBuilder
。以下是部分源码:
import {QQHomeController} from "../home/Controller/QQHomeController"
import {QQMallController} from "../mall/Controller/QQMallController"
import {QQMineController} from "../mine/Controller/QQMineController"
@Entry
@Component
struct TabsPage {
@State currentIndex: number = 0;
private tabsController: TabsController = new TabsController()
@Builder TabBuilder(title:string, targetIndex:number, normalImg:string, selectedImg:string) {
Column() {
Image(this.currentIndex == targetIndex ? selectedImg : normalImg)
.width(24)
.height(24)
Text(title)
.fontSize(10)
.margin({top:5})
.fontColor(this.currentIndex == targetIndex ? '#0F1114' : '#565D66')
}
.backgroundColor('#ffffff')
.width('100%')
.height(60)
.justifyContent(FlexAlign.Center)
.onClick(()=>{
this.currentIndex = targetIndex
this.tabsController.changeIndex(this.currentIndex)
})
}
build() {
RelativeContainer() {
Column() {
Tabs({barPosition:BarPosition.End, controller:this.tabsController, index:0}) {
TabContent() {
QQHomeController()
}.tabBar(this.TabBuilder('首页', 0, 'Images/tabbar/tabbar_home_normal.png','Images/tabbar/tabbar_home_select.png'))
TabContent() {
QQMallController()
}.tabBar(this.TabBuilder('商城', 1, 'Images/tabbar/tabbar_mall_normal.png','Images/tabbar/tabbar_mall_select.png'))
TabContent() {
QQMineController()
}.tabBar(this.TabBuilder('我的', 2, 'Images/tabbar/tabbar_mine_normal.png','Images/tabbar/tabbar_mine_select.png'))
}
}
}
.height('100%')
.width('100%')
}
}
QQHomeController
作为首页,使用了一些常用的控件,以下是部分源码解读:
TabSegmentPage
是自定义的 Segment
控制器,使用 Tabs
控件实现不同位置的展示效果。以下是部分源码:
import { QQChannelModel } from '../../Model/QQChannelModel'
@Entry
@Component
export struct TabSegmentPage {
@State itemWidth: number = 50
@State currentIndex: number = 0
@State channelArray:Array<QQChannelModel> = Array<QQChannelModel>()
@Builder TabBuilder(index: number, name: string) {
Column() {
Text(name)
.fontColor(this.currentIndex === index ? '#161616' : '#868c8d')
.fontSize(this.currentIndex === index ? 18 : 16)
.fontWeight(this.currentIndex === index ? 500 : 400)
.lineHeight(18)
.margin({top: 10, bottom:5})
Divider()
.width(16)
.strokeWidth(2)
.color('#202020')
.opacity(this.currentIndex === index ? 1 : 0)
}
.width(this.itemWidth)
.height('100%')
}
build() {
RelativeContainer() {
Column() {
Tabs({ barPosition: BarPosition.Start}) {
if (this.channelArray.length > 0) {
ForEach(this.channelArray, (item:QQChannelModel, index) => {
TabContent() {
}
.tabBar(this.TabBuilder(index, item.name ?? ''))
.margin(0)
})
}
}
.height('100%')
.barWidth(this.itemWidth*this.channelArray.length)
.align(Alignment.Start)
.vertical(false)
.scrollable(false)
.barPosition(BarPosition.Start)
.barMode(BarMode.Fixed)
.animationDuration(300)
.onChange((index:number) => {
this.currentIndex = index
console.log('TabSegmentPage index = ',index)
})
}
}
.height('100%')
.width('100%')
}
}
使用 Swiper
实现轮播效果,代码如下:
Swiper() {
ForEach(this.bannerListArray, (item:QQBannerBody, index) => {
Image(item.imgUrl)
.onClick(() => {
router.pushUrl({
url:'common/webview/QQWebviewController',
params:{
url:item.intent
}
})
})
})
}
.width('100%')
.height(150)
.loop(true)
.autoPlay(true)
.interval(3000)
.indicator(Indicator.dot()
.itemWidth(10)
.itemHeight(2)
.selectedItemWidth(15)
.selectedItemHeight(2)
.color('#918c8e')
.selectedColor(Color.White))
Grid() {
ForEach(this.iconListArray, (item:QQIconBody, index) => {
GridItem() {
Column({space:10}) {
Image(item.iconUrl)
.width(55)
.width(55)
Text(item.name)
.fontSize(10)
.fontColor('#939999')
}
.onClick(() => {
router.pushUrl({
url:'common/webview/QQWebviewController',
params:{
url:item.intent
}
})
})
}
.width('20%')
.height(80)
})
}
.width('100%')
.backgroundColor(Color.White)
在 QQNetworkRequest.ets
中通过 http
封装 post
和 get
请求,以下是部分源码:
import http from '@ohos.net.http'
import { JSON } from '@kit.ArkTS';
let header:Record<string, string> = {
'Cookie': 'tgw_l7_route=0f7eb4ab2f1d32df0ff24f07ba0cf8db; clientType=10; accountType=255',
'qimei': '4f1c8ff7-4677-4c0a-9ac3-831c9dd865df',
'accept': '*/*',
'accept-encoding': 'gzip, deflate, br',
'Content-Type': 'application/json',
'user-agent': 'QTL/9.2.5 (iPhone; IOS 18.0; Scale/3.00)',
'connection': 'keep-alive',
'gh-header': '1-2-105-925-0',
'subchannel': '1',
'accept-language': 'zh-Hans-CN;q=1, zh-Hant-MO;q=0.9, en-CN;q=0.8',
};
// post
export function postRequest(url:string, param:Object, success:(str:string)=>void, fail:(error:Error)=>void) {
let httpRequest = http.createHttp()
let reponseResult = httpRequest.request(url, {
method: http.RequestMethod.POST,
readTimeout:60000,
connectTimeout:60000,
header: header,
extraData: param,
expectDataType:http.HttpDataType.STRING
}, (error, data) => {
if (!error) {
success(data.result.toString())
console.info('Networking ====================================');
console.info('Networking Url:' + url);
console.info('Networking Result 类型:' + typeof data.result);
console.info('Networking Result:' + data.result);
console.info('Networking code:' + data.responseCode);
console.info('Networking header:' + JSON.stringify(data.header));
console.info('Networking cookies:' + data.cookies);
console.info('Networking ====================================');
} else {
fail(error)
console.info('Networking error:' + JSON.stringify(error));
httpRequest.destroy();
}
})
}
// get
export function getRequest(url:string, param:Object, success:(str:string)=>void, fail:(error:Error)=>void) {
let httpRequest = http.createHttp()
let reponseResult = httpRequest.request(url, {
method: http.RequestMethod.GET,
readTimeout:60000,
connectTimeout:60000,
header: header,
extraData: param,
expectDataType:http.HttpDataType.STRING
}, (error, data) => {
if (!error) {
success(data.result.toString())
console.info('Networking ====================================');
console.info('Networking Url:' + url);
console.info('Networking Result 类型:' + typeof data.result);
console.info('Networking Result:' + data.result);
console.info('Networking code:' + data.responseCode);
console.info('Networking header:' + JSON.stringify(data.header));
console.info('Networking cookies:' + data.cookies);
console.info('Networking ====================================');
} else {
fail(error)
console.info('Networking error:' + JSON.stringify(error));
httpRequest.destroy();
}
})
}
对于网络请求获取的 json
数据,可以通过方法 JSON.parse(jsonStr)
来转换成对应的数据模型进行使用。以下是创建对应的数据模型类 QQBannerModel.ets
的部分代码:
export class QQBannerModel {
code?: number
data?: QQBannerData
msg?: string
result?: number
}
export class QQBannerData {
result?: number
next?: string
feedsInfo?: Array<QQBannerFeedsInfo>
attach?: object
scope?: string
distance?: number
}
export class QQBannerFeedsInfo {
feedBase?: QQBannerFeedBase
feedNews?: QQBannerFeedNews
}
export class QQBannerFeedNews {
body?: Array<QQBannerBody>
}
export class QQBannerBody {
enableWholeBannerClick?: boolean
isAmsAd?: boolean
imgUrl?: string
bigImgUrl?: string
intent?: string
title?: string
taskName?: string
contentId?: string
isVideo?: boolean
vid?: string
reservedDesc?: string
packageName?: string
universalLink?: string
algorithmInfo?: QQBannerAlgorithmInfo
extend?: QQBannerExtend
commonInfo?: QQBannerCommonInfo
}
export class QQBannerCommonInfo {
cover?: string
type?: string
}
export class QQBannerExtend {
roomid?: string
}
export class QQBannerAlgorithmInfo {
adid?: string
actionID?: string
fname?: string
bannerId?: string
ecode?: string
from?: string
clickEventId?: string
expoEventId?: string
gamecode?: string
url?: string
}
export class QQBannerFeedBase {
layoutType?: string
contentType?: string
contentId?: string
intent?: string
position?: number
priority?: number
}
在网络请求获取 json
的方法中,可以写入以下代码获取具体数据进行渲染:
let model:QQBannerModel = JSON.parse(str)
网络请求示例:
// 请求banner数据
loadHomeRequestBannerData() {
this.viewModel.loadHomeRequestBannerData((str) => {
console.log('home Banner 数据',str)
let model:QQBannerModel = JSON.parse(str)
// 刷新数据
let dataModel:QQBannerData = model.data as QQBannerData
let feedsInfoArray = dataModel.feedsInfo as Array<QQBannerFeedsInfo>
if (feedsInfoArray.length > 0) {
let feedsInfo = feedsInfoArray[0]
let feedNews = feedsInfo.feedNews
this.bannerListArray = feedNews?.body as Array<QQBannerBody>
}
console.log('home Banner 数据 .data ',JSON.parse(str))
}, (error) => {
console.log('home Banner 数据 error ',error)
})
}
路由跳转可以实现一个页面跳转到另一个页面,并可以携带参数。例如,首页 QQHomeController
跳转 webview
页面 QQWebviewController
,并携带参数 url
:
router.pushUrl({
url:'common/webview/QQWebviewController',
params:{
url:'https://www.baidu.com'
}
})
在 QQWebviewController
中返回按钮方法中写入路由返回方法:
router.back()
对于跳转携带的参数 url
,可以在 QQWebviewController
中的生命周期方法中接收:
aboutToAppear(): void {
const param = router.getParams() as Map<string, string>
this.url = param['url']
}
使用模拟器需要提前进行申请并下载,具体的模拟器申请可以参考:华为模拟器申请。模拟器使用中遇见问题可以参考:鸿蒙系统HarmonyOS-ArkTS项目开发问题汇总。
项目实现效果参考:华为模拟器录屏。项目源码:HMApp_ArkTS。
通过本文的介绍,我们详细了解了这个鸿蒙ArkTS实战项目的结构、UI设计、网络请求、数据封装、路由跳转等方面的内容。希望本文能为鸿蒙开发者提供一些有用的参考,帮助大家更好地掌握ArkTS语言和鸿蒙系统开发。
以上就是关于该项目的详细介绍,如果你在开发过程中遇到任何问题,欢迎在评论区留言讨论。