當前位置:網站首頁>HarmonyOS 用 Matrix 實現各種圖片 ScaleType 縮放

HarmonyOS 用 Matrix 實現各種圖片 ScaleType 縮放

2021-08-20 10:54:14 HarmonyOS技術社區

本文將從零開始實現一個圖片組件,並展示如何使用 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 差异,准備了一大一小兩張圖片。

big.jpg
small.jpg

用於預覽的 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

center.PNG

    /** * 圖片居中顯示在組件中,不對圖片進行縮放 */
    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方向縮放,直到一個方向鋪滿組件,縮放後的圖片居中顯示在組件中

fitCenter.PNG

    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方向獨立縮放,直到圖片鋪滿組件。這種方式可能會改變圖片原本的寬高比,導致圖片拉伸變形。

fitXY.PNG

    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方向縮放,直到一個方向鋪滿組件,縮放後的圖片與組件左上角對齊進行顯示。

fitStart.PNG

    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方向縮放,直到一個方向鋪滿組件,縮放後的圖片與組件右下角對齊進行顯示。

fitEnd.PNG

    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方向縮放,直到每個方向都大於等於組件對應的尺寸,縮放後的圖片居中顯示在組件中,超出部分做裁剪處理。

centerCrop.PNG

    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 處理。

centerInside.PNG

    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.

更多原創內容請關注: 中軟國際 HarmonyOS 技術學院
入門到精通、技巧到案例,系統化分享HarmonyOS開發技術,共建鴻蒙生態,歡迎投稿和訂閱,讓我們一起攜手前行共建鴻蒙生態。

想了解更多關於鴻蒙的內容,請訪問:

51CTO和華為官方戰略合作共建的鴻蒙技術社區

https://harmonyos.51cto.com/#bkwz

版權聲明
本文為[HarmonyOS技術社區]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2021/08/20210820105413535s.html

隨機推薦