前言
之前写过一篇关于元服务项目的上架流程,为了更好的了解及开发元服务,准备从0到1简单开发一个小项目,也希望能够帮助到刚刚介入到鸿蒙开发的同学,具体项目呢,也是十分的简单,就是一个小巧的备忘录项目,可以编辑内容,可以展示已经编辑好的内容列表,开发上很快,一般半天到一天就可以搞定。
之所以选择这样的一个项目,最大的原因是不需要联网操作,数据都是本地的存储,方便个人开发者进行开发和后续的上架。
我们先看下最终要实现的效果:
主页,无数据状态
主页,有数据状态
编辑页面
由于是项目开发前的开篇第一篇,本篇文章会带着大家把基本的UI进行绘制了。
首页UI
首页UI非常的简单,从上到下的结构依次是,标题栏,搜索框,备忘录列表;页面UI排版中,我们可以选择Column组件作为根布局,然后从上到下依次排开,因为有编辑按钮,还有空数据时的缺省页面,这里建议直接使用RelativeContainer组件,当然了,这并不是唯一的布局方式。
标题栏,大家可以使用Text组件设置,搜索使用Search组件,列表的话,直接使用List组件即可,至于缺省组件,需要根据是否有备忘录数据来动态的展示。
完整的代码如下:
RelativeContainer() { Column() { ActionBar({ title: "随心记" }) Search({ placeholder: "搜索……" }) .margin({ left: 10, right: 10, top: 10 }) List({ space: 10 }) { ForEach(this.mListContentBean, (item: ListContentBean, index: number) => { ListItem() { Column() { Text(item.time) .width("100%") .textAlign(TextAlign.End) .margin({ top: 5, right: 5 }) Text(item.title) .fontWeight(FontWeight.Bold) .width("100%") Text(item.desc) .width("100%") .margin({ top: 10, bottom: 10 }) } .width("100%") .height(100) .padding({ top: 5, bottom: 5, left: 10, right: 10 }) .backgroundColor(item.bgColor == undefined ? "#e8e8e8" : item.bgColor) .borderRadius(10) }.swipeAction({ end: { builder: () => { this.swipeDelete(this, item.bgColor == undefined ? "#e8e8e8" : item.bgColor, item.id?.toString(), index) }, actionAreaDistance: 80, } }) .onClick(() => { //点击条目,跳转 }) }) } .width("100%") .layoutWeight(1) .padding({ left: 10, right: 10 }) .margin({ top: 10 }) } Text("+") .width(60) .height(60) .backgroundColor("#FB553C") .borderRadius(50) .fontColor(Color.White) .fontSize(45) .textAlign(TextAlign.Center) .margin({ right: 20, bottom: 20 }) .alignRules({ bottom: { anchor: "__container__", align: VerticalAlign.Bottom }, right: { anchor: "__container__", align: HorizontalAlign.End } }) .onClick(() => { //点击跳转编辑页面 }) //缺省提示 Text("暂时没有笔记,赶紧添加一条吧~") .fontWeight(FontWeight.Bold) .visibility(this.isContentEmpty ? Visibility.Visible : Visibility.None) .alignRules({ center: { anchor: "__container__", align: VerticalAlign.Center }, middle: { anchor: "__container__", align: HorizontalAlign.Center } }) }.height('100%') .width('100%')
编辑页UI
编辑页面相对来说,稍稍复杂一些,除了内容编辑组件之外,增加了顶部的换肤,还有底部的一排样式设置,同样根布局也是使用的RelativeContainer组件。
绘制编辑页面UI,有两个需要注意的,一个是底部的一排样式按钮,需要根据软键盘的高度动态的设置位置,当然了本篇仅仅是UI绘制,我们后续的篇章在说。另一个就是样式列表,比如文本颜色,文本大小,皮肤列表等等,需要做动态的显示盒隐藏。
如何进行样式效果的动态显示呢?很简单,你可以把样式效果先写好,然后先隐藏,当点击的时候,进行显示即可,比如顶部的换肤,你可以先把换肤的UI写好。
Column() { List() { ForEach(this.skinColors, (item: string, index: number) => { ListItem() { Column() { Image($r("app.media.complete")) .width(20) .height(20) .visibility(this.clickSkinPosition == index ? Visibility.Visible : Visibility.None) .border({ width: 1, color: "#666666", radius: 20 }) .margin({ bottom: 20 }) } .width(100) .height(160) .backgroundColor(item) .justifyContent(FlexAlign.End) .borderRadius(5) .margin({ left: 10 }) .onClick(() => { this.clickSkinPosition = index this.clickSkinColorValue = item this.isClickSkin = !this.isClickSkin }) } }) } .width("100%") .height(180) .backgroundColor("#e8e8e8") .listDirection(Axis.Horizontal) .padding({ top: 10 }) .scrollBar(BarState.Off) } .backgroundColor(Color.Transparent) .width("100%") .height("100%") .onClick(() => { this.isClickSkin = !this.isClickSkin }) .visibility(this.isClickSkin ? Visibility.Visible : Visibility.None) .alignRules({ top: { anchor: "bar", align: VerticalAlign.Bottom }, })
点击换肤按钮的时候,进行显示换肤列表。
//点击换肤 显示背景 this.isClickSkin = !this.isClickSkin
基本效果
编辑页面所有UI代码
RelativeContainer() { ActionBar({ title: "编辑笔记", leftIcon: $r("app.media.complete"), left2Icon: $r("app.media.skin"), leftMenuAttribute: { imageWidth: 22, imageHeight: 22 }, leftMenu2Attribute: { imageWidth: 22, imageHeight: 22, imageMargin: { left: 20 } }, onLeftImageClick: (position) => { if (position == 0) { //点击返回 router.back() } else { //点击换肤 显示背景 this.isClickSkin = !this.isClickSkin } } }).id("bar") Column() { TextInput({ placeholder: "请输入笔记标题……", text: $$this.title }) .backgroundColor(Color.Transparent) .placeholderFont({ weight: FontWeight.Bold, size: 15 }) .placeholderColor("#666666") .fontSize(18) .maxLength(50) .fontColor("#222222") .fontWeight(FontWeight.Bold) .caretColor(Color.Red)//光标的颜色 .padding(10) .borderRadius(0) .margin({ top: 10 }) Text() { Span(this.nowTime) } .width("100%") .fontSize(13) .fontColor("#666666") .padding({ left: 10 }) .margin({ top: 10 }) RichEditor(this.options) .onReady(() => { //获取当前的时间 this.nowTime = this.getDateTime() this.nowInterval = setInterval(() => { this.nowTime = this.getDateTime() }, 1000) }) .placeholder("随心记,记录点点滴滴……", { fontColor: "#666666" }) .caretColor(Color.Red) .padding(10) .margin({ top: 10 }) .onSelect((value: RichEditorSelection) => { this.start = value.selection[0]; this.end = value.selection[1]; }) } .alignRules({ top: { anchor: "bar", align: VerticalAlign.Bottom }, bottom: { anchor: "bottom_bar", align: VerticalAlign.Top } }).margin({ bottom: 80 }) Column() { List({ space: 10 }) { ForEach(this.fontColors, (item: ResourceColor) => { ListItem() { Text() .width(20) .height(20) .backgroundColor(item) .borderRadius(20) .border({ width: 1, color: "#e8e8e8" }) .onClick(() => { this.clickStyleColorValue = item this.changeStyle() this.setFontColor() }) } }) } .width("100%") .height(30) .listDirection(Axis.Horizontal) .padding({ left: 10, right: 10 }) .scrollBar(BarState.Off) .visibility(this.isClickStyleColor ? Visibility.Visible : Visibility.None) List({ space: 10 }) { ForEach(this.fontSizes, (item: string, index: number) => { ListItem() { Text(item) .height(20) .borderRadius(20) .fontColor(Color.Black) .fontWeight(FontWeight.Bold) .onClick(() => { let fontSize = 15 if (index == 0) { fontSize = 21 } else if (index == 1) { fontSize = 20 } else if (index == 2) { fontSize = 19 } else if (index == 3) { fontSize = 18 } else if (index == 4) { fontSize = 17 } else if (index == 5) { fontSize = 16 } else if (index == 6) { fontSize = 15 } else { fontSize = Number(item) } this.clickStyleSizeValue = fontSize this.changeStyle() //设置文字大小 this.setFontSize() }) } }) } .width("100%") .height(30) .listDirection(Axis.Horizontal) .padding({ left: 10, right: 10 }) .scrollBar(BarState.Off) .visibility(this.isClickStyleSize ? Visibility.Visible : Visibility.None) Row() { Image($r("app.media.font_size")) .onClick(() => { this.isClickStyleSize = !this.isClickStyleSize this.setBold() }) .backgroundColor(this.isClickStyleSize ? "#e8e8e8" : Color.Transparent) .width(20) .height(20) Text("B") .onClick(() => { this.isClickStyleB = !this.isClickStyleB this.changeStyle() this.setBold() }) .fontWeight(FontWeight.Bold) .fontSize(20) .backgroundColor(this.isClickStyleB ? "#e8e8e8" : Color.Transparent) .width(30) .height(30) .textAlign(TextAlign.Center) .margin({ left: 20 }) Text("I") .onClick(() => { this.isClickStyleI = !this.isClickStyleI this.changeStyle() this.setStyle() }) .fontWeight(FontWeight.Bold) .fontStyle(FontStyle.Italic) .backgroundColor(this.isClickStyleI ? "#e8e8e8" : Color.Transparent) .fontSize(20) .margin({ left: 20 }) .width(30) .height(30) .textAlign(TextAlign.Center) Image($r("app.media.color_bg")) .onClick(() => { this.isClickStyleColor = !this.isClickStyleColor }) .backgroundColor(this.isClickStyleColor ? "#e8e8e8" : Color.Transparent) .margin({ left: 20 }) .width(20) .height(20) } .width("100%") .height(50) .backgroundColor(this.clickSkinColorValue) .border({ width: { top: 1 }, color: "#e8e8e8" }) .padding({ left: 20, right: 20 }) }.id("bottom_bar") .alignRules({ bottom: { anchor: "__container__", align: VerticalAlign.Bottom } }) //背景颜色 Column() { List() { ForEach(this.skinColors, (item: string, index: number) => { ListItem() { Column() { Image($r("app.media.complete")) .width(20) .height(20) .visibility(this.clickSkinPosition == index ? Visibility.Visible : Visibility.None) .border({ width: 1, color: "#666666", radius: 20 }) .margin({ bottom: 20 }) } .width(100) .height(160) .backgroundColor(item) .justifyContent(FlexAlign.End) .borderRadius(5) .margin({ left: 10 }) .onClick(() => { this.clickSkinPosition = index this.clickSkinColorValue = item this.isClickSkin = !this.isClickSkin }) } }) } .width("100%") .height(180) .backgroundColor("#e8e8e8") .listDirection(Axis.Horizontal) .padding({ top: 10 }) .scrollBar(BarState.Off) } .backgroundColor(Color.Transparent) .width("100%") .height("100%") .onClick(() => { this.isClickSkin = !this.isClickSkin }) .visibility(this.isClickSkin ? Visibility.Visible : Visibility.None) .alignRules({ top: { anchor: "bar", align: VerticalAlign.Bottom }, }) } .height('100%') .width('100%') .height(this.screenHeight) // 动态设置可视区域高度 .backgroundColor(this.clickSkinColorValue) .expandSafeArea([SafeAreaType.SYSTEM, SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
相关总结
UI页面绘制没什么好说的,就是组件的位置摆放,和组件的显示逻辑,有很多的属性并没有文章记录,大家可以去仓库中查看即可,文章中用到了我的一个标题栏组件,如果大家不想用,可以使用自己写的即可。