HarmonyOS三层架构实战
目录:
- 1、三层架构项目结构
- 1.0、三层架构简介
- 1.1、 common层(主要放一些公共的资源等)
- 1.2、 features层(主要模块定义的组件以及图片等静态资源)
- 1.3、 products层(主要放主页面层和一些主要的资源,内部快速入门,课程学习,知识地图等都是模块放在features层)
- 1.4、MVVM模式
- 2、products主页面
- Index.ets
- 3、快速入门模块
- QuickStartPage.ets
- Banner.ets
- bufferToString.ets
- BannerClass.ets
- EnablementView.ets
- TutorialView.ets
- ArticleClass.ets
- ArticleDetailPage.ets
- 4、课程学习模块
- CourseLearning.ets
- 5、知识地图模块
- KnowledgeMap.ets
- NavBarItem.ets
- KnowledgeMapContent.ets
1、三层架构项目结构
这里新建三个文件夹不是模块,来构建鸿蒙项目的三层架构。
1.0、三层架构简介
1.1、 common层(主要放一些公共的资源等)
1.2、 features层(主要模块定义的组件以及图片等静态资源)
1.3、 products层(主要放主页面层和一些主要的资源,内部快速入门,课程学习,知识地图等都是模块放在features层)
1.4、MVVM模式
2、products主页面
这里放置的都是一些跟主页面相关的以及入口文件等。
Index.ets
import { CourseLearning } from '@ohos/learning';
import { KnowledgeMap } from '@ohos/map';
import { QuickStartPage } from '@ohos/quickstart';
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
private tabsController: TabsController = new TabsController();
@Builder
tabBarBuilder(title: string, targetIndex: number, selectedIcon: Resource, unselectIcon: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedIcon : unselectIcon)
.width(24)
.height(24)
Text(title)
.fontFamily('HarmonyHeiTi-Medium')
.fontSize(10)
.fontColor(this.currentIndex === targetIndex ? '#0A59F7' : 'rgba(0,0,0,0.60)')
.textAlign(TextAlign.Center)
.lineHeight(14)
.fontWeight(500)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.currentIndex = targetIndex;
this.tabsController.changeIndex(targetIndex);
})
}
build() {
//控制导航栏方向。根据设计图,导航页签栏位于应用界面的底部,我们可以通过Tabs组件的barPosition参数进行设置,当设置barPosition为BarPosition.End时,页签栏会位于应用界面的下方进行显示
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
TabContent() {
QuickStartPage()
}
.tabBar(this.tabBarBuilder('快速入门', 0, $r('app.media.ic_01_on'), $r('app.media.ic_01_off')))
TabContent() {
CourseLearning()
}
.tabBar(this.tabBarBuilder('课程学习', 1, $r('app.media.ic_02_on'), $r('app.media.ic_02_off')))
TabContent() {
KnowledgeMap()
}
.tabBar(this.tabBarBuilder('知识地图', 2, $r('app.media.ic_03_on'), $r('app.media.ic_03_off')))
}
.vertical(false)
.divider({
strokeWidth: 0.5,
color: '#0D182431'
})
.scrollable(false)
.backgroundColor('#F1F3F5')
.padding({ top: 36, bottom: 28 })
}
}
以前导出模块都是在各模块index.ets文件下导出,现在是products依赖feature模块,在oh-package.json5中如下配置即可引入相关依赖。
在module.json5中进行权限配置(这里其他模块需要的权限都在products中的这个配置文件中配置即可)。
3、快速入门模块
QuickStartPage.ets
import { TutorialView } from '../view/TutorialView';
import { ArticleClass } from '../model/ArticleClass'
import { ArticleDetailPage } from './ArticleDetailPage';
import { Banner } from '../view/Banner';
import { EnablementView } from '../view/EnablementView';
import { BannerDetailPage } from './BannerDetailPage';
import { BannerClass } from '../model/BannerClass';
@Component
export struct QuickStartPage {
@State message: string = '快速入门';
@Provide('articlePathStack') articlePathStack: NavPathStack = new NavPathStack();
@Builder
quickStartRouter(name: string, param?: ArticleClass | BannerClass) {
if (name === 'articleDetail') {
ArticleDetailPage()
} else if (name === 'bannerDetailPage') {
BannerDetailPage()
}
}
build() {
Navigation(this.articlePathStack) {
Column() {
Text(this.message)
.fontSize(24)
.fontWeight(700)
.width('100%')
.textAlign(TextAlign.Start)
.padding({ left: 16 })
.fontFamily('HarmonyHeiTi-Bold')
.lineHeight(33)
//此处采用Scroll作为外层容器,是由于其内部内容很有可能会超过屏幕高度,为保证内容显示,可以采用Scroll组件来进行滚动显示。scrollBar设置为BarState.Off,表示关闭滚动时的滚动条显示
Scroll() {
Column() {
//轮播图广告组件
Banner()
//赋能组件
EnablementView()
//入门教程
TutorialView()
}
}
.layoutWeight(1)
.scrollBar(BarState.Off)
.align(Alignment.TopStart)
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
.navDestination(this.quickStartRouter)
.hideTitleBar(true)
.mode(NavigationMode.Stack)
}
}
Banner.ets
import { BannerClass } from '../model/BannerClass';
import { bufferToString } from '../util/BufferUtil';
@Component
export struct Banner {
//相信大家还记得,完成了Navigation导航首页的内容开发,接下来就要切到Navigation非首页里进行操作,首先,我们在Banner文件中使用@Consume拿到路由栈,这样才能使用相关的功能
@Consume('articlePathStack') articlePathStack: NavPathStack;
@State bannerList: BannerClass[] = [];
aboutToAppear(): void {
//在组件初始化时就加载数据
this.getBannerDataFromJSON()
}
//在Banner中,定义一个方法getBannerDataFromJson,并通过ResourceManager获取当前工程目录下rawfile中的json文件内容。
//转换内容需要两个步骤:
//1、将获取的buffer内容转换为字符串
//2、将字符串转换为页面数据结构
//因为预览器并不支持获取rawfile目录下的文件,所以无法成功获取到保存在rawfile目录下的json文件里的内容。请用真机/模拟器测试,预览器仅支持简单页面的预览
getBannerDataFromJSON() {
getContext(this).resourceManager.getRawFileContent('BannerData.json').then(value => {
this.bannerList = JSON.parse(bufferToString(value)) as BannerClass[];
})
}
clickToDetailPage(item: BannerClass) {
//让我们为每个Banner的图片添加一个点击事件,当点击Banner时,使用路由栈提供的pushPathByName方法,并提供url作为路由参数
this.articlePathStack.pushPathByName('bannerDetailPage', item);
}
build() {
//Swiper组件作为容器可以使轮播图具有轮播的效果
Swiper() {
ForEach(this.bannerList, (item: BannerClass) => {
//$r("字符串类型的")
Image($r(item.imageSrc))
.objectFit(ImageFit.Contain) //保持宽高比进行缩小或者放大
.width('100%')
.borderRadius(16)
.padding({ top: 11, left: 16, right: 16 })
.onClick(() => {
this.clickToDetailPage(item)
})
}, (item: BannerClass) => item.id)
}
//autoPlay控制是否自动轮播子组件,loop属性控制是否循环播放,indicator属性自定义导航点的位置和样式
.autoPlay(true)
.loop(true)
.indicator(
new DotIndicator()
.color('#1a000000')
.selectedColor('#0A59F7'))
}
}
bufferToString.ets
import { util } from '@kit.ArkTS';
//由于ResourceManager获取到的是Uint8Array类型的内容,所以需要将对应的内容转换为字符串,并将字符串解析为对应的数据结构。考虑到其他的文件也会使用这个公共方法,可以新建一个util文件夹,并创建一个BufferUtil文件,实现这个字符串转换方法
export function bufferToString(buffer: Uint8Array): string {
let textDecoder = util.TextDecoder.create('utf-8', {
ignoreBOM: true
});
let resultPut = textDecoder.decodeToString(buffer);
return resultPut;
}
BannerClass.ets
export class BannerClass {
id: string = '';
imageSrc: string = '';
url: string = ''
constructor(id: string, imageSrc: string, url: string) {
this.id = id
this.imageSrc = imageSrc;
this.url = url;
}
}
EnablementView.ets
import { ArticleClass } from '../model/ArticleClass';
import { bufferToString } from '../util/BufferUtil';
@Component
export struct EnablementView {
@State enablementList: ArticleClass[] = [];
@Consume('articlePathStack') articlePathStack: NavPathStack;
aboutToAppear(): void {
----------
this.getEnablementDataFromJSON()
}
getEnablementDataFromJSON() {
getContext(this).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, right: 16 })
.width('100%')
Grid() {
ForEach(this.enablementList, (item: ArticleClass) => {
GridItem() {
EnablementItem({ enablementItem: item })
.onClick(() => {
this.articlePathStack.pushPathByName('articleDetail', item)
})
}
}, (item: ArticleClass) => item.id)
}
.rowsTemplate('1fr')
.columnsGap(8)
.scrollBar(BarState.Off)
.height(169)
.padding({ top: 2, left: 16, right: 16 })
}
.margin({ top: 18 })
}
}
@Component
export struct EnablementItem {
@Prop enablementItem: ArticleClass;
build() {
Column() {
Image($r(this.enablementItem.imageSrc))
.width('100%')
//设置填充效果为cover模式,即保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界
.objectFit(ImageFit.Cover)
.height(96)
.borderRadius({
topLeft: 16,
topRight: 16
})
Text(this.enablementItem.title)
.height(19)
.width('100%')
.fontSize(14)
.textAlign(TextAlign.Start)
//textOverFlow属性设置文本超长时的显示方式,在这里我们设置它的值为Ellipsis,表示超长时使用省略号替代
.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(160)
.height(169)
.borderRadius(16)
.backgroundColor(Color.White)
}
}
TutorialView.ets
import { bufferToString } from '../util/BufferUtil';
import { ArticleClass } from '../model/ArticleClass';
@Component
export struct TutorialView {
@State tutorialList: ArticleClass[] = [];
@Consume('articlePathStack') articlePathStack: NavPathStack;
aboutToAppear(): void {
this.getTutorialDataFromJSON()
}
getTutorialDataFromJSON() {
getContext(this).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, right: 16 })
.width('100%')
List({ space: 12 }) {
ForEach(this.tutorialList, (item: ArticleClass) => {
ListItem() {
TutorialItem({ tutorialItem: item })
.onClick(() => {
this.articlePathStack.pushPathByName('articleDetail', item)
})
}
}, (item: ArticleClass) => item.id)
}
.scrollBar(BarState.Off)
.padding({ left: 16, right: 16 })
}
.margin({ top: 18 })
.alignItems(HorizontalAlign.Start)
}
}
@Component
export 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,表示它们在任意尺寸的设备下自适应占满剩余空间
.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)
}
}
ArticleClass.ets
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;
}
}
ArticleDetailPage.ets
import { webview } from '@kit.ArkWeb';
import { ArticleClass } from '../model/ArticleClass'
@Component
export struct ArticleDetailPage {
@State webviewController: webview.WebviewController = new webview.WebviewController;
@Consume('articlePathStack') articlePathStack: NavPathStack;
//在其中进行数据结构的定义,该页面在之后会接收到一个ArticleClass类型的数据,由于之后会使用路由栈进行传参所以此处使用@State进行定义
@State articleDetail: ArticleClass | null = null;
aboutToAppear(): void {
this.articleDetail = this.articlePathStack.getParamByName('articleDetail')[0] as ArticleClass;
}
build() {
NavDestination() {
Column() {
Row() {
Row() {
Image($r('app.media.ic_back'))
.width(40)
.height(40)
.onClick(() => {
//使用路由栈的pop方法来实现页面返回
this.articlePathStack.pop()
})
Row() {
Text(this.articleDetail?.title)
.fontFamily('HarmonyHeiTi-Bold')
.fontSize(20)
.textAlign(TextAlign.Start)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
.fontWeight(700)
.margin({ left: 8 })
}
}
.width('80%')
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.height(56)
WebComponent({ articleDetail: this.articleDetail, webviewController: this.webviewController })
}
.padding({ left: 16, right: 16 })
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.hideTitleBar(true)
}
}
@Component
struct WebComponent {
@Prop articleDetail: ArticleClass | null;
@Prop webviewController: WebviewController;
build() {
Column() {
Web({ src: this.articleDetail?.webUrl, controller: this.webviewController })
.darkMode(WebDarkMode.Auto)
.domStorageAccess(true)
.zoomAccess(true)
.fileAccess(true)
.mixedMode(MixedMode.All)
.cacheMode(CacheMode.None)
.javaScriptAccess(true)
.width('100%')
.layoutWeight(1)
}
}
}
4、课程学习模块
CourseLearning.ets
import { webview } from '@kit.ArkWeb';
@Component
export struct CourseLearning {
//创建webviewController,开发者后续可以通过该Controller控制Web组件加载的界面
private webviewController: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
//加载本地界面,修改Web组件的src属性,使用$rawfile加载刚刚放入rawfile目录下的course_learning资源
Web({ src: $rawfile('course_learning/index.html'), controller: this.webviewController })
//设置domStorageAccess属性,开启文档对象模型存储接口权限
.domStorageAccess(true)
}
}
}
5、知识地图模块
KnowledgeMap.ets
import { KnowledgeMapContent, Section } from '../view/KnowledgeMapContent';
import { NavBarItem, NavBarItemType } from '../view/NavBarItem';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
@Component
export struct KnowledgeMap {
@State navBarList: NavBarItemType[] = [
{ order: '01', title: '准备与学习' },
{ order: '02', title: '构建应用' },
{ order: '03', title: '应用测试' },
{ order: '04', title: '上架' },
{ order: '05', title: '运营增长' },
{ order: '06', title: '商业变现' },
{ order: '07', title: '更多' }
];
//在KnowledgeMap页面中定义路由栈。我们通过创建NavPathStack实例并定义为@Provide类型的状态变量来进行路由栈的定义。Navigation有属于自己的路由栈,可以用于路由的前进、回退、路由历史记录的保存、路由参数的保存与获取。所以首先我们需要创建一个路由栈。
//注意此处采用@Provide的方式进行定义,由于之后会将该路由栈的数据传递给跳转后的后代组件,为了方便后代组件的获取,所以此处采用了@Provide的方式进行定义。
@Provide('knowledgeMapPageStack') knowledgeMapPageStack: NavPathStack = new NavPathStack();
@State currentNavBarIndex: number = -1;
@State sections: Section[] = [];
private getSections() {
try {
getContext(this).resourceManager.getRawFileContent("MapData.json", (error: BusinessError, value: Uint8Array) => {
const textDecoder = util.TextDecoder.create("utf-8");
const res = textDecoder.decodeWithStream(value, { stream: false });
this.sections = JSON.parse(res);
});
} catch (error) {
console.error(`callback getRawFileContent failed, error is ${JSON.stringify(error)}`)
}
}
//在KnowledgeMap组件内定义getSections函数,使用util类来实现将json文件转化为前端数据。具体实现参考右侧代码。最终将getSections函数放入组件的aboutToAppear生命周期内,创建自定义组件的新实例后可执行getSections函数
aboutToAppear(): void {
this.getSections();
}
@Builder
PageMap(name: string) {
if (name === 'KnowledgeMapContent') {
KnowledgeMapContent({ section: this.sections[this.currentNavBarIndex] });
}
}
build() {
//绑定路由栈到Navigation组件
Navigation(this.knowledgeMapPageStack) {
Scroll() {
Column() {
Text('知识地图')
.fontFamily('HarmonyHeiTi-Bold')
.fontSize(24)
.fontColor(Color.Black)
.textAlign(TextAlign.Start)
.lineHeight(33)
.fontWeight(700)
.width('100%')
Image($r("app.media.knowledge_map_banner"))
.width('100%')
.borderRadius(16)
.margin({ top: 19, bottom: 8 })
Text('通过循序渐进的学习路径,无经验和有经验的开发者都可以轻松掌握ArkTS语言声明式开发范式,体验更简洁、更友好的HarmonyOS应用开发旅程。')
.fontFamily('HarmonyHeiTi')
.fontSize('14vp')
.fontColor('rgba(0,0,0,0.60)')
.fontWeight(400)
.textAlign(TextAlign.Start)
List({ space: 12 }) {
ForEach(this.navBarList, (item: NavBarItemType, index: number) => {
ListItem() {
NavBarItem({ navBarItem: item, currentNavBarIndex: this.currentNavBarIndex })
}
.width('100%')
}, (item: NavBarItemType): string => item.title)
}
.width('100%')
.margin({ top: 24 })
}
.padding({
top: 12,
right: 16,
bottom: 12,
left: 16
})
}
.backgroundColor('#F1F3F5')
.align(Alignment.TopStart)
//此处添加constraintSize并设置minHeight为100%,可以解决当内容条数不足时,Scroll组件滚动时会出现空白区域的错误效果
.constraintSize({ minHeight: '100%' })
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.scrollBarColor(Color.Gray)
//edgeEffect用于设置边缘滑动效果,设置为EdgeEffect.Spring表示设置为弹性物理动效。该效果滑动到边缘后可以根据初始速度或通过触摸事件继续滑动一段距离,松手后回弹
.edgeEffect(EdgeEffect.Spring)
}
//控制导航栏显示模式。可以通过Navigation的mode属性控制导航栏的显示模式,该属性支持以下的取值:
//将mode属性为NavigationMode.Auto,为自适应模式,即当设备宽度大于520vp时,Navigation组件采用分栏模式,反之采用单页面模式。
//将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。
//将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分栏显示模式。
//结合之前的效果图,在手机设备上为单页面显示模式,所以我们设置mode属性并传入NavigationMode.Stack参数。
.mode(NavigationMode.Stack)
.hideTitleBar(true)
//关联NavDestination组件与Navigation组件。我们需要关联NavDestination组件与Navigation组件,才能在Navigation组件中使用页面名称的方式来进行组件路由。具体的关联方式为使用Navigation的navDestination属性,该属性支持传入一个自定义构建函数,我们可以使用之前定义的PageMap来作为参数传入。
.navDestination(this.PageMap)
.navBarWidth(288)
}
}
NavBarItem.ets
export interface NavBarItemType {
order: string,
title: string
}
@Component
export struct NavBarItem {
@Consume('knowledgeMapPageStack') knowledgeMapPageStack: NavPathStack;
//由于此处只需要单向的从外部获得参数并进行渲染,后续将改为@Prop;@State是双向的
@Prop navBarItem: NavBarItemType;
@Link currentNavBarIndex: number;
build() {
Row() {
Text(this.navBarItem.order)
.margin({ right: 6 })
.fontFamily('HarmonyHeiTi-Bold')
.fontSize(21)
.fontColor('#182431')
.textAlign(TextAlign.Start)
.lineHeight(22)
.fontWeight(700)
Text(this.navBarItem.title)
.fontFamily('HarmonyHeiTi-Medium')
.fontSize(16)
.fontColor('#182431')
.textAlign(TextAlign.Start)
.lineHeight(22)
.fontWeight(500)
Blank()
Image($r('app.media.ic_arrow'))
.width(12)
.height(24)
}
.width('100%')
.height(48)
.borderRadius(16)
.alignItems(VerticalAlign.Center)
.padding({ left: 12, right: 12 })
.backgroundColor(
this.currentNavBarIndex === Number(this.navBarItem.order) - 1 ?
'#1A0A59F7' :
Color.Transparent
)
.onClick(() => {
const index = Number(this.navBarItem.order) - 1;
this.currentNavBarIndex = index;
//实现路由跳转。通过自定义导航条的点击事件实现路由Navigation组件的跳转。首先通过@Consume获取到祖代组件传递过来的路由栈数据,然后使用该路由栈实现路由的跳转,Navigation支持的跳转方式主要有两种,分别为push的方式与replace的方式。
//pushPath()/pushPathByName():根据传入的参数将参数对应的NavDestination页面信息入栈。
//replacePath()/replacePathByName():将当前页面栈栈顶退出,再根据传入参数将对应的NavDestination页面信息入栈。
//现在给NavBar加上点击事件用于路由跳转,根据需求,此处采用replacePath的方式,该方法需要传入一个NavPathInfo类型的对象,可以包含路由的页面名称,携带的参数等信息,此处只需要用到路由名称。
this.knowledgeMapPageStack.replacePath({ name: 'KnowledgeMapContent' });
})
}
}
KnowledgeMapContent.ets
interface KnowledgeBaseItem {
type: string,
title: string
}
interface Material {
subtitle: string,
knowledgeBase: KnowledgeBaseItem[]
}
export interface Section {
title: string,
brief: string,
materials: Material[]
}
//定义类型与图标的映射。此时图标已经放入media目录下,我们需要考虑如何设计来映射type与图标。可以使用Record类型来进行映射,也可以使用Map进行映射,此处采用Record类型
const TypeMapIcon: Record<string, string> = {
'指南': 'app.media.ic_guide',
'准备': 'app.media.ic_prepare',
'学习与获取证书': 'app.media.ic_medals',
'视频教程': 'app.media.ic_video',
}
@Component
export struct KnowledgeMapContent {
@Prop section: Section;
scroller: Scroller = new Scroller();
@Builder
KnowledgeBlockLine(knowledgeBaseItem: KnowledgeBaseItem) {
Row() {
Image($r(TypeMapIcon[knowledgeBaseItem.type]))
.width(20)
.height(20)
Column() {
Text(knowledgeBaseItem.title)
.fontFamily('HarmonyHeiTi-Medium')
.fontSize(16)
.fontWeight(500)
Text(knowledgeBaseItem.type)
.fontFamily('HarmonyHeiTi')
.fontSize(14)
.fontWeight(400)
}
//Column组件容器的alignItems属性默认为HorizontalAlign.Center。这里我们要实现起始端对齐的效果,因此这里的属性参数设定为HorizontalAlign.Start
.alignItems(HorizontalAlign.Start)
.margin({ left: 18 })
Blank()
Image($r('app.media.ic_arrow'))
.width(12)
.height(24)
}
.width('100%')
.height(64)
.alignItems(VerticalAlign.Center)
}
@Builder
KnowledgeBlock(material: Material) {
Column() {
Text(material.subtitle)
.fontFamily('HarmonyHeiTi-Medium')
.fontSize(14)
.fontWeight(500)
.margin({ bottom: 8 })
List({ space: 12 }) {
ForEach(material.knowledgeBase, (item: KnowledgeBaseItem, index: number) => {
this.KnowledgeBlockLine(item)
}, (item: KnowledgeBaseItem, index: number) => item.title + index)
}
.backgroundColor(Color.White)
.borderRadius(16)
.padding({ left: 12, right: 12 })
.divider({
strokeWidth: 0.5,
startMargin: 38,
endMargin: 0,
color: '#F2F2F2'
})
}
.width('100%')
.margin({ top: 28 })
.alignItems(HorizontalAlign.Start)
}
build() {
NavDestination() {
Scroll(this.scroller) {
Column() {
Text(this.section?.title)
.fontFamily('HarmonyHeiTi-Bold')
.fontSize(20)
.fontWeight(700)
.fontColor(Color.Black)
Text(this.section?.brief)
.fontFamily('HarmonyHeiTi')
.fontSize(12)
.fontColor('rgba(0,0,0,0.60)')
.textAlign(TextAlign.JUSTIFY)
.fontWeight(400)
.margin({ top: 12 })
ForEach(this.section?.materials, (material: Material) => {
this.KnowledgeBlock(material)
}, (material: Material, index: number) => material.subtitle + index)
}
.padding({
left: 24,
top: 12,
right: 24,
bottom: 12
})
.alignItems(HorizontalAlign.Start)
}
.align(Alignment.TopStart)
.constraintSize({ minHeight: '100%' })
.edgeEffect(EdgeEffect.Spring)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.backgroundColor('#F1F3F5')
}
//在组件路由导航中,内容区都需要使用NavDestination组件进行包裹,否则会导致点击后内容区为白屏,所以需要在features/map/src/main/ets/view/KnowledgeMapContent.ets文件中build函数内最外层添加一层NavDestination组件,同样的,在数据驱动UI更新小节中我们已经自定义了标题栏,可以关闭NavDestination默认的标题栏(hideTitleBar)。
.hideTitleBar(true)
}
}