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

前言

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

标题栏组件:

https://ohpm.openharmony.cn/#/cn/detail/@abner%2Fbar

文章系列

鸿蒙开发:如何上架一个元服务应用

项目地址:https://gitee.com/abnercode/harmony-easy-recording

备注:目前随心记元服务项目已经上架,大家可以在应用商店搜索随心记即可体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/938505.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux-ubuntu点LED灯C语言版

​ 一,C语言点灯 1.寄存器配置 设置为SVC模式,复用寄存器设置GPIO1-IO003,设置电气属性,设置为输出模式。 2.软件 汇编语言对模式设置,并且将堆栈指针指向主程序: .global _start_start: /*设置为svr模式 */mrs …

SLM510A系列——24V,15到150mA单通道可调电流线性恒流LED驱动芯片

SLM510A 系列产品是单通道、高精度、可调电流线性恒流源的 LED 驱动芯片,在各种 LED 照明产品中非常简单易用。其在宽电压输入范围内,能保证极高的输出电流精度,从而在大面积的光源照明中,都能让 LED 照明亮度保持均匀一致。 由于…

【JavaEE】网络(2)

一、网络编程套接字 1.1 基础概念 【网络编程】指网络上的主机,通过不同的进程,以编程的方式实现网络通信;当然,我们只要满足进程不同就行,所以即便是同一个主机,只要是不同进程,基于网络来传…

【Java数据类型学习——String】

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​💫个人格言:“没有罗马,那就自己创造罗马~” 文章目录 打印字符串长度的两种方法字符串String的比较1.用于比较引用的对象是否指向同一个内存地址2.用equa…

基于Spring Boot的校园部门资料管理系统

一、系统背景与目的 随着信息技术的飞速发展,校园信息化建设成为必然趋势。学校各部门在日常工作中积累了大量的资料,包括教学资料、学生档案、科研成果、行政文件等。传统的纸质资料管理方式存在效率低、易丢失、难以检索等问题,无法满足现…

STL 剖析

STL 六大组件 「STL 六大组件的交互关系」 Container 通过 Allocator 取得数据储存空间Algorithm 通过 Iterator 存取 Container 内容Functor 可以协助 Algorithm 完成不同的策略变化Adapter 可以修饰或套接 Functor、Iterator 配置器(allocator) 配置器:负责空间…

企业网络构建:如何满足业务需求与提升效率

企业组网指通过网络将企业内部的各种设备(如电脑、打印机和服务器等)连接起来,实现资源共享、信息交流与协同办公的过程。要打造一个高效的企业网络,需要从安全性、可靠性、稳定性和性能等多个方面进行综合考虑。以下内容将详细解…

升级thinkphp8最新版本,升级后发现版本不变

升级thinkphp8.0.3最新版本8.1.1,升级后发现版本不变, 更新TP有两个方法 1 全部更新(所有插件都一起更新) composer update 2 只更新TP框架核心 composer update topthink/framework 造成可能有两个原因,一是缓存问题,二是更新…

Cesium进阶教程——自定义图形、外观、绘图基础、现有着色器移植至Cesium、ShadowMapping、视频GIS、模型压平、卷帘

基础必看 WEBGL基础(从渲染管线角度解读) 参考路线 http://www.xt3d.online/tutorial/further/article.html 自定义图形 https://blog.csdn.net/m0_55049655/article/details/138908327 https://blog.csdn.net/m0_55049655/article/details/140306837 …

理解数据结构 hashtable的简易理解思路

结构图 为了方便演示,下图中分区算法为下标取模 private int hashFun(int id) {//使用 hash并取模return id % size;}Hashtable的结构如图所示:是一个数组(元素为各个链表的表头) 多个链表组成,也就是说 hashtable 结…

【YashanDB知识库】kettle同步PG至崖山提示no encryption pg_hba.conf记录

【问题分类】数据导入导出 【关键字】数据同步,kettle,数据迁移,pg_hba.conf 【问题描述】使用kettle同步postgresql至崖山数据库时提示以下报错信息: 【问题原因分析】pg_hba.conf 文件中没有正确配置允许从 IP 地址 连接到数…

记录2024-leetcode-字符串DP

10. 正则表达式匹配 - 力扣(LeetCode)

UE5制作伤害浮动数字

效果演示: 首先创建一个控件UI 添加画布和文本 文本设置样式 添加伤害浮动动画,根据自己喜好调整,我设置了缩放和不透明度 添加绑定 转到事件图表,事件构造设置动画 创建actor蓝图类 添加widget 获取位置 设置位移 创建一个被击中…

【USB-HID】“自动化键盘“

这里写目录标题 【USB-HID】"自动化键盘"1. 前言2. 框架3. 实现3.1 模拟键盘按键输入 【USB-HID】“自动化键盘” 1. 前言 最近从朋友那了解了一种"自动化键盘",能够通过上位机录制按键脚本,然后执行脚本,实现物理键盘…

STM32F407ZGT6-UCOSIII笔记4:时间片轮转调度

本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 编写熟悉一下 UCOSIII系统的 时间片轮转调度 文章提供测试代码讲解、完整工程下载、测试效果图 目录 解决上文的卡系统问题: 使能时间片轮转调度: 任务初始化定义更改: 文件结构…

【Flask+OpenAI】利用Flask+OpenAI Key实现GPT4-智能AI对话接口demo - 从0到1手把手全教程(附源码)

文章目录 前言环境准备安装必要的库 生成OpenAI API代码实现详解导入必要的模块创建Flask应用实例配置OpenAI API完整代码如下(demo源码)代码解析 利用Postman调用接口 了解更多AI内容结尾 前言 Flask作为一个轻量级的Python Web框架,凭借其…

搭建springmvc项目

什么是springmvc MVC它是一种设计理念。把程序按照指定的结构来划分: Model模型 View视图 Controller控制层 springmvc框架是spring框架的一个分支。它是按照mvc架构思想设计的一款框架。 springmvc的主要作用: 接收浏览器的请求数据,对数据进行处理,…

Three.js相机Camera控件知识梳理

原文:https://juejin.cn/post/7231089453695238204?searchId20241217193043D32C9115C2057FE3AD64 1. 相机类型 Three.js 主要提供了两种类型的相机:正交相机(OrthographicCamera)和透视相机(PerspectiveCamera&…

为“行车大脑”降温:Simdroid-EC助力汽车ECU设计研发

ECU(Electronic Control Unit,电子控制单元)被誉为汽车的行车大脑,在工作时会产生大量的热量,而其散热存在以下难题:一是工作环境恶劣,ECU常处于高温环境中;二是ECU所处的空间较为狭…

改进系列(6):基于DenseNet网络添加TripletAttention注意力层实现的番茄病害图像分类

目录 1. DenseNet 介绍 2. TripletAttention 3. DenseNet TripletAttention 4. 番茄场景病害病虫识别 4.1 数据集情况 4.2 训练 4.3 训练结果 4.4 推理 1. DenseNet 介绍 DenseNet是一种深度学习架构,卷积神经网络(CNN)的一种变体&…