HarmonyOS开发案例:【电子相册】

介绍

如何实现一个简单的电子相册应用的开发,主要功能包括:

  1. 实现首页顶部的轮播效果。

  2. 实现页面跳转时共享元素的转场动画效果。

  3. 实现通过手势控制图片的放大、缩小、左右滑动查看细节等效果。

相关概念

  • [Swiper]:滑块视图容器,提供子组件滑动轮播显示的能力。
  • [Grid]:网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。
  • [Navigation]:Navigation组件一般作为Page页面的根容器,通过属性设置来展示页面的标题、工具栏、菜单。
  • [List]:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
  • [组合手势]:手势识别组,多种手势组合为复合手势,支持连续识别、并行识别和互斥识别。

环境搭建

软件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:[润和RK3568开发板]。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]
  3. 搭建开发环境。

    1. 开始前请参考[工具准备],完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导]创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用[真机进行调测]。
    4. 开发前请熟悉鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/ets                // 代码区
│  ├──common
│  │  ├──constansts
│  │  │  └──Constants.ets            // 常量类
│  │  └──utils
│  │     └──Logger.ets               // Logger公共类
│  ├──entryability
│  │  └──EntryAbility.ts             // 程序入口类
│  ├──pages
│  │  ├──DetailListPage.ets          // 图片详情页面
│  │  ├──DetailPage.ets              // 查看大图页面
│  │  ├──IndexPage.ets               // 电子相册主页面
│  │  └──ListPage.ets                // 图片列表页面
│  └──view
│     └──PhotoItem.ets               // 首页相册Item组件
└──entry/src/main/resources          // 资源文件

`HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿`

搜狗高速浏览器截图20240326151450.png

构建应用页面

应用首页

应用首页用Column组件来实现纵向布局,从上到下依次是标题组件Text、轮播图Swiper、相册列表Grid。标题和轮播图均设置固定高度,底部相册列表通过layoutWeight属性实现自适应占满剩余空间。

// IndexPage.ets
Column() {
  Row() {
    Text($r('app.string.EntryAbility_label'))
  }

  Swiper(this.swiperController) {
    ForEach(Constants.BANNER_IMG_LIST, (item: Resource) => {
      Row() {
        Image(item)
        ...
      }
    }, (item: Resource, index: number) => JSON.stringify(item) + index)
  }
  ...

  Grid() {
    ForEach(IMG_ARR, (photoArr: Array<Resource>) => {
      GridItem() {
        PhotoItem({ photoArr })
      }
      ...
      .onClick(() => {
        router.pushUrl({
          url: Constants.URL_LIST_PAGE,
          params: { photoArr: JSON.stringify(photoArr) }
        }).catch((error: Error) => {
          Logger.error(Constants.TAG_INDEX_PAGE, JSON.stringify(error));
        });
      })
    }, (item: Array<Resource>, index: number) => JSON.stringify(item) + index)
  }
  ...
  .layoutWeight(1)
}

图片列表页面

图片列表页是网格状展开的图片列表,主要使用Grid组件和GridItem组件,GridItem高度通过aspectRatio属性设置为跟宽度一致。

// ListPage.ets
Navigation() {
  Grid() {
    ForEach(this.photoArr, (img: Resource, index: number) => {
      GridItem() {
        Image(img)
          .onClick(() => {
            this.selectedIndex = index;
            router.pushUrl({
              url: Constants.URL_DETAIL_LIST_PAGE,
              params: {
                photoArr: JSON.stringify(this.photoArr),
              }
            }).catch((error: Error) => {
              Logger.error(Constants.TAG_LIST_PAGE, JSON.stringify(error));
            });
          })
      }
      ...
      .aspectRatio(1)
    }, (item: Resource) => JSON.stringify(item))
  }
  .columnsTemplate(Constants.GRID_COLUMNS_TEMPLATE)
  .layoutWeight(1)
}

图片详情页面

图片详情页由两个横向滚动的List组件完成整体布局,两个组件之间有联动的效果。滚动底部的List,上边展示的图片会随着改变,同样左右滑动上边的图片时,底部List组件也会随之改变。

// DetailListPage.ets
Stack({ alignContent: Alignment.Bottom }) {
  List({ scroller: this.bigScroller, initialIndex: this.selectedIndex }) {
    ForEach(this.photoArr, (img: Resource, index: number) => {
      ListItem() {
        Image(img)
          ...
          .gesture(PinchGesture({ fingers: Constants.DOUBLE_NUMBER })
            .onActionStart(() => this.goDetailPage()))
          .onClick(() => this.goDetailPage())
      }
    }, (item: Resource) => JSON.stringify(item))
  }
  ...
  .onScroll((scrollOffset, scrollState) => {
    if (scrollState === ScrollState.Fling) {
      this.bigScrollAction(scrollTypeEnum.SCROLL);
    }
  })
  .onScrollStop(() => this.bigScrollAction(scrollTypeEnum.STOP))

  List({ scroller: this.smallScroller, space: Constants.LIST_ITEM_SPACE, initialIndex: this.selectedIndex }) {
    ForEach(this.smallPhotoArr, (img: Resource, index: number) => {
      ListItem() {
        this.SmallImgItemBuilder(img, index)
      }
    }, (item: Resource, index: number) => JSON.stringify(item) + index)
  }
  ...
  .listDirection(Axis.Horizontal)
  .onScroll((scrollOffset, scrollState) => {
    if (scrollState === ScrollState.Fling) {
      this.smallScrollAction(scrollTypeEnum.SCROLL);
    }
  })
  .onScrollStop(() => this.smallScrollAction(scrollTypeEnum.STOP))
}

查看大图页面

查看大图页面由一个横向滚动的List组件来实现图片左右滑动时切换图片的功能,和一个Row组件实现图片的缩放和拖动查看细节功能。对图片进行缩放时会从List组件切换成Row组件来实现对单张图片的操作,对单张图片进行滑动操作时,也会由Row组件转换为List组件来实现图片的切换功能。

// DetailPage.ets
Stack() {
  List({ scroller: this.scroller, initialIndex: this.selectedIndex }) {
    ForEach(this.photoArr, (img: Resource) => {
      ListItem() {
        Image(img)
          ...
          .onClick(() => router.back()
          )
      }
      .gesture(PinchGesture({ fingers: Constants.DOUBLE_NUMBER })
        .onActionStart(() => {
          this.resetImg();
          this.isScaling = true;
          this.imgOffSetX = 0;
          this.imgOffSetY = 0;
        })
        .onActionUpdate((event: GestureEvent) => {
          this.imgScale = this.currentScale * event.scale;
        })
        .onActionEnd(() => {
          if (this.imgScale < 1) {
            this.resetImg();
            this.imgOffSetX = 0;
            this.imgOffSetY = 0;
          } else {
            this.currentScale = this.imgScale;
          }
        })
      )
    }, (item: Resource) => JSON.stringify(item))
  }
  ...
  .onScrollStop(() => {
    let currentIndex = Math.round((this.scroller.currentOffset()
      .xOffset + (this.imageWidth / Constants.DOUBLE_NUMBER)) / this.imageWidth);
    this.selectedIndex = currentIndex;
    this.scroller.scrollTo({ xOffset: currentIndex * this.imageWidth, yOffset: 0 });
  })
  .visibility(this.isScaling ? Visibility.Hidden : Visibility.Visible)

  Row() {
    Image(this.photoArr[this.selectedIndex])
    ...
  }
  .visibility(this.isScaling ? Visibility.Visible : Visibility.Hidden)
}

通过手势控制图片

大图浏览界面双指捏合时通过改变Image组件的scale来控制图片的缩放,单手拖动时通过改变Image的偏移量来控制图片的位置,手势操作调用组合手势GestureGroup实现。其中PinchGesture实现双指缩放手势,PanGesture实现单指拖动手势。

// DetailPage.ets 
Row() {
    Image(this.photoArr[this.selectedIndex])
      .position({ x: this.imgOffSetX, y: this.imgOffSetY })
      .scale({ x: this.imgScale, y: this.imgScale })
  }
  .gesture(GestureGroup(GestureMode.Exclusive,
  PinchGesture({ fingers: Constants.DOUBLE_NUMBER })
    .onActionUpdate((event: GestureEvent) => {
      this.imgScale = this.currentScale * event.scale;
    })
    .onActionEnd(() => {
      if (this.imgScale < 1) {
        this.resetImg();
        this.imgOffSetX = 0;
        this.imgOffSetY = 0;
      } else {
        this.currentScale = this.imgScale;
      }
    }),
  PanGesture()
    .onActionStart(() => {
      this.preOffsetX = this.imgOffSetX;
      this.preOffsetY = this.imgOffSetY;
    })
    .onActionUpdate((event: GestureEvent) => {
      this.imgOffSetX = this.preOffsetX + event.offsetX;
      this.imgOffSetY = this.preOffsetY + event.offsetY;
    })
    .onActionEnd(() => this.handlePanEnd())
  ))

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

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

相关文章

cmake install命令无法覆盖同名文件

文章目录 1. 问题记录2. 原因排查3. 解决方案 1. 问题记录 我有两个同名文件test.txt&#xff0c;它们内容不同&#xff0c;但时间戳相同&#xff08;文件属性中的修改时间相同&#xff09; 我希望在cmake中利用install命令&#xff0c;将${PATH_SRC}/test.txt替换${PATH_DES…

Elasticsearch:使用 MongoDB connector 同步数据到 Elasticsearch

MongoDB 是一个基于分布式文件存储的数据库。由 C 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。Elasticsearch 是一个高效强…

[CISCN2019 华北赛区 Day1 Web2]ikun

看到提示说一定要找到lv6 这要写脚本来爆破了&#xff0c;用bp是爆破不出来的 发现LV等级都是有参数挂着的 写个脚本看一下 import requests for i in range(1,1000):payload"http://node4.anna.nssctf.cn:28150/shop?page%d"%(i)resrequests.get(payload)if "…

【PCIE】基于PCIE4C的数据传输(四)——使用MSIX中断

基于PCIE4C的数据传输&#xff08;三&#xff09;——遗留中断与MSI中断 一文介绍了遗留中断与MSI中断两种中断方式的代码实现&#xff0c;本文继续基于Xilinx UltrascaleHBM VCU128开发板与linux&#xff08;RHEL8.9&#xff09;&#xff0c;介绍MSIX中断方式的代码实现。本文…

矩阵的压缩存储介绍

引入 概述 特殊矩阵的压缩 对称矩阵 三角矩阵 对角矩阵 稀疏矩阵 三元组存储 十字链表法 示例

java:递归实现的案例

//求第20个月兔子的对数 //每个月兔子对数&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8 public class Test {//求第20个月兔子的对数//每个月兔子对数&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8pu…

《Python编程从入门到实践》day21

# 昨日知识点回顾 设置背景颜色 在屏幕中央绘制飞船 # 今日知识点学习 12.5 重构&#xff1a;方法_check_events()和_update_screen() 12.5.1 方法_check_events() import sys import pygame from Settings import Settings from Ship import Shipclass AlienInvasion:"…

[Maven]IDEA报错-xxx is referencing itself

在IDEA中&#xff0c;执行 mvn clean时报错xxx is referencing itself。 解决方案&#xff1a;https://stackoverflow.com/questions/64246267/maven-error-using-intellij-is-referencing-itself 具体做法&#xff1a;采用上图第二条&#xff0c;将父模块pom文件中的对子模块…

练习题(2024/5/7)

1验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 …

JSP企业快信系统的设计与实现参考论文(论文 + 源码)

【免费】JSP企业快信系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89277688 JSP企业快信系统的设计与实现 摘 要 计算机网络的出现到现在已经经历了翻天覆地的重大改变。因特网也从最早的供科学家交流心得的简单的文本浏览器发展成为了商务和信息的中心…

深入理解Java虚拟机(JVM)

引言&#xff1a; Java虚拟机&#xff08;JVM&#xff09;是Java平台的核心组件&#xff0c;它负责将Java字节码转换成平台特定的机器指令&#xff0c;并在相应的硬件和操作系统上执行。JVM的引入使得Java语言具有“一次编写&#xff0c;到处运行”的跨平台特性。本文将深入探…

【练习2】

1.汽水瓶 ps:注意涉及多个输入&#xff0c;我就说怎么老不对&#xff0c;无语~ #include <cmath> #include <iostream> using namespace std;int main() {int n;int num,flag,kp,temp;while (cin>>n) {flag1;num0;temp0;kpn;while (flag1) {if(kp<2){if(…

初识C++ · 类和对象(下)

目录 1 再谈构造函数 2 类中的隐式类型转换 3 Static成员 4 友元和内部类 5 匿名对象 6 编译器的一些优化 1 再谈构造函数 先看一段代码&#xff1a; class Date { public :Date(int year, int month, int day){_year year;_month month;_day day;} private:int _ye…

Redis线程模型

文章目录 &#x1f496; Redis 单线程模型⭐ 单线程监听大量的客户端连接⭐ Redis 6.0 之前为什么不用多线程&#xff1f; &#x1f496; Redis多线程⭐ Redis 后台线程⭐ Redis 网络IO多线程 对于读写命令来说&#xff0c;Redis 一直是单线程模型。不过&#xff0c;在 Redis 4…

TinyXML-2介绍

1.简介 TinyXML-2 是一个简单、小巧的 C XML 解析库&#xff0c;它是 TinyXML 的一个改进版本&#xff0c;专注于易用性和性能。TinyXML-2 用于读取、修改和创建 XML 文档。它不依赖于外部库&#xff0c;并且可以很容易地集成到项目中。 tinyXML-2 的主要特点包括&#xff1a…

华为:三层交换机与路由器连通上网实验

三层交换机是一种网络交换机&#xff0c;可以实现基于IP地址的高效数据转发和路由功能&#xff0c;通常用于大型企业、数据中心和校园网络等场景。此外&#xff0c;三层交换机还支持多种路由协议&#xff08;如OSPF、BGP等&#xff09;&#xff0c;以实现更为复杂的网络拓扑结构…

automa警惕通过点击元素打开新的标签页,因为你可能会被他蒙蔽!

大家好&#xff0c;我是大胡子&#xff0c;专注于研究RPA实战与解决方案。 我们经常用到automa里面的【点击元素】组件&#xff0c;但要警惕通过点击元素打开新的标签页&#xff0c;例如下面这个场景&#xff0c;点击公众号的图文消息&#xff0c;之后&#xff0c;要自动输入标…

python环境下labelImg图片标注工具的使用

labelimg GitHub地址 python环境下labelImg图片标注工具的使用 1. 写在开头2. 如何使用2.1安装2.2 启动2.2.1 先启动后设置标注的目录2.2.2 指定标注的目录和预设置的标签 2.3 设置自动保存和显示类别。2.4 保存文件类型2.5 [快捷键](https://github.com/HumanSignal/labelImg…

【数据结构】C/C++ 带头双向循环链表保姆级教程(图例详解!!)

目录 一、前言 二、链表的分类 &#x1f95d;单链表 &#x1f95d;双链表 &#x1f95d;循环链表 &#x1f95d;带头双向循环链表 &#x1f34d;头节点&#xff08;哨兵位&#xff09;的作用 ✨定义&#xff1a; ✨作用&#xff1a; &#x1f347;总结 三、带头双向循环链表 …

技术速递|使用 .NET 为 Microsoft AI 构建可扩展网关

作者&#xff1a;Kara Saucerman 排版&#xff1a;Alan Wang Microsoft AI 团队构建了全面的内容、服务、平台和技术&#xff0c;以便消费者在任何设备上、任何地方获取他们想要的信息&#xff0c;并为企业改善客户和员工的体验。我们的团队支持多种体验&#xff0c;包括 Bing、…