首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

HarmonyOS学习第六周(实践版)(一)

  • 25-02-18 10:20
  • 2851
  • 12955
blog.csdn.net

本周是跟着尚硅谷的教程进行了一个实践项目的编写,相比于黑马的项目,我个人觉得尚硅谷的项目会比较的简单,结构比较清晰。更多使用@Builder和@Component,将组件封装在page页面中,好处是更容易寻找,而且不会有那么多的文件,坏处是代码会显得很冗长,而且代码的复用性会比较差。

本周的项目是一个单词打卡项目,包括了登录、打卡、答题等页面,是一个相对完整的项目,但这个项目更像一个纯前端项目,通过调用后端接口,而不是建表和操作数据库来进行数据的交互与存储。

欢迎页

这个页面非常的简单,就是一个背景图片加上一个转场动画。

背景设置

  1. .backgroundImage($r('app.media.img_splash_bg'))
  2. .backgroundImageSize({ width: '100%', height: '100%' })

转场动画

转场动画的详细介绍见第三周。

  1. if(this.flag){
  2. Image($r('app.media.ic_logo'))
  3. .logoStyle()
  4. .transition({type:TransitionType.Insert,opacity:0,translate:{x:-150}})
  5. Text('快速单词记忆神器')
  6. .titleStyle()
  7. .transition({type:TransitionType.Insert,opacity:0,translate:{x:150}})
  8. }

关键在于调用transition,并设置各种自定义属性。如type可以设置进场动画或者是出场动画。

跳转和动画启动逻辑 

  1. onPageShow(){
  2. animateTo({duration:1000,onFinish:()=>{
  3. setTimeout(()=>{
  4. router.replaceUrl({url:'pages/Index'})
  5. },200)
  6. }},()=>{
  7. this.flag=true;
  8. })
  9. }

当我们用transition设置好动画样式后,我们要用animateTo设置动画的运行时间 。

onfinish回调函数,用于设置动画完成后的逻辑。动画完成2s后,我们要跳转到到主页,所以用路由router的replaceUrl进行跳转。(用replaceUrl的原因是:欢迎页面一般只有在进入APP时要调用,后续不会再出现)

flag是控制动画开始与否的变量,当flag为true时,才会显示上面的Image和text,动画才会显示。

最外层的onPageShow是生命周期函数,当页面展示时会调用这个函数。

答题页

(细心的友友可能发现这个页面的停止测试的止不见了,还有一个奇怪的答案灌,这个我也不太清楚是为什么,在预览器的时候是正常的,但是用模拟器跑的时候就会这样,其他项目也没有问题。知道是为什么的友友麻烦评论区跟我说一下,谢谢大家!)

在这个页面中,我们可以看到底部又有熟悉的tabbar页面切换,这应该是每个手机软件的标配了。用于切换不同的主要页面。除此之外主要有三个部分,统计部分,单词部分,选项部分。

统计部分

由图可见,这四个部分的结构都是相同的,由一个icon加上text,中间一段空白,最后再有一个不同的组件。所以我们应该将这个结构单独封装起来,简化代码,提高复用性。但是我们最后有一个不同组件,要怎么封装哇,这时候我们就要引入一个新的装饰器@BuilderParam

@BuilderParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。什么意思呢,就是我们可以用@Builder封装起来的内容,就可以传入到@BuilderParam装饰的方法中。

所以代码如下图所示:

  1. @Component
  2. export struct StatItem {
  3. icon:Resource
  4. name:string
  5. @BuilderParam statComp:()=>void
  6. fontColor:Color
  7. build() {
  8. Row({space:10}){
  9. Image(this.icon)
  10. .height(14)
  11. .width(14)
  12. Text(this.name)
  13. .fontWeight(FontWeight.Medium)
  14. .fontSize(14)
  15. .fontColor(this.fontColor)
  16. Blank()
  17. this.statComp()
  18. }
  19. .width('100%')
  20. .height(30)
  21. }
  22. }

以“进度”举例:

  1. StatItem({
  2. icon:$r('app.media.ic_progress'),
  3. name:'进度',
  4. fontColor:Color.Black
  5. }){
  6. Progress({value:this.answeredCount,total:this.totalCount})
  7. .width(100)
  8. }

在传入其他参数后,我们再传入一个对象,这个对象就和我们编写@builder一样,传入自定组件机器样式即可,如这个代码中就传入了一个进度条组件Progress。 

同时我们每次每次完成一次答题,就会有一个记录我们答题情况的弹窗,这个弹窗的结构也和统计页面的样式相似,也可以复用这个结构。

个数

个数中的自定义组件是一个button组件,点击后会出现一个弹窗,是一个TextPickerDialog,是一个文本滑动弹窗。

用于设置本次答题组中包含多少个单词。

  1. StatItem({
  2. icon:$r('app.media.ic_count'),
  3. name:'个数',
  4. fontColor:Color.Black
  5. }){
  6. Button(this.totalCount.toString())
  7. .width(100)
  8. .height(25)
  9. .backgroundColor('#EBEBEB')
  10. .enabled(this.practiceStatus===PracticeStatus.Stopped)
  11. .fontColor(Color.Black)
  12. .onClick(()=>{
  13. TextPickerDialog.show({
  14. range:['5','10','15','20'],
  15. value:this.totalCount.toString(),
  16. onAccept:(result)=>{
  17. this.totalCount=parseInt(result.value)
  18. this.questions=getRandomQuestions(this.totalCount)
  19. }
  20. })
  21. })

 这里面有个enabled属性,标识什么时候这个组件可以被点击。practiceStatus是一个用于标识单词测试状态的变量,有停止、暂停、进行三个状态,只有状态为停止才可以进行修改。因为我们不可以测试测一半进行修改,这不符合逻辑。TextPickerDialog中的onAccept就是我们点击确定后的逻辑。getRandomQuestions是自定义的一个函数,用于从题库中抽取用户设置的题目数量的题。

  1. export function getRandomQuestions(count: number) {
  2. let length = questionData.length;
  3. let indexes: number[] = [];
  4. while (indexes.length < count) {
  5. let index = Math.floor(Math.random() * length);
  6. if (!indexes.includes(index)) {
  7. indexes.push(index)
  8. }
  9. }
  10. return indexes.map(index => questionData[index])
  11. }

计时器

计时器中用到了一个新的组件TextTimer,是一个通过文本显示计时信息并控制其计时器状态的组件。

  1. TextTimer({ controller: this.timerController })
  2. .onTimer((utc,elapsedTime)=>{
  3. this.timeUsed=elapsedTime
  4. })

需要设置一个控制组件controller,需要提前进行声明:

  timerController: TextTimerController = new TextTimerController();

 声明后timerController将会有start、pause、stop三个属性,对应我们所定义的开始、暂停、停止。

onTimer事件时间文本发生变化时触发,elapsedTime:计时器经过的时间,单位为毫秒。

单词部分

这个部分就很简单了,就是两个text的组件,代码如下:

  1. Column(){
  2. Text(this.questions[this.currentIndex].word)
  3. .wordStyle()
  4. Text(this.questions[this.currentIndex].sentence)
  5. .sentenceStyle()
  6. }

选项部分

因为四个选项的样式逻辑全都相同,且储存在数组中,所以我们可以使用foreach循环来进行渲染

  1. ForEach(this.questions[this.currentIndex].options,(option)=>{
  2. OptionButton({
  3. option: option,
  4. answerStatus:this.answerStatus,
  5. answer:this.questions[this.currentIndex].answer,
  6. selectedOption:this.selectedOption
  7. })
  8. .enabled(this.answerStatus==AnswerStatus.Answering)
  9. .onClick(()=>{
  10. if(this.practiceStatus!==PracticeStatus.Running){
  11. promptAction.showToast({message:'请先开始测试'})
  12. return
  13. }
  14. this.selectedOption=option
  15. this.answeredCount++
  16. if(option===this.questions[this.currentIndex].answer){
  17. this.rightCount++
  18. }
  19. this.answerStatus=AnswerStatus.Answered
  20. if(this.currentIndex<this.questions.length-1){
  21. setTimeout(()=>{
  22. this.currentIndex++
  23. this.answerStatus=AnswerStatus.Answering
  24. },500)
  25. }else{
  26. this.stopPractice()
  27. }
  28. })
  29. },option => this.questions[this.currentIndex].word + '-' + option)

OptionButton是封装的组件,也就是每个选项的样式和逻辑,如下:

  1. @Component
  2. struct OptionButton{
  3. option:string
  4. answer:string
  5. @State optionStatus:OptionStatus=OptionStatus.Default
  6. //注意!!声明顺序影响更新顺序,会导致出错
  7. @Prop selectedOption:string
  8. @Prop @Watch('onAnswerStatus') answerStatus:AnswerStatus
  9. onAnswerStatus(){
  10. if (this.option===this.answer) {
  11. this.optionStatus=OptionStatus.Right
  12. }else{
  13. if(this.option===this.selectedOption){
  14. this.optionStatus=OptionStatus.Wrong
  15. }else{
  16. this.optionStatus=OptionStatus.Default
  17. }
  18. }
  19. }
  20. getBgColor(){
  21. switch (this.optionStatus) {
  22. case OptionStatus.Right:
  23. return '#1DBF7B'
  24. case OptionStatus.Wrong:
  25. return '#FA635F'
  26. default:
  27. return Color.White
  28. }
  29. }
  30. build(){
  31. Stack(){
  32. Button(this.option)
  33. .optionButtonStyle({
  34. bg: this.getBgColor(),
  35. font: this.optionStatus === OptionStatus.Default ? Color.Black : Color.White
  36. })
  37. if(this.optionStatus===OptionStatus.Right){
  38. Image($r('app.media.ic_right'))
  39. .width(22)
  40. .height(22)
  41. .offset({x:10})
  42. }else if (this.optionStatus===OptionStatus.Wrong){
  43. Image($r('app.media.ic_wrong'))
  44. .width(22)
  45. .height(22)
  46. .offset({x:10})
  47. }
  48. }.alignContent(Alignment.Start)
  49. }
  50. }

首先我们要明确点击选项后我们需要触发什么逻辑操作:1、我们要判断所选项是否为正确选项;2、如果为正确选项,则该选项要变绿,不为正确选项则要变红,且正确选项要变绿;3、在选择后要跳转到下一题,若已经是最后一题则要跳出弹窗。

onAnswerStatus用于判断按键状态,判断所选是否为正确,getBgColor用于给按键添加背景颜色,正确或错误的背景色。answerStatus是用于监测用户是否已经点击了选项,监听到状态变化则触发onAnswerStatus。

  1. if(this.currentIndex<this.questions.length-1){
  2. setTimeout(()=>{
  3. this.currentIndex++
  4. this.answerStatus=AnswerStatus.Answering
  5. },500)
  6. }else{
  7. this.stopPractice()
  8. }

这段代码则是用于判断是否为本测试组的最后一题,如果是的话,则调用停止逻辑: 

  1. stopPractice(){
  2. this.practiceStatus = PracticeStatus.Stopped
  3. //停止计时器
  4. this.timerController.pause()
  5. //弹窗
  6. this.dialogController.open()
  7. }

弹窗内容就是前面讲到的完成答题后的弹窗。

 

Tabs页面切换

  1. Tabs({ index: this.currentTabIndex }){
  2. TabContent(){
  3. PracticePage()
  4. }.tabBar(this.barBuilder(0,'答题',$r('app.media.ic_practice'),$r('app.media.ic_practice_selected')))
  5. TabContent(){
  6. CirclePage()
  7. }.tabBar(this.barBuilder(1,'圈子',$r('app.media.ic_circle'),$r('app.media.ic_circle_selected')))
  8. TabContent(){
  9. MinePage()
  10. }.tabBar(this.barBuilder(2,'我的',$r('app.media.ic_mine'),$r('app.media.ic_mine_selected')))
  11. }

用Tabs搭配TabContent和tabbar使用,TabContent中用于存放页面的UI界面 ,tabbar则是定义页面切换导航的样式,详见第五六周的文章。本代码中,把每个页面详细的样式封装成了单独的组件,然后用import引入后使用。

tabbar样式
  1. @Builder barBuilder(index:number,title:string,icon:Resource,iconSelected:Resource){
  2. Column(){
  3. Image(this.currentTabIndex === index ? iconSelected : icon)
  4. .width(25)
  5. .height(25)
  6. Text(title)
  7. .tabTitleStyle(this.currentTabIndex === index ? Color.Black:'#959595')
  8. }
  9. }

这里自定义了tabbar 的样式,让其为图片加文字的组合。并且当tabbar被选中时会有不同其他tabbar的效果。

注:本文转载自blog.csdn.net的超级困~的文章"https://blog.csdn.net/2402_83739313/article/details/138561015"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

135
HarmonyOS
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top