解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題

GitLqr發表於2018-09-05

一、需求

在螢幕邊緣顯示一張圖片,超出螢幕寬度時,只顯示圖片的左邊部分,並且不被擠壓,其餘部分剪下。但我在實際開發中,踩了個坑,這裡做個記錄,下面通過圖片直觀瞭解一下情況:

理想 現實
解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題
解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題

目前可以確定,這種情況會出現在使用RelativeLayout作為ImageView父控制元件的情況下,其他型別的ViewGroup效果如何,暫不確定。

二、分析

這部分是我對問題研究的記錄,心急的朋友可以直接跳到第三部分,看原始碼實現。

1、使用 HorizontalScrollView

在網上也百度到了一個類似的問題: Android ImageView 超出螢幕邊界 圖片會被擠壓

解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題

這位朋友遇到的問題跟我是差不多的,當子控制元件ImageView超過父控制元件尺寸時,ImageView顯示的圖片就會擠壓,熱心的網友們給他支了一個招:把ImageVIew放在HorizontalScrollView控制元件裡面。但是,我遇到的需求是不要滾動效果(不要跟我說可以遮蔽ScrollView滾動。。。),就單單顯示圖片的部分割槽域而已,使用HorizontalScrollView控制元件明顯不適用,只能另尋出路了。

2、使用 LinearLayout

實踐證明,使用LinearLayout作為ImageView的父控制元件,當ImageView超出父控制元件尺寸時,不會擠壓圖片。

解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題

這種方式是一種不錯的解決方案,但是,侷限性太大,大多數情況下,會希望使用RelativeLayout作為控制元件的父控制元件,所以,這種情況也不適用我目前的情況。

3、使用ImageView的ScaleType

因為前面的方案都要求將ImageView放置到特定父控制元件中,侷限性太大,嚴重的,可能會造成UI多次繪製,降低效能,造成畫面卡頓,所以,感覺還是從ImageView本身下手比較合理。

ImageView的ScaleType有如下幾種:

源自ImageView (一) ——從原始碼的角度分析ScaleType (縮放模式)

縮放模式 裁剪 按比例 放大 縮小 描述
MATIRX 未知 未知 未知 未知 通過設定setImageMatrix函式相應的矩陣,來完成。
FIT_XY 直接將圖片鋪滿整個view
CENTER 1)如果圖片較小,直接居中全部顯示2) 如果圖片較大,裁剪後居中顯示
CENTER_CROP 按比例縮放,按照縮放比例大的進行縮放,然後居中顯示(除非圖片和view的形狀一樣,否則一定存在裁剪,所以有的描述為圖片的長寬≥View的長寬)
CENTER_INSIDE 1) 如果圖片較小,直接放入view,不進行放大處理,居中顯示2) 將圖片按比例進行縮放,使得圖片完全展示,並且長或者寬等於view的長和寬(所以有的描述為圖片的長寬≤View的長寬)
FIT_START 對圖片的處理和FIT_CENTER,區別為:1)view的橫向有空,居左邊2)view的縱向有空,居上邊
FIT_CENTER(預設) 將圖片按比例進行縮放,使得圖片完全展示,並且長或者寬等於view的長和寬(所以有的描述為圖片的長寬≤View的長寬),最後居中顯示
FIT_END 對圖片的處理和FIT_CENTER,區別為:1) view的橫向有空,居右邊2) view的縱向有空,居下邊

通過上表描述,可以確定只有CENTER_CROP的效果比較符合,但還是有點區別,CENTER_CROP只會顯示中間區域部分,而我要的是顯示左邊區域,所以,下面就進行ImageView原始碼分析。

在ImageView中搜尋CENTER_CROP,定位到核心邏輯就在configureBounds()方法中,程式碼如下:

解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題

以下是 部落格:ImageView的ScaleType原理及效果分析 中對CENTER_CROP的解釋。

該模式按比例擴大圖片的尺寸並居中顯示,使得圖片長(寬)等於或大於View的長(寬)。

如果dwidth/dheight>vwidth/vheight(圖片的寬高比大於ImageView的寬高比),即vheight/dheight>vwidth/dwidth,也就是說ImageView和圖片高度比小於ImageView和圖片的寬度比,這時候取vheight/dheight的比例進行圖片縮放,這樣就能保證圖片寬度在進行同等比例縮放的時候,圖片寬度大於或等於ImageView的寬度,因為(vheight/dheight)* dwidth>vwidth。

同理,如果vwidth/dwidth>vheight/dheight時,取vwidth/dwidth作為圖片的縮放比例,可以保證縮放完成後圖片寬度等於ImageView的寬度,圖片的高度大於或等於ImageView的高度,因為(vwidth/dwidth)*dheight>vheight。圖片在縮放之後再進行CENTER操作即可。

vwidth/dwidth>vheight/dheight

上圖所示,vwidth/dwidth>vheight/dheight,圖片先按照vwidth/dwidth進行縮放,縮放後的圖片高度>=vheight,然後再進行向上移位。

vheight/dheight>vwidth/dwidth

上圖所示,vheight/dheight>vwidth/dwidth,圖片先按照vheight/dheight進行縮放,縮放後的圖片寬度>=vwidth,然後再進行想左移位。

回到主線,根據上面的分析,明白了CENTER_CROP的工作原理(可能也不太明白,但沒關係),我們只需要在CENTER_CROP的基礎上控制dx或dy即可,同時,在分析完整個configureBounds()方法之後,可以確定,不管是哪種ScaleType,其原理都是操控圖片矩陣mDrawMatrix來實現的,這時候ScaleType中的MATRIX就可以派上用場了,下面是我對MATRIX做的一些封裝,你也可以根據自己的專案需求,編寫自己的處理邏輯。

三、程式碼

用了在佈局中使用方便,我將它做成一個自定義控制元件,命名為ImageViewExt。

1、自定義屬性attrs_image_view_ext

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ImageViewExt">
        <attr name="ive_scale_type_matrix_ext" format="enum">
            <enum name="left_crop" value="1"/>
            <enum name="right_crop" value="2"/>
            <enum name="center_crop" value="3"/>
        </attr>
    </declare-styleable>
</resources>
複製程式碼

1、自定義控制元件ImageViewExt

/**
 * @建立者 CSDN_LQR
 * @時間 2018/9/4
 * @描述 ImageView擴充套件控制元件
 * <p>
 * 1、對 ScaleType.MATRIX 進行封裝擴充
 */
public class ImageViewExt extends ImageView {

    public static final int SCALE_TYPE_MATRIX_LEFT_CROP   = 1; // 等比例縮放,當影象超出控制元件尺寸時,保留左邊,其餘部分剪下掉。
    public static final int SCALE_TYPE_MATRIX_RIGHT_CROP  = 2; // 等比例縮放,當影象超出控制元件尺寸時,保留右邊,其餘部分剪下掉。
    public static final int SCALE_TYPE_MATRIX_CENTER_CROP = 3; // 等比例縮放,當影象超出控制元件尺寸時,保留中間,其餘部分剪下掉。

    @IntDef({SCALE_TYPE_MATRIX_LEFT_CROP, SCALE_TYPE_MATRIX_RIGHT_CROP, SCALE_TYPE_MATRIX_CENTER_CROP})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ScaleTypeMatrixExt {
    }

    private int mScaleTypeMatrixExt;

    public ImageViewExt(Context context) {
        this(context, null);
    }

    public ImageViewExt(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ImageViewExt(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    protected void init(Context context, @Nullable AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageViewExt);
        mScaleTypeMatrixExt = typedArray.getInt(R.styleable.ImageViewExt_ive_scale_type_matrix_ext, -1);
        typedArray.recycle();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        handScaleTypeMatrixExt();
    }

    @Override
    public void requestLayout() {
        super.requestLayout();
        handScaleTypeMatrixExt();
    }

    private void handScaleTypeMatrixExt() {
        if (this.getScaleType() == ScaleType.MATRIX && mScaleTypeMatrixExt != -1) {
            // 圖片實際尺寸
            final int dwidth = getDrawable().getIntrinsicWidth();
            final int dheight = getDrawable().getIntrinsicHeight();
            // ImageView圖片顯示尺寸
            final int vwidth = getWidth() - getPaddingLeft() - getPaddingRight();
            final int vheight = getHeight() - getPaddingTop() - getPaddingBottom();
            float scale;
            float dx = 0, dy = 0;
            if (dwidth * vheight > vwidth * dheight) {      // 圖片寬高比 > 控制元件寬高比
                scale = (float) vheight / (float) dheight;
                switch (mScaleTypeMatrixExt) {
                    case SCALE_TYPE_MATRIX_LEFT_CROP:
                        dx = 0;                             // 保留左邊
                        break;
                    case SCALE_TYPE_MATRIX_RIGHT_CROP:
                        dx = (vwidth - dwidth * scale);     // 保留右邊
                        break;
                    case SCALE_TYPE_MATRIX_CENTER_CROP:
                        dx = (vwidth - dwidth * scale) * 0.5f; // 保留中間(效果與 CENTER_CROP 一樣)
                        break;
                    default:
                        break;
                }
            } else {
                scale = (float) vwidth / (float) dwidth;
                // dy = (vheight - dheight * scale) * 0.5f; // 根據實際情況編寫,預設為0,保留上邊
            }
            Matrix matrix = new Matrix();
            matrix.setScale(scale, scale);
            matrix.postTranslate(Math.round(dx), Math.round(dy));
            this.setImageMatrix(matrix);
        }
    }

    public void setScaleTypeMatrixExt(@ScaleTypeMatrixExt int scaleTypeMatrixExt) {
        this.mScaleTypeMatrixExt = scaleTypeMatrixExt;
        requestLayout();
    }
}
複製程式碼

四、使用

需要使用matrix作為ImageView的ScaleType,再指定ive_scale_type_matrix_ext屬性即可,也可以在程式碼中呼叫setScaleType()、setScaleTypeMatrixExt()來指定。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="382.5dp"
    android:background="@android:color/white"
    android:orientation="horizontal">

    <com.lqr.widget.ImageViewExt
        android:layout_width="275dp"
        android:layout_height="135.5dp"
        android:layout_marginLeft="450dp"
        android:scaleType="matrix"
        android:src="@mipmap/main_recommand_1_4_sample3"
        app:ive_scale_type_matrix_ext="left_crop"/>

</LinearLayout>
複製程式碼

效果如下:

解決ImageView超出父控制元件(或螢幕邊界)時,圖片擠壓問題

相關文章