Android的ImageView背后的绘制原理

作者:木有粗面_9602
链接:https://www.jianshu.com/p/171fdd0db870
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

前言

上次被一位大佬问着这个问题,之前大家都是直接调imageView的自带api使用,深入底层的话,实际上对视频图像渲染也有一定的启迪作用。

ImageView的构造函数

 public ImageView(Context context) {
        super(context);
        //初始化一些变量和配置
        initImageView();
    }

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

    public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        //这里也调用到了这个函数
        initImageView();

        // ImageView is not important by default, unless app developer overrode attribute.
        if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
        }

        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
        //xml中设置的图片源
        final Drawable d = a.getDrawable(R.styleable.ImageView_src);
        if (d != null) {
            setImageDrawable(d);
        }
        //基线对齐
        mBaselineAlignBottom = a.getBoolean(R.styleable.ImageView_baselineAlignBottom, false);
        mBaseline = a.getDimensionPixelSize(R.styleable.ImageView_baseline, -1);
        //是否调整view边界
        setAdjustViewBounds(a.getBoolean(R.styleable.ImageView_adjustViewBounds, false));
        //设置View的最大宽
        setMaxWidth(a.getDimensionPixelSize(R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
        //设置View的最大高
        setMaxHeight(a.getDimensionPixelSize(R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
        //xml中的缩放类型
        final int index = a.getInt(R.styleable.ImageView_scaleType, -1);
        if (index >= 0) {
            setScaleType(sScaleTypeArray[index]);
        }

        if (a.hasValue(R.styleable.ImageView_tint)) {
            mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint);
            mHasDrawableTint = true;

            // Prior to L, this attribute would always set a color filter with
            // blending mode SRC_ATOP. Preserve that default behavior.
            mDrawableTintMode = PorterDuff.Mode.SRC_ATOP;
            mHasDrawableTintMode = true;
        }

        if (a.hasValue(R.styleable.ImageView_tintMode)) {
            mDrawableTintMode = Drawable.parseTintMode(a.getInt(
                    R.styleable.ImageView_tintMode, -1), mDrawableTintMode);
            mHasDrawableTintMode = true;
        }

        applyImageTint();
        //图片透明度
        final int alpha = a.getInt(R.styleable.ImageView_drawableAlpha, 255);
        if (alpha != 255) {
            setImageAlpha(alpha);
        }

        mCropToPadding = a.getBoolean(
                R.styleable.ImageView_cropToPadding, false);

        a.recycle();

        //need inflate syntax/reader for matrix
    }

在初始化时都用到了 initImageView();方法,看看着了方法做了什么;

 private void initImageView() {
        // 初始化了默认矩阵
        mMatrix = new Matrix();
       // 初始化了默认缩放类型是fit_center
        mScaleType = ScaleType.FIT_CENTER;

        if (!sCompatDone) {
            final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
            sCompatAdjustViewBounds = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
            sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
            sCompatDrawableVisibilityDispatch = targetSdkVersion < Build.VERSION_CODES.N;
            sCompatDone = true;
        }
    }

ImageView 的几个种设置图片的方式

我们知道,对于Imageview显示图片,常用的有一下几种方式

  • imaegView.setImageBitmap();

  • imaegView.setImageResource();

  • imaegView.setImageDrawable();

  • imaegView.setImageURI();

setImageBitmap()

 /**
     * Sets a Bitmap as the content of this ImageView.
     *
     * @param bm The bitmap to set
     */
    @android.view.RemotableViewMethod
    public void setImageBitmap(Bitmap bm) {
        // Hacky fix to force setImageDrawable to do a full setImageDrawable
        // instead of doing an object reference comparison
        mDrawable = null;
        if (mRecycleableBitmapDrawable == null) {
            mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
        } else {
            mRecycleableBitmapDrawable.setBitmap(bm);
        }
        setImageDrawable(mRecycleableBitmapDrawable);
    }

通过源码可知,setImageBitmap()最终是把传递过来的bitmap转换成一个Drawable对象,然后调用的setImageDrawable()方法,那setImageDrawable()方法又是干嘛的呢?

setImageDrawable

public void setImageDrawable(Drawable drawable) {
        if (mDrawable != drawable) {
            //资源置0
            mResource = 0;
            //uri置null
            mUri = null;
            //原来drawable的宽高
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            //更新此ImageView显示的drawable,此方法重要,几个设置图像的方法都调用
            updateDrawable(drawable);
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
               //重新布局
                requestLayout();
            }
            //重新绘制
            invalidate();
        }
    }

由源码可知,

  • 全局变量mResource设置为0,
  • 全局变量mUri置为null,
  • 把传递过来的drawable对象再传递到updateDrawable()法中

setImageResource()

public void setImageResource(int resId) {
        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;
        updateDrawable(null);
        mResource = resId;
        mUri = null;
        resolveUri();
        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }

由源码可知:

  • 没有Drawable对象,所以 updateDrawable()传递null
  • 把传递过来的res对象赋值给 全局变量mResource,
  • 全局变量mUri置为null,
  • 调用 resolveUri()

setImageURI()

 public void setImageURI(Uri uri) {
        if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
            updateDrawable(null);
            mResource = 0;
            mUri = uri;
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            resolveUri();
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            invalidate();
        }
    }

由源码可知:

  • 全局变量mResource设置为0
  • 把传递过来的uri对象赋值给全局变量mUri,
  • 由于没有Drawable对象,所以 updateDrawable()递null
  • 调用 resolveUri()

从上可知,有两个方法挺关键的,一个是updateDrawable(),还有就是resolveUri(),接下来就查查这两个方法是干嘛的。

updateDrawable()更新ImageView需要显示Drawable。

    private void updateDrawable(Drawable d) {
        if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
            mRecycleableBitmapDrawable.setBitmap(null);
        }

        boolean sameDrawable = false;

        if (mDrawable != null) {
            sameDrawable = mDrawable == d;
            mDrawable.setCallback(null);
            unscheduleDrawable(mDrawable);
            if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
                mDrawable.setVisible(false, false);
            }
        }
        //赋值,更新此ImageView显示的drawable
        mDrawable = d;

        if (d != null) {
            d.setCallback(this);
            d.setLayoutDirection(getLayoutDirection());
            if (d.isStateful()) {
                d.setState(getDrawableState());
            }
            if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
                final boolean visible = sCompatDrawableVisibilityDispatch
                        ? getVisibility() == VISIBLE
                        : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
                d.setVisible(visible, true);
            }
            d.setLevel(mLevel);
            mDrawableWidth = d.getIntrinsicWidth();
            mDrawableHeight = d.getIntrinsicHeight();
            
           // 设置图片的着色列表,这里会用到颜色状态列表ColorStateList
            applyImageTint();
          //  设置图片的颜色过滤器,即设置ColorFilter
            applyColorMod();
            
      //  这个重要,是指根据设置的ScaleType,使用对应的矩阵对Drawable进行变换,以显示合适的区域
            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;
        }
    }

resolveUri()

private void resolveUri() {
        if (mDrawable != null) {
            return;
        }
        Resources rsrc = getResources();
        if (rsrc == null) {
            return;
        }
        Drawable d = null;
        if (mResource != 0) {
            try {
                d = rsrc.getDrawable(mResource);
            } catch (Exception e) {
                mUri = null;
            }
        } else if (mUri != null) {
            String scheme = mUri.getScheme();
            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
                try {
                    ContentResolver.OpenResourceIdResult r = mContext.getContentResolver().getResourceId(mUri);
                    d = r.r.getDrawable(r.id);
                } catch (Exception e) {
                    Log.w("ImageView", "Unable to open content: " + mUri, e);
                }
            } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
                    || ContentResolver.SCHEME_FILE.equals(scheme)) {
                InputStream stream = null;
                try {
                    stream = mContext.getContentResolver().openInputStream(mUri);
                    d = Drawable.createFromStream(stream, null);
                } catch (Exception e) {
                    Log.w("ImageView", "Unable to open content: " + mUri, e);
                } finally {
                    if (stream != null) {
                        try {
                            stream.close();
                        } catch (IOException e) {
                            Log.w("ImageView", "Unable to close content: " + mUri, e);
                        }
                    }
                }
        } else {
                d = Drawable.createFromPath(mUri.toString());
            }
            if (d == null) {
                System.out.println("resolveUri failed on bad bitmap uri: " + mUri);
                // Don't try again.
                mUri = null;
            }
        } else {
            return;
        }
        updateDrawable(d);
    }

通过上面的代码可知

  • 如果mResource不为null ,把mResource 转换成一个Drawable对象,然后执行updateDrawable()
  • 如果mUri不为null,就把uri转换成一个Drawable对象,然后执行updataDrawable()方法

所以,不管是setImageUri还是setImageDrawable或者setImageResource()或者setImageBitmap

  • 首先都是把传递过来的对象转换成一个Drawable对象,
  • 然后把执行updataDrawable()方法,
  • 因为之前在updataDrawable 中重新设置的宽高,所以执行requestLayout() 重新布局view
  • 最后执行invalidate()重新绘制

对于上面的updateDrawale中的三个方法:

1. applyImageTint()

   // ImageView在的Drawable 的着色变量

     private Drawable mDrawable = null;
    private BitmapDrawable mRecycleableBitmapDrawable = null;
  //  颜色状态列表
    private ColorStateList mDrawableTintList = null;
   // 着色模式,有16种
    private PorterDuff.Mode mDrawableTintMode = null;
    private boolean mHasDrawableTint = false;
    private boolean mHasDrawableTintMode = false;

 private void applyImageTint() {
        if (mDrawable != null && (mHasDrawableTint || mHasDrawableTintMode)) {
            mDrawable = mDrawable.mutate();

            if (mHasDrawableTint) {
             //  设置着色集合
                mDrawable.setTintList(mDrawableTintList);
            }

            if (mHasDrawableTintMode) {
            //   设置着色模式
                mDrawable.setTintMode(mDrawableTintMode);
            }

            // The drawable (or one of its children) may not have been
            // stateful before applying the tint, so let's try again.
            if (mDrawable.isStateful()) {
                mDrawable.setState(getDrawableState());
            }
        }
    }

1.1 ColorStateList类

ColorStateList是颜色状态列表类,ColorStateList是从XML中定义的XML资源文件中创建的应用程序资源目录的“color”子目录。 XML文件包含一个单一的“选择器”元素内有多个“item”元素。

<selector xmlns:android="http://schemas.android.com/apk/res/android"/>
   < item android:state_focused="true"
           android:color="@color/sample_focused" />;
     <item android:state_pressed="true"
            android:state_enabled="false"
            android:color="@color/sample_disabled_pressed" />;
      <item android:state_enabled="false"
           android:color="@color/sample_disabled_not_pressed" />;
     <item android:color="@color/sample_default" />;
   </selector/>

这定义了一组状态规格/颜色对,其中每个状态规范指定了一组表示视图必须处于或不在,颜色指定相关的颜色与该规范。
安卓控制Texview的文字颜色切换:

<?xml version="1.0" encoding="utf-8"?>  
<selector xmlns:android="http://schemas.android.com/apk/res/android">  
    <item android:state_focused="true" android:color="#ff2525"></item>  
    <item android:color="#ff5b5b5b"></item>  
</selector>  
ColorStateList csl=(ColorStateList)getResources().getColorStateList(R.color.button_text);  
       for (int i =0;i<3;i++){  
           Button btn =  new Button(mContext);  
           btn.setText("按钮"+i);  
           btn.setTextColor(csl);  
           this.addView(btn,params);  
       }  

2. applyColorMod()

// these are applied to the drawable
    
   // 颜色过滤器   
    private ColorFilter mColorFilter = null;
    private boolean mHasColorFilter = false;
    private Xfermode mXfermode;
    private int mAlpha = 255;
    private final int mViewAlphaScale = 256;
    private boolean mColorMod = false;

 private void applyColorMod() {
        // Only mutate and apply when modifications have occurred. This should
        // not reset the mColorMod flag, since these filters need to be
        // re-applied if the Drawable is changed.
        if (mDrawable != null && mColorMod) {
            mDrawable = mDrawable.mutate();
            if (mHasColorFilter) {
                // 设置颜色过滤器
                mDrawable.setColorFilter(mColorFilter);
            }
            mDrawable.setXfermode(mXfermode);
            mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
        }
    }

2.1 ColorFilter类的使用

ColorFilter主要用来处理颜色,这里将讲解它的三个子类,ColorMatrixColorFilter,LightingColorFilter以及PorterDuffColorFilter。

** ColorMatrixColorFilter**

这个类主要是使用matrix也就是矩阵对颜色做运算,矩阵的形态如下:

颜色值和该矩阵的换算关系如下:

RGB和Alpha的终值计算方法如下:
Red通道终值= a[0] * srcR + a[1] * srcG + a[2] * srcB + a[3] * srcA + a[4]
Green通道终值= a[5] * srcR + a[6] * srcG + a[7] * srcB + a[8] * srcA + a[9]
Blue通道终值= a[10] * srcR + a[11] * srcG + a[12] * srcB + a[13] * srcA + a[14]
Alpha通道终值= a[15]*srcR+a[16]*srcG + a[17] * srcB + a[18] * srcA + a[19]

备注:
srcR为原图Red通道值,srcG为原图Green通道值,srcB为原图Blue通道值,srcA为原图Alpha通道值。
每个通道的源值和终值都在0到255的范围内。即使计算结果大于255或小于0,值都将被限制在0到255的范围内。

看了上面的说明可能不太清晰,这里再看看例子

上面有3张图片,其实是同一个图片绘制的,只是使用了不同的ColorMatrixColorFilter,最后面一张是完全黑白的。

private final static float[] MATRIX = new float[] {
            0.5f, 0, 0, 0, 0,
            0, 0.5f, 0, 0, 0,
            0, 0, 0.5f, 0, 0,
            0, 0, 0, 1, 0 };

Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.home);

canvas.drawBitmap(bitmap, 100, 0, paint);

ColorMatrixColorFilter filter = new ColorMatrixColorFilter(MATRIX);
paint.setColorFilter(filter);
canvas.drawBitmap(bitmap, 100, 500, paint);


ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(bitmap, 100, 1000, paint);

MATRIX中的值是0.5使得R,G,B通道的值都减半了,所以第二张图看起来暗了很多。第三张图使用了ColorMatrix,并且使用colorMatrix.setSaturation(0)将饱和度设置为了0,这样一来就变为了黑白图片。

明白了上面颜色运算的规则,就可以自己更改矩阵的值从而达到想要的效果。

LightingColorFilter

LightingColorFilter是上面ColorMatrixColorFilter的一个简化版本,构造函数也比较简单:

public LightingColorFilter(int mul, int add)  

mul代表multiply,也就是乘法。add代表加法,也就是颜色偏移量

那么这里将怎么计算颜色值呢,其实跟上面矩阵的方式差不多。这里两个参数都是int类型,所以每8位被分别分解为RGB对应的值来做乘法和加法。

Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.home);

canvas.drawBitmap(bitmap, 100, 0, paint);

LightingColorFilter filter = new LightingColorFilter(0x888888, 0x000000);
paint.setColorFilter(filter);
canvas.drawBitmap(bitmap, 100, 500, paint);

LightingColorFilter filter2 = new LightingColorFilter(0x888888, 0x555555);
paint.setColorFilter(filter2);
canvas.drawBitmap(bitmap, 100, 1000, paint);

看第二张图片的ColorFilter构造new LightingColorFilter(0x888888, 0x000000);

其中mul为0x888888,那么RGB通道对应的mul值都为88,add为0x000000则对应的偏移量都为0。第二张图变暗了,基本可以看出计算方法。

color = colormul/255+add (计算结果大于等于255都指定为255)

其中color可以为RGB三种通道中的一种,mul和add分别为通道对应的值。假设R通道的值就为 R=R0x88/0xff+0

0x88/0xff肯定是小于1的,所以颜色变暗了。

第三张图的mul值和第二张相同,但是由于第三张图的add值比较大,所以反而比第一张图还亮。

PorterDuffColorFilter

public PorterDuffColorFilter(int srcColor, PorterDuff.Mode mode)  

srcColor源颜色,mode是色彩的混合模式,这里的混合模式我们在后面再详细讲解,这里简单看一个示例。

Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.home);

canvas.drawBitmap(bitmap, 100, 0, paint);

PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.BLUE, PorterDuff.Mode.MULTIPLY);
paint.setColorFilter(filter);
canvas.drawBitmap(bitmap, 100, 500, paint);

这里PorterDuffColorFilter的两个参数分别是Color.BLUE以及PorterDuff.Mode.MULTIPLY

先不要纠结PorterDuff.Mode.MULTIPLY的含义,这里主要是将Color.BLUE提供的蓝色和原图进行一定的合并运算。就得到了上面的效果,那么传入不同PorterDuff.Mode值就能得到不同的效果。各个值不同的效果我们在后面的篇幅中详细讲解。

关于PorterDuff.Mode,Android系统一共提供了18种混排模式,在模拟器的ApiDemos/Graphics/XferModes,有张效果图:

这张图可以很形象的说明图片各种混排模式下的效果。其中Src代表原图,Dst代表目标图,两张图片使用不同的混排方式后,得到的图像是如上图所示的。PorterDuff.Mode也提供了18种混排模式已经算法,其中比上图多了ADD和OVERLAY两种模式。

其中Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色,[...,..]前半部分计算的是结果图像的Alpha通道值,“,”后半部分计算的是结果图像的颜色值。图像混排后是依靠这两个值来重新计算ARGB值的,具体计算算法,抱歉,我也不知道,不过不要紧,不了解计算算法也不影响我们程序员写程序的。我们只要对照上面的apiDemo中提供的图片就能推测出混排后的结果的,下面是在网上找到的汉字语言描述,感谢这位作者的总结。

注意:先绘制dst,再绘制src。

1.PorterDuff.Mode.CLEAR

所绘制源图像不会提交到画布上。

2.PorterDuff.Mode.SRC

只显示源图像。

3.PorterDuff.Mode.DST

只显示目标图像。

4.PorterDuff.Mode.SRC_OVER

正常绘制显示,源图像居上显示。

5.PorterDuff.Mode.DST_OVER

上下层都显示。目标图像居上显示。

6.PorterDuff.Mode.SRC_IN

取两层绘制交集中的源图像。

7.PorterDuff.Mode.DST_IN

取两层绘制交集中的目标图像。

8.PorterDuff.Mode.SRC_OUT

只在源图像和目标图像不相交的地方绘制源图像。

9.PorterDuff.Mode.DST_OUT

只在源图像和目标图像不相交的地方绘制目标图像。

10.PorterDuff.Mode.SRC_ATOP

在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像。

11.PorterDuff.Mode.DST_ATOP

在源图像和目标图像相交的地方绘制目标图像而在不相交的地方绘制源图像。

12.PorterDuff.Mode.XOR

异或:去除两图层交集部分

13.PorterDuff.Mode.DARKEN

取两图层全部区域,交集部分颜色加深

14.PorterDuff.Mode.LIGHTEN

取两图层全部,点亮交集部分颜色

15.PorterDuff.Mode.MULTIPLY

取两图层交集部分叠加后颜色

16.PorterDuff.Mode.SCREEN

滤色。

PorterDuff的源码

package android.graphics;

  这个类包含了alpha合成和混合模式的列表
  可以传递给{@link PorterDuffXfermode},一个专门的实现
  {@link Paint}的{@link Paint#setXfermode(Xfermode)传输模式}。
  所有可用的模式都可以在{@link Mode}枚举中找到

public class PorterDuff {
   
    public enum Mode {
        // these value must match their native equivalents. See SkXfermode.h
     
        CLEAR       (0),
   
        SRC         (1),
       
        DST         (2),
     
        SRC_OVER    (3),
      
        DST_OVER    (4),
        
        SRC_IN      (5),
      
        DST_IN      (6),
      
        SRC_OUT     (7),
      
        DST_OUT     (8),
       
        SRC_ATOP    (9),
       
        DST_ATOP    (10),
       
        XOR         (11),
        
        DARKEN      (16),
       
        LIGHTEN     (17),
       
        MULTIPLY    (13),
       
        SCREEN      (14),
       
        ADD         (12),
        
        OVERLAY     (15);

        Mode(int nativeInt) {
            this.nativeInt = nativeInt;
        }

        /**
         * @hide
         */
        public final int nativeInt;
    }

    /**
     * @hide
     */
    public static int modeToInt(Mode mode) {
        return mode.nativeInt;
    }

    /**
     * @hide
     */
    public static Mode intToMode(int val) {
        switch (val) {
            default:
            case  0: return Mode.CLEAR;
            case  1: return Mode.SRC;
            case  2: return Mode.DST;
            case  3: return Mode.SRC_OVER;
            case  4: return Mode.DST_OVER;
            case  5: return Mode.SRC_IN;
            case  6: return Mode.DST_IN;
            case  7: return Mode.SRC_OUT;
            case  8: return Mode.DST_OUT;
            case  9: return Mode.SRC_ATOP;
            case 10: return Mode.DST_ATOP;
            case 11: return Mode.XOR;
            case 16: return Mode.DARKEN;
            case 17: return Mode.LIGHTEN;
            case 13: return Mode.MULTIPLY;
            case 14: return Mode.SCREEN;
            case 12: return Mode.ADD;
            case 15: return Mode.OVERLAY;
        }
    }
}

3. applyColorMod()

 private void configureBounds() {
        if (mDrawable == null || !mHaveFrame) {
            return;
        }
       // 图片drawable的宽和高
        final int dwidth = mDrawableWidth;
        final int dheight = mDrawableHeight;
       // imageview用于显示区域的宽和高
        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);
        // ScaleType.FIT_XY 的缩放类型
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* If the drawable has no intrinsic size, or we're told to
                scaletofit, then we just fill our entire view.
            */
          //  选择mDrawable的区域显示
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            mDrawable.setBounds(0, 0, dwidth, dheight);
           //   ScaleType.MATRIX 的缩放类型
            if (ScaleType.MATRIX == mScaleType) {
                // Use the specified matrix as-is.
                if (mMatrix.isIdentity()) {
                    mDrawMatrix = null;
                } else {
                    mDrawMatrix = mMatrix;
                }
            } else if (fits) {
                // The bitmap fits exactly, no transform needed.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) {
                //   ScaleType.CENTER 的缩放类型
                // Center bitmap in view, no scaling.
                mDrawMatrix = mMatrix;
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                                         Math.round((vheight - dheight) * 0.5f));
            } else if (ScaleType.CENTER_CROP == mScaleType) {
               //   ScaleType.CENTER_CROP 的缩放类型
                mDrawMatrix = mMatrix;

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }
               // 先对图片缩放
                mDrawMatrix.setScale(scale, scale);
               // 在平移
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
                ScaleType.CENTER_INSIDE 的缩放类型
                mDrawMatrix = mMatrix;
                float scale;
                float dx;
                float dy;

                if (dwidth <= vwidth && dheight <= vheight) {
                  //  图片宽高小于view宽高时,不缩放
                    scale = 1.0f;
                } else {
                 //  计算缩放
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }

                dx = Math.round((vwidth - dwidth * scale) * 0.5f);
                dy = Math.round((vheight - dheight * scale) * 0.5f);
              //  先缩放 后平移
                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(dx, dy);
            } else {
                // Generate the required transform.
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);

                mDrawMatrix = mMatrix;
               //剩下的Fit_XY等Fit系列,都在这里
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }
    private static final Matrix.ScaleToFit[] sS2FArray = {
        Matrix.ScaleToFit.FILL,
        Matrix.ScaleToFit.START,
        Matrix.ScaleToFit.CENTER,
        Matrix.ScaleToFit.END
    };

    private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st)  {
        // ScaleToFit enum to their corresponding Matrix.ScaleToFit values
        return sS2FArray[st.nativeInt - 1];
    }

 /**
      *将矩阵设置为比例并转换将源矩形映射到的值
      *目标矩形,如果结果可以表示,则返回true。
     *
      * @param src要映射的源矩形。
      * @参数dst要映射到的目标矩形。
      * @参数stf ScaleToFit选项
      * @如果矩阵可以用矩形映射表示,则返回true。
     */
    public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
        if (dst == null || src == null) {
            throw new NullPointerException();
        }
        return nSetRectToRect(native_instance, src, dst, stf.nativeInt);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值