鸿蒙6.0应用开发——图片预览器的实现

概述

图片预览器是常见的开发应用场景。在诸多日常使用的软件中,图片预览器都是提升用户体验的关键组件。它允许用户在上传、分享或编辑图片之前,先对图片进行预览,从而确保图片的质量和效果符合预期。本文章将深入探讨实现图片预览器过程中的几个复杂场景,具体包括:图片如何“跟手”,如何计算并合理限制图片的边界,以及如何解决Swiper组件与滑动手势之间产生的冲突问题。

在这里插入图片描述

实现原理

场景描述

基础的图片预览器功能包括如下操作:

  1. 双指捏合图片,即实现对图片以双指中心点为基准点的缩放操作。
  2. 双击图片即可切换其大小,当图片处于放大状态时,再次双击即恢复至默认尺寸。
  3. 大图片支持左右滑动查看。
  4. 点击或滑动图片指示器,主图会随之更新。

其中,缩放图片是通过矩阵变换功能matrix4来实现的,图片的平移是通过属性translate来实现的。

关键技术

图片预览器中的图片查看功能,主要由大图界面来承担,交互操作相对复杂。下面,简要梳理一下大图界面中基本手势的处理与计算方式。

“跟手”的原理

“跟手”操作细分为两大类别:平移“跟手”与缩放“跟手”。

在平移“跟手”中,无论用户的手指如何在屏幕上滑动,其触摸点相对于图片所保持的百分比位置始终保持不变。缩放“跟手”,则是在图片依据用户手势进行缩放调整时,用户手势的中心点不仅相对于屏幕上的坐标保持不变,而且相对于图片内容的百分比位置也保持不变。如下图所示,屏幕是蓝色区域,初始图片是橙色区域,放大后的图片是灰色区域。

在这里插入图片描述

假定当前图片位置是<lastScale, offsetX, offsetY>,控件原始宽高为<w, h>,本次缩放图片的缩放值为scale,缩放的中心点百分比位置为<centerX, centerY>,偏移为<offX, offY>,计算终点位置设为<scale’, offsetX’, offsetY’>。

在这里插入图片描述

如上图所示,假定缩放时,未发生偏移,蓝色看作交互开始时的控件,橙色是交互后的控件,如果缩放中心点在图片中心(图1),那么控件最终的offset没有任何变化;如果缩放中心在最左边缘(图3),在放大的过程中,整个控件的中心向右发生了偏移。由此,可以计算出图片的最终位置。其中,图2、图3中的问号代表图片的偏移量,而图中的橙色圆点是图片缩放操作的中心点。在以下计算公式中,0.5 表示图片中心点的百分比位置,即 50% = 0.5。

  • scale’ = 上次手势结束时的缩放值 * 本次缩放图片的缩放值。

    = lastScale * scale

  • offsetX’ = 平移带来的偏移 + 缩放中心不在中心而带来的偏移。

    = (offsetX + offX) + (0.5 - centerX) * 控件大小变化之差

    = (offsetX + offX) + (0.5 - centerX) * (w * lastScale - w * lastScale * scale)

    = (offsetX + offX) + (0.5 - centerX) * w * (scale - 1) * lastScale

    = (offsetX + offX) + (0.5 - centerX) * w * (1 - scale) * lastScale

  • 同理 offsetY’ = 平移带来的偏移 + 缩放中心不在中心而带来的偏移。

    = (offsetY + offY) + (0.5 - centerY) * h * (1 - scale) * lastScale

    缩放中心百分比位置<centerX , centerY>计算。如下图,橙色为手机屏幕,触摸点反馈的坐标(x,y)是相较屏幕左上角的(假设控件布满全屏)。

    在这里插入图片描述

  • centerX = ( x - imgX ) / imgWidth

    = ( 触摸点坐标x- X方向图片左上角的坐标)/ 图片的宽度

    = ( 触摸点坐标x- ( ( 组件屏幕的宽度 - 当前图片的宽度) / 2 +上次图片X方向的偏移量)) / 图片的宽度

  • 同理 centerY = ( y - imgY ) / imgHeight

    = ( 触摸点坐标y- Y方向图片左上角的坐标 ) / 图片的高度

    = ( 触摸点坐标y- ( ( 组件屏幕的高度 - 当前图片的高度) / 2 +上次图片Y方向的偏移量)) / 图片的高度

边界限制的原理

边界计算涉及两个方面:当前图片显示边界计算、offset范围计算。

  • 当前图片显示边界计算可得出当前图片显示的位置,左右上下是否与显示区域边界对齐,如果已经对齐,则不能继续往某个方向继续拖动。
  • 平移/缩放时必须对offset作限制,否则图片将被移出显示区域或是手势结束后周围有黑边。从原理上看,某个scale下offset(x&y方向上)的范围是固定的,所以只需在手势交互时根据当前的scale计算得出offsetRange即可(超出边界时取边界作为结果)。

假定下面两个图中显示区域为黑框区域,当前放大倍率是curScale。

如下图,当X方向offset到达上界时图片上边缘x坐标等于0:

在这里插入图片描述

到达下界时,图片右边缘与显示区域右边缘重合:

在这里插入图片描述

// Calculate the actual display size of the scaled image.
let scaledImageWidth = this.imageWidth * this.curScale;
let scaledImageHeight = this.imageHeight * this.curScale;

// Calculation of X-axis boundaries
if (scaledImageWidth > this.componentWidth) {
  // If the width of the picture exceeds the width of the screen, you can move it left and right.
  let maxXOffset = (scaledImageWidth - this.componentWidth) / 2;
  this.maxOffsetX = maxXOffset;
  this.minOffsetX = -maxXOffset;
} else {
  // The width of the image does not exceed the width of the screen. It should be centered and no X-axis offset is allowed.
  this.maxOffsetX = 0;
  this.minOffsetX = 0;
}
  1. 计算缩放图像的宽度和高度:
    • 通过当前缩放比例(this.curScale)乘以原始图像宽度(this.imageWidth)和高度(this.imageHeight)来获取缩放后的图像尺寸。
  2. 计算X轴边界偏移量:
    • 判断缩放后的图像宽度是否大于组件宽度。
    • 如果图像宽度超过组件宽度,计算最大和最小X轴偏移量(this.maxOffsetXthis.minOffsetX),以允许图像在屏幕内左右移动。
    • 如果图像宽度不超过组件宽度,将最大和最小X轴偏移量设置为0,使图像居中显示。
  3. 代码中涉及的变量包括:
    • scaledImageWidthscaledImageHeight:存储缩放后的图像宽度和高度。
    • maxXOffset:存储图像宽度超过组件宽度时的最大X轴偏移量。
    • this.maxOffsetXthis.minOffsetX:存储X轴的边界偏移量。

Y方向同理,上下界为:

// Calculate the actual display size of the scaled image.
let scaledImageWidth = this.imageWidth * this.curScale;
let scaledImageHeight = this.imageHeight * this.curScale;

// ...

// Y-axis boundary calculation
if (scaledImageHeight > this.componentHeight) {
  // If the image height exceeds the screen height, it can be scrolled up and down.
  let maxYOffset = (scaledImageHeight - this.componentHeight) / 2;
  this.maxOffsetY = maxYOffset;
  this.minOffsetY = -maxYOffset;
} else {
  // The height of the image does not exceed the height of the screen. It should be centered and no Y-axis offset is allowed.
  this.maxOffsetY = 0;
  this.minOffsetY = 0;
}
  1. 计算缩放图像的宽度和高度:
    • 通过当前缩放比例(this.curScale)乘以原始图像宽度(this.imageWidth)和高度(this.imageHeight)来得到缩放后的图像尺寸。
  2. 计算Y轴边界偏移量:
    • 检查缩放后的图像高度是否超过组件高度(this.componentHeight)。
    • 如果超过,计算最大和最小Y轴偏移量(this.maxOffsetYthis.minOffsetY),以允许图像上下滚动。
    • 如果未超过,将最大和最小Y轴偏移量设置为0,使图像垂直居中。
    • 检查缩放后的图像高度是否超过组件高度(this.componentHeight)。
  3. 代码中涉及的变量和逻辑确保了图像在不同尺寸下的显示效果,提供了良好的用户体验。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐