鸿蒙开发【记事APP】实战案例

1 简介

生活中的零碎信息太容易忘记,「记事APP」作为记录琐碎的小帮手,时刻记录用户关心的内容,分类整理,高效编辑,快捷分享。Less is More,借助HarmonyOS NEXT丰富的原生能力,一步操作完成各种所想,抓住每一刻灵感。

  • 目标用户:学生、记者、产品经理、艺术创作者

本篇将介绍如何使用HarmonyOS NEXT原生能力开发灵感速记APP。效果为:

  • 化繁为简,便捷高效

    • 聊天式一键添加备忘、关联日程、强提醒消息、私密消息等轻量笔记。
    • 可分类、搜索、分享、删除等管理记录的每条内容。
      0
  • 统一生态,有趣好用:

    • 可共享消息到生态互联设备(灵动看板)。

    • 支持多端布局。

      1

2 环境搭建

我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。

软件要求

  • DevEco Studio版本:DevEco Studio NEXT Developer Preview2及以上。
  • HarmonyOS SDK版本:HarmonyOS NEXT Developer Preview2 SDK及以上。

2

硬件要求

  • 设备类型:华为手机。
  • HarmonyOS系统:HarmonyOS NEXT Developer Preview2及以上。

环境搭建

  1. 安装DevEco Studio,详情请参考[下载]和[安装软件]。

  2. 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,详情请参考[配置开发环境]。

  3. 开发者可以参考以下链接,完成设备调试的相关配置:

    • [使用真机进行调试]

3 代码结构解读

本篇文档只对核心代码进行讲解,对于完整代码,开源后提供下载链接。

.
|-- common
|   |-- BreakPointSystem.ets //断点-多端布局
|   |-- CommonConstant.ets   //常量
|   |-- DateUtil.ets         //时间日期工具
|   |-- InspiDataModule.ets  //灵感数据模型
|   |-- Logger.ets           //日志工具
|   |-- calendar.ets         //日历日程工具
|   |-- database             //RDB数据库,实现持久化存储
|   |   |-- InspirationTable.ets
|   |   |-- RDB.ets
|   |   `-- inspirationData.ets
|   |-- messageManager       //后台提醒、日程消息
|   |   |-- BackgroundReminder.ets
|   |   `-- CalenderMeaasge.ets
|   |-- microphone           //语音输入
|   |   `-- Recorder.ets
|   `-- shareTool.ets        //分享工具
|-- entryability
|   `-- EntryAbility.ts   
|-- entryformability
|   `-- EntryFormAbility.ets
|-- pages
|   |-- Index.ets            //入口页面
|   `-- SettingPage.ets      //设置页面
|-- subview                    
|   |-- AddPanel.ets            //添加笔记组件
|   |-- InspiComponent.ets      //笔记列表组件
|   |-- InspirationItemView.ets //单条笔记组件
|   |-- InspirationTotalView.ets//笔记分类视图
`-- widget22
    `-- pages
        `-- Widget22Card.ets    //2*2桌面卡片

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.

4 构建应用主界面

灵感速记应用程序遵循多端、极简页面、功能一触即达的设计理念。以手机端为例,如下图所示,首页布局了95%的功能,自下而上依次是添加和共享笔记、笔记查看、管理笔记的三大功能区。
2

在主页面中使用[断点])来分配布局,例如在手机端首页只展示消息列表视图,在折叠屏或平板上屏幕右侧新增了分类视图。整体的布局框架如下:

 build() {
    Stack() {
      Row() {
        Column() {
          // menu
	  Row(){  ... }
          // inspiration内容
          // 配合List组件,循环渲染,可滚动内容
            List({ space: 6, scroller: this.listScroller }) {
              ForEach(this.Inspirations : this.searchInspirations,
                (item: InspirationData, index: number) => {
                  ListItem() {
                    InspirationItemView({
                      InspiItem: item,
                     ...
                 }) 
            }
      
          if (!this.isSearchPageShow && !this.isInsert && !this.isEditMode) {
            // 添加新内容+按钮
            Image($r('app.media.add_filled'))
            // 共享到灵动看板桌面 弹窗
            Image($r('app.media.panel_icon'))
              .onClick(() => {
                this.isShowDeskPanel = true
              })
        }
        .width(this.MycurrentBreakpoint != 'sm' ? '50%' : '100%') 
	  // 断点来实现多端布局
        if (this.MycurrentBreakpoint != 'sm') {
          Column() {
            // 内容按颜色整理的详情页
            InspirationTotalView({
              Inspirations: this.Inspirations
            })
          }
          .width('50%')
          .height('100%')
        }
      }.width('100%').height('100%')
    }
  }

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.

5 笔记数据管理

数据定义

应用开发与数据结构息息相关,在进行其他功能开发前,规划如下灵感速记的数据,主要包含笔记文本内容、记录时间、类别、完成情况等。

export default class InspirationData {
  id: number = -1;
  inspirationName: string = '';
  updateTime: string = '';
  progressValue: number = 0;
  inspirationType:number = InspirationType.CREATE_IDEA;
  inspirationColor:string = CommonConstants.CREATE_COLOR;
  inspirationDone: boolean = false;
  inspirationAlarm: boolean  = false; 
  inspirationPic: string = '';
  inspirationPicIndex: number = 0;
  inspirationIcon: string ='app.media.create_idea_filled';
  constructor() {
    ...
  }
}

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

数据持久化

用户记录的笔记希望能持久化保存,便于用户随时查找翻阅笔记。
HarmonyOS NEXT中的ArkData ([方舟数据管理])为开发者提供数据存储、数据管理和数据同步能力。其中,数据存储提供通用数据持久化能力,根据数据特点,分为用户首选项、键值型数据库和关系型数据库。灵感速记的数据较为复杂,选择[关系型数据库](RDB)进行存储。

  • 首先获取一个RdbStore,其中包括建库、建表、升降级等操作。
import { relationalStore } from '@kit.ArkData';
export default class Rdb {
  private rdbStore: relationalStore.RdbStore | null = null;
  private tableName: string;
  private sqlCreateTable: string;
  private columns: Array<string>;

  constructor(tableName: string, sqlCreateTable: string, columns: Array<string>) {
    this.tableName = tableName;
    this.sqlCreateTable = sqlCreateTable;
    this.columns = columns;
  }

  getRdbStore(callback: Function = () => {
  }) {
    let context: Context = getContext(this) as Context;
    relationalStore.getRdbStore( AppStorage.get('context'), CommonConstants.STORE_CONFIG, (err, rdb) => {
     ...
      this.rdbStore = rdb;
      this.rdbStore.executeSql(this.sqlCreateTable);
    });
  }

  insertData(data: relationalStore.ValuesBucket, callback: Function = () => {
  }) {
  
    let resFlag: boolean = false;
    const valueBucket: relationalStore.ValuesBucket = data;
    if (this.rdbStore) {
      this.rdbStore.insert(this.tableName, valueBucket, (err, ret) => {
        ...
      });
    }
  }

  deleteData(predicates: relationalStore.RdbPredicates, callback: Function = () => {
  }) {
    let resFlag: boolean = false;
    if (this.rdbStore) {
      this.rdbStore.delete(predicates, (err, ret) => {
...
      });
    }
  }

  updateData(predicates: relationalStore.RdbPredicates, data: relationalStore.ValuesBucket, callback: Function = () => {
  }) {
   
    let resFlag: boolean = false;
    const valueBucket: relationalStore.ValuesBucket = data;
    if (this.rdbStore) {
      this.rdbStore.update(valueBucket, predicates, (err, ret) => {
       ...
      });
    }
  }

  query(predicates: relationalStore.RdbPredicates, callback: Function = () => {
  }) {
    
    if (this.rdbStore) {
      this.rdbStore.query(predicates, this.columns, (err, resultSet) => {
       ..
      });
    }
  }
}

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.
  • 其次创建灵感速记笔记的RDB数据表,赋予数据的增删改查能力。
import { relationalStore } from '@kit.ArkData';
import Rdb from './RDB';

export  class InspirationTable {
  private accountTable = new Rdb(CommonConstants.INSPIRATION_TABLE.tableName, CommonConstants.INSPIRATION_TABLE.sqlCreate,
    CommonConstants.INSPIRATION_TABLE.columns);

  constructor(callback: Function = () => {
  }) {
    this.accountTable.getRdbStore(callback);
  }

  getRdbStore(callback: Function = () => {
  }) {
    this.accountTable.getRdbStore(callback);
  }

  insertData(inspiration: InspirationData, callback: Function) {
...
    this.accountTable.insertData(valueBucket, callback);
  }

  deleteData(inspiration: InspirationData, callback: Function) {
...
    this.accountTable.deleteData(predicates, callback);
  }

  updateData(inspiration: InspirationData, callback: Function) {
...
    this.accountTable.updateData(predicates, valueBucket, callback); 
  }
InspirationTable()
export default  new InspirationTable() ; 
function generateBucket(inspiration: InspirationData): relationalStore.ValuesBucket {
  let obj: relationalStore.ValuesBucket = {};
  obj.inspirationName = inspiration.inspirationName;
  obj.updateTime = inspiration.updateTime;
...
  return obj;
}

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.

6 笔记功能开发

展示的每条笔记都是一个List元素,默认是一条高度固定的消息条。在用户单击后Item后,通过条件渲染,启用如下编辑选项:选择类别、可编辑内容、复制与华为分享、插入图片。

  • 选择类别
    每种笔记类别都有专属的图标和颜色,根据用户的选择实时渲染整条List Item,如下所示为用户选择默认紫色灵感类别时,临时渲染的内容做相应的渲染,当用户选择√后,再做持久化保存。
        // idea type button
        if (this.isExpanded) {
          Row({ space: 10 }) {
            Button({ type: ButtonType.Circle, stateEffect: true }) {
              Image($r(CommonConstants.CREATE_IMG))
                .fancyImage(20)
            }.fancyButton(CommonConstants.CREATE_COLOR)
            .onClick(() => {
              this.InspiItem.inspirationColor = CommonConstants.CREATE_COLOR
              this.InspiItem.inspirationType = InspirationType.CREATE_IDEA
              this.InspiItem.inspirationIcon = CommonConstants.CREATE_IMG
            })
		...
          }.justifyContent(FlexAlign.Center).width(this.edite_width)
          .position({ x: 0, y: -15 })
        }

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

如果用户选择了强提醒类别,会触发通知时间设置,到点自动发送通知和震动响铃。
3

                    if (this.InspiItem.inspirationType === InspirationType.ATTENTION_IDEA) {
                      DatePickerDialog.show({
                        start: this.startDate,
                        end: new Date("2100-12-31"),
                        selected: this.selectedDate,
                        showTime: true,
                        useMilitaryTime: true,
                        disappearTextStyle: {
                          color: CommonConstants.ATTENTION_COLOR,
                          font: { size: '14fp', weight: FontWeight.Bold }
                        },
                        textStyle: {
                          color: CommonConstants.ATTENTION_COLOR,
                          font: { size: '18fp', weight: FontWeight.Normal }
                        },
                        selectedTextStyle: { color: '#ff182431', font: { size: '22fp', weight: FontWeight.Regular } },
                        onDateAccept: (value: Date) => {
                          // 通过Date的setFullYear方法设置按下确定按钮时的日期,这样当弹窗再次弹出时显示选中的是上一次确定的日期
                          this.selectedDate = value
                          BackgroundReminder.publishReminder(this.InspiItem, value);
                          if (this.updateItem !== undefined) {
                            console.info('inspitest-点击修改内容')
                            this.updateItem(this.isInsert, this.InspiItem)
                            this.isInsert = false
                          }
                          // this.isExpanded = false;
                          this.animationClick(false);
                        }
                    } 

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.

如果用户选择了日程类别,会触发日程时间选择,自动关联到系统日历。
4

else if (this.InspiItem.inspirationType === InspirationType.TODO_IDEA) {
                      CalendarPickerDialog.show({
                        selected: this.selectedDate,
                        onAccept: (value) => {
                          CalenderMessage.publishCalenderEvent(this.InspiItem, value);
                          if (this.updateItem !== undefined) {
                            console.info('inspitest-点击修改内容')
                            this.updateItem(this.isInsert, this.InspiItem)
                            this.isInsert = false
                          }
                          // this.isExpanded = false;
                          this.animationClick(false);
                          console.info("calendar onAccept:" + JSON.stringify(value))
                        }
                      })
                    }

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.
  • 可编辑内容
    笔记内容包括最近编辑时间(Text)、可编辑笔记文本(InpuText)、选择的图片(Image)。这部分功能使用基础组件即可实现。

  • 复制、华为分享

    • 使用pasteboard接口实现复制。
 // copy
              Column() {
                  Image($r('app.media.ic_public_copy')).fancyImage(25)
                }
                .width('20%')
                .onClick(() => {
                  if (this.TempInspiName === '') {
                    promptAction.showToast({
                      message: '内容为空',
                      duration: 1500
                    })
                    return;
                  }
                  let text: string = this.TempInspiName;
                  let pasteData: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
                  let systemPasteBoard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
                  systemPasteBoard.setData(pasteData).catch((err: BusinessError) => {
                    console.error(`Failed to set pastedata. Code: ${err.code}, message: ${err.message}`);
                  });
                  let subText = this.TempInspiName.substring(0, 4);
                  if (subText.length < this.TempInspiName.length) { //只提示部分内容
                    subText = subText + '...'
                  }
                })

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
  • 使用systemShare接口即可实现华为分享。
                // share
                Column() {
                  Image($r('app.media.ic_public_share')).fancyImage(25)
                }.width('20%')
                .onClick(() => {
                  let ShareDta: systemShare.SharedData = new systemShare.SharedData({
                    utd: utd.UniformDataType.PLAIN_TEXT,
                    content: this.TempInspiName
                  });
                  // 构建ShareController
                  let ShareController: systemShare.ShareController = new systemShare.ShareController(ShareDta);
                  // 获取UIAbility上下文对象
                  let ShareContext: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
                  // 注册分享面板关闭监听
                  ShareController.on('dismiss', () => {
                    console.log('Share panel closed');
                    // 分享结束,可处理其他业务。
                  });
                  // 进行分享面板显示
                  ShareController.show(ShareContext, {
                    previewMode: systemShare.SharePreviewMode.DETAIL,
                    selectionMode: systemShare.SelectionMode.SINGLE
                  });
                })

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
  • 插入图片
    通过picker.PhotoViewPicker访问用户相机和相册,保护用户隐私,使用也快捷方便。
    5
 //add camera or photoPicker
                Column() {
                  Image($r('app.media.ic_public_camera'))
                    .fancyImage(25)
                }
                .width('16%')
                .onClick(() => {
                  if (this.TempInspiPicIndex < 5) {
                    const photoSelectOptions = new picker.PhotoSelectOptions();
                    photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
                    photoSelectOptions.maxSelectNumber = 5 - this.TempInspiPicIndex;
                    const photoViewPicker = new picker.PhotoViewPicker();
                    //调用select()接口拉起图库界面进行文件选择,文件选择成功后,返回PhotoSelectResult结果集
                    photoViewPicker.select(photoSelectOptions)
                      .then(async (photoSelectResult: picker.PhotoSelectResult) => {
                        //用一个全局变量存储返回的uri
                        this.selectedImage = photoSelectResult.photoUris
                        for (let i = 0; i < photoSelectResult.photoUris.length; i += 1) {
                          this.TempInspiPic += photoSelectResult.photoUris[i] + '|';
                          this.TempInspiPicIndex += 1;
                        }                       
                        console.info('photoViewPicker.select to file succeed and uris are:' + this.TempInspiPic);            
                      })
                      .catch((err: BusinessError) => {
                        console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
                      })
                  } else {
                    promptAction.showToast({
                      message: '图片超出限制',
                      duration: 1500
                    })
                  }
                })

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.

通过开发上述编辑选项功能,实现的内容编辑效果如下图所示:
6

7 动效开发

APP的开发离不开动画,HarmonyOS NEXT ArkUI中提供多种动画接口(属性动画、转场动画等),灵感速记中使用了转场动画、弹簧效果等动效,给用户舒适的视觉体验。
通过基础的组件转场接口transition与TransitionEffect的组合使用,定义出一镜到底的操作效果。如笔记内容在展开或收起时,加入下动画修饰:

          .transition(TransitionEffect.asymmetric(
            TransitionEffect.opacity(0)
              .animation({ curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), duration: 200, delay: 150 }),
            TransitionEffect.opacity(0)
              .animation({ curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), duration: 200 }),
          ))

1.2.3.4.5.6.

笔记展开、收起动画效果立竿见影:
7

8 共享消息与灵动看板

灵感速记的共享消息功能,是将APP记录的内容分享到生态硬件(灵动看板),在灵动开板上点击完成,APP也有相应的记录。共享消息功能将笔记“用起来”,挖掘更多应用场景。共享消息功能主要使用了IoT技术实现,技术思路如下图:
8

在APP内置了本地html网页,用于实现配置mqtt参数以及收发数据,该过程涉及应用侧与前端页面的交互,HarmonyOS NEXT的[ArkWeb(方舟Web)]提供了能力运行前端JavaScript函数、数据双向传输等能力。
这里讲解如何实现html与ArkTS之间数据交互。
首先,在启动app时,要在Index.ets的aboutToAppear()中创建一个和H5页面通信的消息通道,实现如下:

  TryWebPort(): void {
    try {
      // 1、创建两个消息端口。
      this.ports = this.webviewController.createWebMessagePorts();
      // 2、在应用侧的消息端口(如端口1)上注册回调事件。
      this.ports[1].onMessageEvent((result: web_webview.WebMessage) => {
        let msg = 'Got msg from HTML:';
 ...

      })
      // 3、将另一个消息端口(如端口0)发送到HTML侧,由HTML侧保存并使用。
      this.webviewController.postMessage('__init_port__', [this.ports[0]], '*');
    } catch (error) {
      promptAction.showToast({duration:2000,message:'发送失败'})
      let e: business_error.BusinessError = error as business_error.BusinessError;
      console.error(`ErrorCode: ${e.code},  Message: ${e.message}`);
    }
  }

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.

其次,需要在本地src/main/resources/rawfile/index.html 中创建一个用于接收的监听端口,具体实现如下:

// 页面
var h5Port;
var output = document.querySelector('.output');
window.addEventListener('message', function (event) {
    if (event.data === '__init_port__') {
        if (event.ports[0] !== null) {
            h5Port = event.ports[0]; // 1. 保存从ets侧发送过来的端口
            h5Port.onmessage = function (event) {
              // 2. 接收ets侧发送过来的消息.
              var msg = 'Got message from ets:';
              var result = event.data;
              if (typeof(result) === 'string') {
                console.info(`received string message from html5, string is: ${result}`);
                msg = result;
              } else if (typeof(result) === 'object') {
                if (result instanceof ArrayBuffer) {
                  console.info(`received arraybuffer from html5, length is: ${result.byteLength}`);
                  msg = msg + 'lenght is ' + result.byteLength;
                } else {
                  console.info('not support');
                }
              } else {
                console.info('not support');
              }
              // this.PositionName  = msg.toString();
      
            }
        }
    }
})

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.

本地的H5可以通过与ets建立的消息通道,直接发送数据到用户页面,这个通道也可以用来接收H5发送回来的数据.

// 使用h5Port往ets侧发送消息.
function PostMsgToEts(data) {
    console.info('H5 to Ets data:'+data);
    if (h5Port) {
      h5Port.postMessage(data);
    } else {
      console.error('h5Port is null, Please initialize first');
    }
}
// 调用接口发送数据到ets用户页面,便于存储和展示
this.PostMsgToEts(jsonObjTaskNumber.toString()+jsonObjTaskChecked.toString());

1.2.3.4.5.6.7.8.9.10.11.

9

3

在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
上面更多鸿蒙最新技术知识点,请前往作者博客:https://gitee.com/li-shizhen-skin/zhihu/blob/master/README.md

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值