您现在的位置是:亿华云 > 域名

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

亿华云2025-10-03 06:33:36【域名】2人已围观

简介想了解更多内容,请访问:和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com本文将从零开始实现一个图片组件,并展示如何使用 Matrix 实现图片的各种 ScaleT

想了解更多内容,实现e缩请访问:

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

https://harmonyos.51cto.com

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

背景知识:

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

Matrix提供了Translate(平移)、Scale(缩放)、实现e缩Rotate(旋转)、各种Skew(扭曲)四中变换操作,图片这四种操作实质上是实现e缩调用了setValues()方法来设置矩阵数组来达到变换效果。除Translate(平移)外,各种Scale(缩放)、图片Rotate(旋转)、实现e缩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);     } } 

然后执行初始化流程:

// 初始化,包括读取属性,根据 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();    } 

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> 

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

center

/**     * 图片居中显示在组件中,源码库不对图片进行缩放     */    private void center() {         float wTranslate = (getDisplayWidth() - getElementWidth()) * 0.5f;        float hTranslate = (getDisplayHeight() - getElementHeight()) * 0.5f;         matrix.postTranslate(wTranslate, hTranslate);    } 

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);     } 

fitXY

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

private void fitXY(){         float wPercent = (float)getDisplayWidth() / (float)getElementWidth();        float hPercent = (float)getDisplayHeight() / (float)getElementHeight();        matrix.setScale(wPercent, hPercent);    } 

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);   } 

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);  } 

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);   } 

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();        }    } 

绘制图片组件

关键是这句 canvas.concat(matrix)

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

项目地址:my-image-component

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

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

https://harmonyos.51cto.com

-->

很赞哦!(7635)