重生之我在大学自学鸿蒙开发第二天-《MVVM模式》
- 个人主页:VON
- 文章所属专栏:从0开始的开源鸿蒙6.0.0
- 个人抖音:清洒
上一篇:
重生之我在大学自学鸿蒙开发第一天-《基础篇》-CSDN博客
目录
一、前言
二、实践
2.1、简述MVVM
2.2、改造model文件
2.3、改造view文件
2.4、改造rawfile文件夹
1、将获取的buffer内容转换为字符串
2、将字符串转换为页面数据结构
3、整体逻辑如下
4、页面的生命周期执行流程
2.5、改造Banner
数据获取
2.6、改造EnablementView和TutorialView
三、测试
四、源码展示
ArticleClass
BannerClass
Index
BufferUtil
Banner
EnablementView
TutorialView
一、前言
在上一篇文章中完成了单独页面的创建,并没有进行分层,导致代码十分冗余,如果想要开发更复杂的APP肯定不能这样写,开发过java的都知道分为三层架构,鸿蒙也是这样也是三层架构的模式,本篇文章主要讲解应用架构设计基础——MVVM模式。废话不多说直接开肝!
官方文档:应用架构设计基础——MVVM模式-HarmonyOS应用开发快速入门-Codelabs-华为开发者联盟
二、实践
本章内容并不复杂,主要三层架构模式见下一章
2.1、简述MVVM
MVVM = Model + View + ViewModel模式,其中状态管理模块起到的就是ViewModel的作用,将数据与视图绑定在一起,更新数据的时候直接更新视图。
2.2、改造model文件
model文件夹用于存储数据模型。它表示组件或其他相关业务逻辑之间传输的数据,是对原始数据的进一步处理。
创建文件的时候看清楚,我刚开始就没看清点击的上面那个导致没有创建成功,这里大家注意下。
这里将Class给提取出来放到model文件中。
导出的时候别忘了使用export进行导出然后再直接import导入即可
2.3、改造view文件
view文件夹主要用于存储UI组件,也就是昨天所写的EnablementView.ets和TutorialView.ets以及Banner.ets组件。
直接将index中的代码拿过来即可,这里由于代码量太多了就将代码折叠起来了。
2.4、改造rawfile文件夹
官方说明:rawfile目录中的资源文件会被直接打包进应用,不经过编译,也不会被赋予资源文件ID。通过指定文件路径和文件名引用。
第一次听说这个文件,好像是用于存储json数据的文件夹。
可以看到这是原本的数据,我们是用数组进行存储的,现在转为json格式存放再rawfile文件夹里面即可。
json格式的数据想必大家也并不陌生。
接下来引用json数据先定义一个方法getBannerDataFromJson(),并通过ResourceManager获取当前工程目录下rawfile中的json文件内容。
转换内容需要两个步骤:
1、将获取的buffer内容转换为字符串
由于ResourceManager获取到的是Uint8Array类型的内容,所以需要将对应的内容转换为字符串,并将字符串解析为对应的数据结构。考虑到其他的文件也会使用这个公共方法,可以新建一个util文件夹,并创建一个BufferUtil文件,实现这个字符串转换方法。
果然这里需要进行转换
2、将字符串转换为页面数据结构
3、整体逻辑如下
这里的函数不用刻意去记,可以当成一个模板来进行套用,其他两个组件也可以使用此方法来进行转换,只要知道思路即可。
4、页面的生命周期执行流程
2.5、改造Banner
通过上面的生命周期不难发现,aboutToAppear要先于build,做过vue项目的同学应该都清楚,加载页面之前要先获取数据,就像vue中的onMountd(),所以我们这里也要先获取数据
数据获取
官方解释:json中数据由于无法使用$r()进行资源访问,所以使用的是字符串"app.media.banner_pic0",而在页面中直接声明时,使用的是$r('app.media.banner_pic0'),而Image组件是无法直接读取字符串"app.media.banner_pic0"的,所以这里需要进行内容调整。
这里要将imageSrc数据类型修改为string(注意下这里的s是小写)
别忘了ArticleClass类中也要进行修改
这里的图片引用的方式也需要改变下
2.6、改造EnablementView和TutorialView
这两个文件的引用一样,只需要改下名字即可。
三、测试
接下来开始进行真机测试,我这里使用模拟机来进行测试
我这里启动虚拟机的时候出了点问题,因为我的电脑没有开启虚拟化,大家可以参考下面文章来进行查看
关于win11如何打开Hyper-V详解_win11开启hyper-v-CSDN博客
我这里好像无法使用命令行下载,还是直接去官网下载吧
官网下载的是真的慢,因为这是国外的软件
还是用命令行吧,如果上面文章中的命令无法使用的剋试一下这个指令
-
使用 PowerShell 下载打开 PowerShell(管理员模式),执行以下命令下载最新版本的 Hyper 安装包:
# 下载 Hyper 安装文件到当前目录 Invoke-WebRequest -Uri "https://releases.hyper.is/download/win" -OutFile "hyper-setup.exe"
-
命令行安装下载完成后,继续在 PowerShell 中执行安装命令:
# 运行安装程序(会自动执行安装流程) .\hyper-setup.exe
-
静默安装(可选)如果需要无界面静默安装,可以添加参数:
.\hyper-setup.exe /S
调试了一下午也是终于调试成功,我这里直接将Hyper上传到网盘了,有需要的朋友可以自行提取。
通过网盘分享的文件:Hyper-Setup-3.4.1.exe
链接: https://pan.baidu.com/s/1OM67IrDC3-_Uffew5nXyIg 提取码: 9f9s
整体效果如下
这里出了点问题
注意:这里千万不能马虎,所有用到图片的地方都要这样引用!!!
四、源码展示
整体结构如下👇:
ArticleClass
export class ArticleClass {id: string = '';imageSrc: string = '';title: string = '';brief: string = '';webUrl: string = '';constructor(id: string, imageSrc: string, title: string, brief: string, webUrl: string) {this.id = id;this.imageSrc = imageSrc;this.title = title;this.brief = brief;this.webUrl = webUrl;}
}
BannerClass
export class BannerClass{id : string='';imageSrc : string='';url : string = 'https://blog.csdn.net/2302_80329073?type=blog';constructor(id : string,imageSrc : string,url : string) {this.id = id;this.imageSrc = imageSrc;this.url = url;}
}
Index
import { TutorialView } from '../view/TutorialView';
import { Banner } from '../view/Banner';
import { EnablementView } from '../view/EnablementView';
@Entry
@Component
struct Index {@State message: string = 'VON';build() {Column(){Text(this.message).fontSize(24).fontWeight(700).width('100%').padding({left:16}).lineHeight(33).textAlign(TextAlign.Start)Scroll(){Column(){Banner()EnablementView()TutorialView()}}.layoutWeight(1).scrollBar(BarState.Off).align(Alignment.TopStart)}.height('100%').width('100%').backgroundColor('#f1f1f1').padding({top:11,left:16,right:16});}
}
BufferUtil
import { util } from '@kit.ArkTS';//将Uint8Array类型的内容转换为字符串
export function bufferToString(buffer: Uint8Array): string {let textDecoder = util.TextDecoder.create('utf-8', {ignoreBOM: true});let resultPut = textDecoder.decodeToString(buffer);return resultPut;
}
Banner
import { BannerClass } from '../model/BannerClass'
import { bufferToString } from '../util/BufferUtil';// Banner组件
@Component
export struct Banner{@State bannerList:Array<BannerClass> = [];aboutToAppear(): void {this.getBannerDataFromJSON();}// 获取json数据并转换为对应类型getBannerDataFromJSON() {this.getUIContext().getHostContext()?.resourceManager.getRawFileContent('BannerData.json').then(value => {this.bannerList = JSON.parse(bufferToString(value)) as BannerClass[];});}build() {Swiper(){ForEach(this.bannerList,(item:BannerClass,index:number)=>{Image($r(item.imageSrc)).objectFit(ImageFit.Contain).width('100%').height(260).borderRadius(16)},(item:BannerClass,index:number)=>item.id)}.autoPlay(true).loop(true).indicator(new DotIndicator().color('#f1f1f1').selectedColor('red'))}
}
EnablementView
import { ArticleClass } from '../model/ArticleClass';
import { bufferToString } from '../util/BufferUtil';@Component
struct EnablementItem{@Prop enablementItem:ArticleClass;build() {Column(){Image($r(this.enablementItem.imageSrc)).width('100%').objectFit(ImageFit.Cover).height(96).borderRadius({topLeft:16,topRight:16})Text(this.enablementItem.title).height(19).width('100%').fontSize(14).textAlign(TextAlign.Start).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).fontWeight(400).padding({ left: 12, right: 12 }).margin({ top: 8 })Text(this.enablementItem.brief).height(32).width('100%').fontSize(12).textAlign(TextAlign.Start).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(2).fontWeight(400).fontColor('rgba(0, 0, 0, 0.6)').padding({ left: 12, right: 12 }).margin({ top: 2 })}.width(169).height(169).borderRadius(16).backgroundColor(Color.White)}
}@Component
export struct EnablementView {@State enablementList: Array<ArticleClass> = [];aboutToAppear(): void {this.getEnablementDataFromJSON();}// 获取json数据并转换为对应类型getEnablementDataFromJSON() {this.getUIContext().getHostContext()?.resourceManager.getRawFileContent('EnablementData.json').then(value => {this.enablementList = JSON.parse(bufferToString(value)) as ArticleClass[];});}build() {Column(){Text('赋能套件').fontColor('#182431').fontSize(16).fontWeight(500).fontFamily('HarmonyHeiTi-medium').textAlign(TextAlign.Start).padding({ left: 16 }).margin({ bottom: 8.5 })Grid() {ForEach(this.enablementList, (item: ArticleClass) => {GridItem() {EnablementItem({ enablementItem: item })}}, (item: ArticleClass) => item.id)}.rowsTemplate('1fr').columnsGap(8).scrollBar(BarState.Off).height(169).padding({ top: 2, left: 16, right: 16 })}.margin({top:18}).alignItems(HorizontalAlign.Start).width('100%')}
}
TutorialView
import { ArticleClass } from '../model/ArticleClass';
import { bufferToString } from '../util/BufferUtil';@Component
struct TutorialItem {@Prop tutorialItem :ArticleClass;build() {Row(){Column() {Text(this.tutorialItem.title).height(19).width('100%').fontSize(14).textAlign(TextAlign.Start).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).fontWeight(400).margin({ top: 4 })Text(this.tutorialItem.brief).height(32).width('100%').fontSize(12).textAlign(TextAlign.Start).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(2).fontWeight(400).fontColor('rgba(0, 0, 0, 0.6)').margin({ top: 5 })}.height('100%').layoutWeight(1).alignItems(HorizontalAlign.Start).margin({ right: 12 })Image($r(this.tutorialItem.imageSrc)).objectFit(ImageFit.Cover).height(64).width(108).borderRadius(16)}.width('100%').height(88).borderRadius(16).backgroundColor(Color.White).padding(12).alignItems(VerticalAlign.Top).margin({top: 20})}
}@Component
export struct TutorialView {@State tutorialList: Array<ArticleClass> = [];aboutToAppear(): void {this.getTutorialDataFromJSON();}// 获取json数据并转换为对应类型getTutorialDataFromJSON() {this.getUIContext().getHostContext()?.resourceManager.getRawFileContent('TutorialData.json').then(value => {this.tutorialList = JSON.parse(bufferToString(value)) as ArticleClass[];});}build() {Column() {Text('入门教程').fontColor('#182431').fontSize(16).fontWeight(500).fontFamily('HarmonyHeiTi-medium').textAlign(TextAlign.Start).padding({ left: 16 }).margin({ bottom: 8.5 })List({ space: 12 }) {ForEach(this.tutorialList, (item: ArticleClass) => {ListItem() {TutorialItem({ tutorialItem: item })}}, (item: ArticleClass) => item.id)}.scrollBar(BarState.Off).padding({ left: 16, right: 16 })}.margin({ top: 18 }).alignItems(HorizontalAlign.Start)}
}