深圳幻海软件技术有限公司 欢迎您!

HarmonyOS用Matrix实现各种图片ScaleType缩放

2023-02-27

想了解更多内容,请访问:51CTO和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com本文将从零开始实现一个图片组件,并展示如何使用Matrix实现图片的各种ScaleType缩放效果。背景知识:Matrix内部通过维护一个float[9]的数组来构成3x3矩阵的形

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

本文将从零开始实现一个图片组件,并展示如何使用 Matrix 实现图片的各种 ScaleType 缩放效果。

背景知识:

Matrix 内部通过维护一个 float[9] 的数组来构成 3x3 矩阵的形式,从底层原理来看,所有的变换方法就是更改数组中某个或某几个位置的数值;

Matrix提供了Translate(平移)、Scale(缩放)、Rotate(旋转)、Skew(扭曲)四中变换操作,这四种操作实质上是调用了setValues()方法来设置矩阵数组来达到变换效果。除Translate(平移)外,Scale(缩放)、Rotate(旋转)、Skew(扭曲)都可以围绕一个中心点来进行,如果不指定,在默认情况下是围绕(0, 0)来进行相应的变换的。

Matrix提供的四种操作,每一种都有pre、set、post三种形式。原因是矩阵乘法不满足乘法交换律,因此左乘还是右乘最终的效果都不一样。我们可以把Matrix变换想象成一个队列,队列里面包含了若干个变换操作,队列中每个操作按照先后顺序操作变换目标完成变换,pre相当于向队首增加一个操作,post相当于向队尾增加一个操作,set相当于清空当前队列重新设置。

鸿蒙 Image 组件没有对外公开 setMatrix 接口,如果项目中需要通过 setMatrix 来控制图片显示,可参考本文,自己实现自绘式 Image 组件。

创建图片组件

先创建一个组件类 MyImageComponent,因为是从零开始,所以我们从 Component 继承,包含以下三个属性:

public class MyImageComponent extends Component implements Component.DrawTask {    
    // 图片资源,从 src 属性读取 
    private Element element; 
    // 读取 scaleType 属性 
    private ScaleType scaleType = ScaleType.fitCenter; 
    // 用于实现 scaleType 的 Matrix  
    private final Matrix matrix = new Matrix(); 
     
    // ...其他构造函数略 
    public MyImageComponent(Context context, AttrSet attrSet, int resId) { 
        super(context, attrSet, resId); 
        init(attrSet); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

然后执行初始化流程:

// 初始化,包括读取属性,根据 scaleType 设置 matrix,添加绘制方法      
   private void init(AttrSet attrSet) { 
       readAttrSet(attrSet);       
       dealScaleType(); 
       addDrawTask(this); 
   } 
 
   // 读取 xml 属性,包括 src 和 scaleType 
   private void readAttrSet(AttrSet attrSet) { 
       element = attrSet.getAttr("src").get().getElement(); 
        
       if (attrSet.getAttr("scaleType").isPresent()) { 
           String scaleTypeString = attrSet.getAttr("scaleType").get().getStringValue(); 
           scaleType = Utils.getEnumFromString(ScaleType.class, scaleTypeString, ScaleType.center); 
       } 
   } 
 
   // 根据 scaleType 属性实现对应的缩放效果 
   private void dealScaleType() { 
       switch (scaleType) { 
           default
           case fitCenter: 
               fitCenter(); 
               break; 
           case center: 
               center(); 
               break; 
           case fitXY: 
               fitXY(); 
               break; 
           case fitStart: 
               fitStart(); 
               break; 
           case fitEnd: 
               fitEnd(); 
               break; 
           case centerCrop: 
               centerCrop(); 
               break; 
           case centerInside: 
               centerInside(); 
               break; 
       } 
   } 
 
   private int getDisplayWidth() { 
       return getWidth() - getPaddingLeft() - getPaddingRight(); 
   } 
  
   private int getDisplayHeight() { 
       return getHeight() - getPaddingLeft() - getPaddingRight(); 
   } 
 
private int getElementWidth() { 
       return element.getWidth(); 
   } 
 
   private int getElementHeight() { 
       return element.getHeight(); 
   } 
  • 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.

ScaleType 效果展示和对应源码

以下逐个展示各种 ScaleType 效果及其实现代码,为方便对比不同大小的图片的 ScaleType 差异,准备了一大一小两张图片。



用于预览的 xml 布局代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<DirectionalLayout 
    xmlns:ohos="http://schemas.huawei.com/res/ohos" 
    xmlns:app="http://schemas.ohos.com/apk/res-auto" 
    ohos:height="match_parent" 
    ohos:width="match_parent" 
    ohos:alignment="center" 
    ohos:orientation="vertical"
 
    <com.bm.mycomponent.MyImageComponent 
        ohos:width="200vp" 
        ohos:height="200vp" 
        ohos:background_element="#4682B4" 
        ohos:margin="6vp" 
        app:src="$media:big" 
        app:scaleType="center" 
        /> 
    <com.bm.mycomponent.MyImageComponent 
        ohos:width="200vp" 
        ohos:height="200vp" 
        ohos:margin="6vp" 
        ohos:background_element="#4682B4" 
        app:src="$media:small" 
        app:scaleType="center" 
        /> 
 
</DirectionalLayout> 
  • 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.

以下是各种 scaleType 的效果和源码:

center


/** 
    * 图片居中显示在组件中,不对图片进行缩放 
    */ 
   private void center() { 
       float wTranslate = (getDisplayWidth() - getElementWidth()) * 0.5f; 
       float hTranslate = (getDisplayHeight() - getElementHeight()) * 0.5f; 
 
       matrix.postTranslate(wTranslate, hTranslate); 
   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

fitCenter

保持图片的宽高比,对图片进行X和Y方向缩放,直到一个方向铺满组件,缩放后的图片居中显示在组件中。


private void fitCenter() { 
        float wPercent = (float)getDisplayWidth() / (float)getElementWidth(); 
        float hPercent = (float)getDisplayHeight() / (float)getElementHeight(); 
        float minPercent = Math.min(wPercent, hPercent); 
 
        float targetWidth = minPercent * getElementWidth(); 
        float targetHeight = minPercent * getElementHeight(); 
 
        float wTranslate = (getDisplayWidth() - targetWidth) * 0.5f; 
        float hTranslate = (getDisplayHeight() - targetHeight) * 0.5f; 
 
        matrix.setScale(minPercent, minPercent); 
        matrix.postTranslate(wTranslate, hTranslate); 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

fitXY

对X和Y方向独立缩放,直到图片铺满组件。这种方式可能会改变图片原本的宽高比,导致图片拉伸变形。


private void fitXY(){ 
       float wPercent = (float)getDisplayWidth() / (float)getElementWidth(); 
       float hPercent = (float)getDisplayHeight() / (float)getElementHeight(); 
       matrix.setScale(wPercent, hPercent); 
   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

fitStart

保持图片的宽高比,对图片进行X和Y方向缩放,直到一个方向铺满组件,缩放后的图片与组件左上角对齐进行显示。


private void fitStart() { 
      float wPercent = (float)getDisplayWidth() / (float)getElementWidth(); 
      float hPercent = (float)getDisplayHeight() / (float)getElementHeight(); 
      float minPercent = Math.min(wPercent, hPercent); 
       
      matrix.setScale(minPercent, minPercent); 
  } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

fitEnd

保持图片的宽高比,对图片进行X和Y方向缩放,直到一个方向铺满组件,缩放后的图片与组件右下角对齐进行显示。


private void fitEnd() { 
     float wPercent = (float)getDisplayWidth() / (float)getElementWidth(); 
     float hPercent = (float)getDisplayHeight() / (float)getElementHeight(); 
     float minPercent = Math.min(wPercent, hPercent); 
 
     float targetWidth = minPercent * getElementWidth(); 
     float targetHeight = minPercent * getElementHeight(); 
     float wTranslate = getDisplayWidth() - targetWidth; 
     float hTranslate = getDisplayHeight() - targetHeight; 
 
     matrix.setScale(minPercent, minPercent); 
     matrix.postTranslate(wTranslate, hTranslate); 
 } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

centerCrop

保持图片的宽高比,等比例对图片进行X和Y方向缩放,直到每个方向都大于等于组件对应的尺寸,缩放后的图片居中显示在组件中,超出部分做裁剪处理。


private void centerCrop() { 
      float scale; 
      float dx; 
      float dy; 
 
      if (getElementWidth() * getDisplayHeight() > getDisplayWidth() * getElementHeight()) { 
          scale = (float)getDisplayHeight() / (float)getElementHeight(); 
          dx = (getDisplayWidth() - getElementWidth() * scale) * 0.5f; 
          dy = 0f; 
      } else { 
          scale = (float)getDisplayWidth() / (float)getElementWidth(); 
          dx = 0f; 
          dy = (getDisplayHeight() - getElementHeight() * scale) * 0.5f; 
      } 
 
      matrix.setScale(scale, scale); 
      matrix.postTranslate(dx + 0.5f, dy + 0.5f); 
  } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

centerInside

如果图片宽度<= 组件宽度&&图片高度<= 组件高度,不执行缩放,居中显示在组件中。其余情况按 fitCenter 处理。


private void centerInside() { 
       if (getElementWidth() <= getDisplayWidth() && getElementHeight() <= getElementHeight()){ 
           float wTranslate = (getDisplayWidth() - getElementWidth()) * 0.5f; 
           float hTranslate = (getDisplayHeight() - getElementHeight()) * 0.5f;             
           matrix.setTranslate(wTranslate, hTranslate); 
       } else { 
           fitCenter(); 
       } 
   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

绘制图片组件

关键是这句 canvas.concat(matrix)

@Override 
    public void onDraw(Component component, Canvas canvas) { 
        // 以下是关键代码,将 matrix 应用到 canvas 
        canvas.concat(matrix);         
        // 将图片绘制到 canvas 
        element.drawToCanvas(canvas); 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

项目地址:my-image-component

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com