2024年最全鸿蒙HarmonyOS小项目开发实战(下),2024年最新面试小知识

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

首先放一个图片,是整个的一个效果,至于页面布局配色方面的问题,希望大家勿喷,就我个人而言做不出具有美感的一些东西…,大家领会精神就行🤓🤓🤓。

移动端搭建

移动端的app主要包含四个界面:首页、答题、空界面,结果展示界面,整个界面做的比较简洁,但是有覆盖到前面所说的一些知识点

项目文件夹展示

那首先呢,还是老规矩,在这里带领大家了解一下我的项目路径

  • /ets/component 主要是自定义的一些组件
  • ImageComponent 结果展示中图片组件
  • ProgressComponent 答题过程中进度条组件
  • PropertiesPanelComponent 结果展示属性面板组件
  • TitleComponent 各个页面菜单栏组件
  • TopicBodyComponent 题目展示答题区域组件
  • /ets/data 自定义的实体类
  • RES 后端数据返回解析到的实体类
  • /ets/pages 各个页面
  • EmptyPage 空界面
  • Index 主界面
  • IsHidden 自己之前测试的,在整个项目中没啥用途
  • ShowResPage 测试结果展示界面
  • Topic 题目展示界面
  • /ets/resources 资源文件 配置文件等,在这里不在过多赘述
主界面搭建

主界面的搭建比较粗糙,页面中没有做一些组件化的东西,大致就是采用行,列布局进行的堆叠,通过图片与单选按钮进行逻辑处理,确定用户选择了什么性别以及什么样的测试方式

在整个页面组件最初声明变量,分别用来保存各个单选按钮是否被选中的状态,以及用户选择性别等的状态以性别这个列布局为例子,进行相关代码中的布局以及判定逻辑的展示

//最开始的变量声明
@State ischeckedA: boolean = false //按钮A的初始化状态,默认不选中
@State ischeckedB: boolean = false //按钮B的初始化状态,默认不选中
@State ischeckedC: boolean = false //按钮C的初始化状态,默认不选中
@State ischeckedD: boolean = false //按钮D的初始化状态,默认不选中
@State sex: number = -1; //性别 初始化状态为-1 0代表女性,1代表男性
@State type: string = ‘’; //测试名称,初始化为空字符串,

//性别选择模块
Column() {
Row() {
Column() {
Text($r(‘app.string.EntryAbility_tips’))//请选择您的性别 提示
.fontSize(15)
.fontWeight(FontWeight.Bold)
}
.width(‘100%’)
}
.border({ width: 1 })
.height(‘5%’)

Row() {
Column() {
Image($r(‘app.media.sex_boy’))
.width(this.imageWidth)
.height(this.imageHeigt)
.border({ width: 1 })
.onClick(() => {
this.ischeckedA = true;
this.ischeckedB = false;
})

Radio({ value: ‘sex_boy’, group: ‘sex’ }).checked(this.ischeckedA)
.height(10)
.width(10)
.onChange((isChecked: boolean) => {

if (isChecked) {
console.log(‘boy’)
this.sex = 1;
}
})
}
.width(‘50%’)

Column() {
Image($r(‘app.media.sex_girl’))
.border({ width: 1 })
.width(this.imageHeigt)
.height((this.imageHeigt))
.onClick(() => {
this.ischeckedA = false;
this.ischeckedB = true;
})
Radio({ value: ‘sex_girl’, group: ‘sex’ }).checked(this.ischeckedB)
.height(10)
.width(10)
.onChange((isChecked: boolean) => {
if (isChecked) {
console.log(‘girl’)
this.sex = 0;
}
})
}
.width(‘50%’)
}
.height(‘20%’)
}

可以通过用户的选择进入不同的界面,不论男女,如果选择了mbti,则进入mbti测试界面,如果选择了disc,则跳转至空界面,当然用户如果不进行选择就开始测试,页面也会提醒用户进行选择。

//开始测试按钮
Row() {
Column() {
Button(‘开始测试’).type(ButtonType.Capsule)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.onClick(() => {

if (this.sex === -1 || this.type === ‘’) {
AlertDialog.show(
{
title: ‘提示:’,
message: ‘请选择性别或者测试方式!’,
autoCancel: true,
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -200 },
gridCount: 4,
}
)
} else if (this.type === ‘disc’) {
console.log(this.sex.toString())
console.log(this.type.toString())
// router.pushUrl({ url: ‘pages/EmptyPage’ })
this.clickNext(‘pages/EmptyPage’)
}
else {
//携带参数 进行页面跳转
console.log(‘’ + this.sex)
console.log(‘’ + this.type)
this.clickNext(‘pages/Topic’)
}
})
}
.width(‘100%’)
}

//页面跳转自定义函数
clickNext(url:string) {
router.pushUrl({
url: url, //页面路由url
params: { //跳转时的携带的参数
args1: this.sex,
args2: this.type,
}
}, router.RouterMode.Single //单实例模式。
//如果目标页面的url在页面栈中已经存在同url页面,离栈顶最近的页面会被移动到栈顶,移动后的页面为新建页。
//如目标页面的url在页面栈中不存在同url页面,按标准模式跳转。
)
}

特别注意:如果想进行页面的跳转,你得让他知道你的基本路由,所以在添加新的界面的时候,一定要在 src/main/resources/base/profile下的main_pages.json进行路由配置,将新建界面的路由加进去才可以进行所谓的页面跳转

//main_pages.json
{
“src”: [
“pages/Index”,
“pages/EmptyPage”,
“pages/Topic”,
“pages/ShowResPage”
]
}

答题界面搭建

答题界面采用了组件的方式,将一个个模块按照组件的方式进行封装,然后进行调用,这样的话,在一定程度上可以精简页面,提高代码的复用性。相比index界面,答题界面的代码明显比较简洁,其中标题,进度条,题目答题区域都被封装成组件提取出去,在本页面使用时只需import进来,传入相关的参数即可。

//Topic.ets
import { TitleComponent } from ‘…/component/TitleComponent’
import { ProgressComponent } from ‘…/component/ProgressComponent’
import { TopicBodyComponent } from ‘…/component/TopicBodyComponent’
import router from ‘@ohos.router’

@Entry
@Component
struct Topic {
@State paramsFromIndex: object = router.getParams()
@State finishTopic: number = 0
@State allTopic: number = 0

build() {
Column({ space: 20 }) {
//标题
TitleComponent({ title: this.paramsFromIndex?.[‘args2’] + “测试 " })
//进度条
ProgressComponent({ finishTopic: this.finishTopic, allTopic: this.allTopic })
//答题界面
TopicBodyComponent({finishTopic: f i n i s h T o p i c , a l l T o p i c : finishTopic,allTopic: finishTopic,allTopic:allTopic})
}
.width(‘100%’)
.height(‘100%’)
.backgroundColor(”#f0f0f0")
}
}

那么对于答题界面而言,topic.ets为父组件,标题,进度条,答题区域都为子组件。

进入该界面之后呢 首先得进行首页传值的接收,接收所传递的是哪种测试,同时将标题中界面展示字样进行修改。由于标题字样仅仅是做展示使用,也不会对这个值进行修改,父组件怎么传递,标题怎么渲染即可,所以父子组件之间传递值是属于单向传递,所以对于子组件中变量的监听与接收使用注解**@Prop**即可

/***

  • 顶部状态栏
    */
    import router from ‘@ohos.router’

@Component
export struct TitleComponent {
@Prop title: string //父子传递为单向 父—>子

build() {
Row({ space: 22 }) {
Image($r(‘app.media.left’)).width(50).height(50).fillColor(Color.White)
.margin({ left: 15 })
.onClick(() => {
router.back()
})
Text(this.title).fontSize(20)
}.height(“10%”)
.width(‘100%’)
.border({ width: 1 })

}
}

在题目展示及答题区组件加载之前,先去加载后台数据,获取题目列表数据进行解析,获取到题目列表和题目总数。在答题的过程中,每当用户答完一道题后,已完成数目便会+1,由于父组件与该组件之间是**@Link**进行的双向数据传递,所以在该组件中获取到的数据,以及对于变量的修改,在父组件重视可以被感知的。

//渲染之前进行网络请求
aboutToAppear(): void {
//初始化数据
let list = []

//获取网络请求
let httpRequest = http.createHttp();
httpRequest.request(“localhost:8899/homp/getAll”, (err, data) => {
if (!err) {
//数据解析
const response = data.result.toString();
const res = JSON.parse(response).data

for (let i = 0; i < res.length; i++) {
let item = res[i];
list.push({
id: item.sequenceNumber,
name: item.name,
optionA: item.optionA,
valA: item.valA,
optionB: item.optionB,
valB: item.valB,
});
// console.log(list[i].id)
}
this.allTopic = res.length //进行总题目数的修改
} else {
console.info(‘error:’ + JSON.stringify(err));
}
});

this.data = new MyDataSource(list)
}

特别注意:由于网络请求这块不算做是默认存在的,得手动开启网络访问权限之后,才可以进行网络数据的获取,主要是对src/main/路径下的module.json5文件进行添加如下代码

//网路权限
“requestPermissions”: [
{
“name”: “ohos.permission.INTERNET”,
“usedScene”: {
“when”: “always”
}
}
]

而在主体答题区呢 主要是用到了一个Swiper组件,类似于实现答一题进行自动翻页的效果,并将获取到的数据进行循环渲染。而在游标到达最后一题,并且已经答完时,跳转至结果展示界面等待后端计算返回性格测评的结果

//答题区
Swiper(this.swiperController) {
LazyForEach(this.data, (item: Topic) => {

//嵌套组件 显示题目
Column() {
//题干
Text(item.id + ". " + item.name)

//选项A
Button() {
Row() {
Text(item.optionA)
}
}
.type(ButtonType.Normal)
.optionStyle()
.onClick(() => {
let index = Number(item.id)
this.finishTopic = index
if (index === this.data.totalCount()) {
//1-保存值
this.res.push(item.valA)
let ans = this.res.join(‘’)
console.log(“ans:” + ans)
//跳转界面(携带拼接好的选项字符串)
router.replaceUrl({
url: ‘pages/ShowResPage’,
params: {
ans: ans,
}
})

} else {
//1-保存值
this.res.push(item.valA)
//2-换到下一题
this.swiperController.showNext()
}

})

//选项B
Button() {
Row() {
Text(item.optionB)
}
}.type(ButtonType.Normal)
.optionStyle()
.onClick(() => {
let index = Number(item.id)
this.finishTopic = index
if (index === this.data.totalCount()) {
//如果到最后一题了 显示提交按钮
//1-保存值
this.res.push(item.valB)
let ans = this.res.join(‘’)
console.log(“ans:” + ans)
//跳转界面
router.replaceUrl({
url: ‘pages/ShowResPage’,
params: {
ans: ans,
}
})

} else {
//1-保存值
this.res.push(item.valB)
//2-换到下一题
this.swiperController.showNext()
}

})

}.width(‘90%’)
.height(180)
.justifyContent(FlexAlign.SpaceEvenly)
}, item => item)

}
.cardStyle() //自定义卡片样式
.cachedCount(2)
.index(0)
.interval(4000)
.indicator(false)
.loop(false)
.duration(1000)
.itemSpace(0)
.disableSwipe(true)
.curve(Curve.Linear)
.onChange((index: number) => {
// console.info(index.toString() + this.res.join(‘’))
})

答题进度中,由于父组件与展示题目子组件之间数据类似于双向绑定,在答题组件进行操作,答题后,会对已完成题目这个变量进行修改,而变量的变化能够被子组件感知并单向传递给进度组件,所以整个过程中,进度组件的显示也会随答题而发生变化

/***

  • 题目列表中的进度模块
    */
    @Component
    export struct ProgressComponent {
    @Prop finishTopic: number
    @Prop allTopic: number

build() {
Column() {
Row() {
Text(‘答题进度:’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Stack() {//堆叠组件,将一个进度条和两个文本框进行堆叠,展示出比较好看的效果
Progress(
{
value: this.finishTopic,
total: this.allTopic,
type: ProgressType.Ring

}).width(80)
Row() {
Text(this.finishTopic.toString())
.fontWeight(18)
.fontColor(“#36D”)
Text(’ / ’ + this.allTopic.toString())
.fontWeight(18)
.fontColor(Color.Black)
}
}

}.cardStyle()
.margin({ top: 15, left: 10, right: 10 })
.justifyContent(FlexAlign.SpaceEvenly)
.backgroundColor(“#FAEBD7”)

}

}
}
//自定义卡片样式
@Styles function cardStyle() {
.width(“95%”)
.padding(20)
.backgroundColor(Color.White)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

10 })
.justifyContent(FlexAlign.SpaceEvenly)
.backgroundColor(“#FAEBD7”)

}

}
}
//自定义卡片样式
@Styles function cardStyle() {
.width(“95%”)
.padding(20)
.backgroundColor(Color.White)

[外链图片转存中…(img-TEHzzUvY-1715703801046)]
[外链图片转存中…(img-qcRYUPm9-1715703801047)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值