鸿蒙元服务项目实战:备忘录UI页面开发

简介: UI页面绘制没什么好说的,就是组件的位置摆放,和组件的显示逻辑,有很多的属性并没有文章记录,大家可以去仓库中查看即可,文章中用到了我的一个标题栏组件,如果大家不想用,可以使用自己写的即可。

前言


代码运行环境:全部基于HarmonyOs NEXT

DevEco Studio:Build Version: 5.0.3.900

API:12

modelVersion5.0.0


之前写过一篇关于元服务项目的上架流程,为了更好的了解及开发元服务,准备从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页面绘制没什么好说的,就是组件的位置摆放,和组件的显示逻辑,有很多的属性并没有文章记录,大家可以去仓库中查看即可,文章中用到了我的一个标题栏组件,如果大家不想用,可以使用自己写的即可。

相关文章
|
4月前
|
开发框架 前端开发 JavaScript
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发一
本文介绍了方舟开发框架(ArkUI)及其两种开发范式:基于ArkTS的声明式开发范式和类Web开发范式。ArkUI是用于构建HarmonyOS应用界面的UI框架,提供极简UI语法和基础设施。声明式开发范式使用ArkTS语言,以组件、动画和状态管理为核心,适合复杂团队协作;类Web开发范式采用HML、CSS、JavaScript三段式开发,适用于简单界面应用,贴近Web开发者习惯。文中还概述了两者的架构和基础能力,帮助开发者选择合适的范式进行高效开发。
147 15
|
4月前
|
编解码 前端开发 Java
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发三
本文介绍了基于声明式UI范式的图形绘制与动画效果实现方法,涵盖绘制图形、添加动画效果及常见组件说明三部分内容。在绘制图形部分,详细讲解了如何通过Circle组件为食物成分表添加圆形标签,以及使用Path组件结合SVG命令绘制自定义图形(如应用Logo)。动画效果部分则展示了如何利用animateTo实现闪屏动画,包括渐出、放大效果,并设置页面跳转;同时介绍了页面间共享元素转场动画的实现方式。最后,文章列举了声明式开发范式中的各类组件及其功能,帮助开发者快速上手构建复杂交互页面。
142 11
|
3月前
|
开发者 容器
44.[HarmonyOS NEXT RelativeContainer案例一] 掌握组件锚点布局:打造灵活精准的UI定位系统
在HarmonyOS NEXT的UI开发中,精确控制组件位置是构建复杂界面的关键。RelativeContainer作为一种强大的布局容器,通过锚点系统提供了精确定位能力,使开发者能够创建出灵活且精准的UI布局。本教程将详细讲解如何使用RelativeContainer的锚点布局功能,帮助你掌握这一核心技术。
110 4
|
4月前
|
开发者 容器
【HarmonyOS Next之旅】ArkTS语法(二) -> 动态构建UI元素
当开发者创建自定义组件,并想对该组件添加特定功能时(例如在自定义组件中添加一个点击跳转操作)。为解决此问题,引入了@BuilderParam装饰器,此装饰器修饰的属性值可为@Builder装饰的函数,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。@BuilderParam装饰器用于修饰自定义组件内函数类型的属性(例如:@BuilderParam noParam: () => void),并且在初始化自定义组件时被@BuilderParam修饰的属性必须赋值。
128 11
|
3月前
|
设计模式 缓存 容器
06.HarmonyOS Next UI进阶:Text组件与视觉样式完全指南
在HarmonyOS Next应用开发中,Text组件是最基础也是最常用的UI元素之一。它不仅用于显示文本内容,还可以通过丰富的样式属性实现各种视觉效果。掌握Text组件的样式设置,是构建精美UI界面的基础技能。
157 1
|
5天前
|
存储 开发者 容器
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
本文介绍了ArkTS语言中的Class类、泛型、接口、模块化、自定义组件及状态管理等核心概念,并结合代码示例讲解了对象属性、构造方法、继承、静态成员、访问修饰符等内容,同时涵盖了路由管理、生命周期和Stage模型等应用开发关键知识点。
49 0
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
|
5天前
|
JavaScript 前端开发 开发者
鸿蒙应用开发从入门到实战(六):ArkTS声明式UI和组件化
鸿蒙开发语言ArkTS在继承了Typescrip语法的基础上,主要扩展了声明式UI开发相关的能力。
|
3月前
|
JavaScript 前端开发 UED
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发四
本文介绍了Web组件开发与性能优化的相关内容。在Web组件开发部分,涵盖创建组件、设置样式与属性、添加事件和方法以及场景示例,如动态播放视频。性能提升方面,推荐使用数据懒加载、条件渲染替代显隐控制、Column/Row替代Flex、设置List组件宽高及调整cachedCount减少滑动白块等方法,以优化应用性能与用户体验。
171 56
|
3月前
|
编解码 UED 开发者
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发之常见布局
本文主要介绍了自适应布局与响应式布局的相关内容。自适应布局部分涵盖线性布局、层叠布局、弹性布局和网格布局,详细说明了各布局的特性及使用方法,例如线性布局中的排列、拉伸与缩放,弹性布局的方向、换行与对齐方式等。响应式布局则重点讲解了栅格系统和媒体查询,阐述如何通过栅格组件和媒体查询条件实现不同设备上的适配效果。这些技术帮助开发者灵活应对多尺寸屏幕的设计需求,提升用户体验。
199 55
|
4月前
|
存储 开发框架 API
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发二
本文详细介绍了基于声明式UI开发的健康饮食应用的设计与实现过程。内容涵盖从基础环境搭建到复杂功能实现的全流程,包括创建简单视图、构建布局(如Stack、Flex)、数据模型设计、列表与网格布局构建,以及页面跳转和数据传递等核心功能。 本文通过实际案例深入浅出地解析了声明式UI开发的关键技术和最佳实践,为开发者提供了宝贵的参考。
147 14