【Android_View】ImageView源码简析笔记(三)

来源:互联网 发布:禁止网络标语 编辑:程序博客网 时间:2024/06/03 15:16

ImageView源码简析


ImageView的绘制–onDraw()

在【Android_View】ImageView源码简析笔记(二)一文中,我们简要回顾了ImageView的测量方法即onMeasure().
我们知道,View的绘制基本上有个【三步论】:测量—布局—绘制。
对于【布局】,很明显,这适应于ViewGroup,即父布局对子View进行布局操作,确定各个子View的布局位置。而这次我们探讨的ImageView是直接继承于VIew的,那么在这就不需考虑相关的布局问题。接下来我们一起看看ImageView的绘制—onDraw()吧!


那么老规矩,我们先看onDraw()方法的源代码:

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //Drawable对象为空,不作任何操作        if (mDrawable == null) {            return;         }        //Drawable对象的宽高为0,不作任何操作        if (mDrawableWidth == 0 || mDrawableHeight == 0) {            return;              }        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {        //Drawable的转换矩阵为空 且 图像紧邻坐标轴与周围没有边距        //===> 直接绘制            mDrawable.draw(canvas);        } else {       //返回Canvas私有堆栈上矩阵/剪辑状态的数量。       //这等于#save()调用 - #restore()调用。            final int saveCount = canvas.getSaveCount();            //保存画布状态            canvas.save();            if (mCropToPadding) {                final int scrollX = mScrollX;                final int scrollY = mScrollY;                //canvas生成矩形裁剪区域                canvas.clipRect(scrollX + mPaddingLeft,                 scrollY + mPaddingTop,                scrollX + mRight - mLeft - mPaddingRight,                scrollY + mBottom - mTop - mPaddingBottom);            }            //canvas移动            canvas.translate(mPaddingLeft, mPaddingTop);            if (mDrawMatrix != null) {                //矩阵转换                canvas.concat(mDrawMatrix);            }            //在canvas上绘制mDrawable            mDrawable.draw(canvas);            //canvas恢复状态            canvas.restoreToCount(saveCount);        }    }

首先来看clipRect()方法。

public boolean clipRect(float left, float top, float right, float bottom) {        return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);    }

这是Canvas类中的方法,我们都知道canvas就是View体系中的画布,所有的图形图像都要在这上面显示。
clipRect()看名称是”裁剪”矩形区域的。而实际上,见名之意,这个方法主要是用来设定canvas的显示区域的。
四个形参分别代表—矩形裁剪区左、上、右、下坐标的位置。
当生成的裁剪区域不为空时,返回true。
特别注意,这个剪切,是不影响【原有的】【已经绘制好的】图形的。


再来看translate()方法:

public void translate(float dx, float dy) {        native_translate(mNativeCanvasWrapper, dx, dy);    }

很明显,根据前缀我们可以看出,native_translate()这个方法是没有java方法体的。根据注释:

Preconcat the current matrix with the specified translation     * @param dx The distance to translate in X     * @param dy The distance to translate in Y

可知,这是用对【当前矩阵】进行指定的转换。

Preconcat

上面这个单词,在实际查询中并没有找到与之匹配的直接到含义。
在Stack Overflow中找到一个答案供大家参考:
Preconcat含义
上面说到:这只是定义的一种矩阵操作。当然与之对应的还有一种操作叫做:【postConcat】。
还有一篇比较详细的解读,也来自Stack Overflow。附上地址,供大家参考:参考解释
不过,简单点看:【preconcat】和【postConcat】实际代表着两种不同的矩阵操作。
举个栗子:M.preConcat(other) 与M.postConcat(other)
【preconcat】 意味着 M’ = M * other
【postConcat】意味着 M’ = other * M


好了,再来看canvas.concat(mDrawMatrix);

 public void concat(@Nullable Matrix matrix) {        if (matrix != null) native_concat(mNativeCanvasWrapper, matrix.native_instance);    }

原方法的注释为:

Preconcat the current matrix with the specified matrix. If the specified matrix is null, this method does nothing.

很明显,就是用指定的矩阵(the specified matrix)【Preconcat操作】当前的矩阵。还有:若指定的矩阵为空,则不进行任何操作。
在onDraw()方法中,

canvas.concat(mDrawMatrix);

的含义是:用onMeasure()方法设定好的图像转换矩阵mDrawMatrix来操作canvas已达到完成相应的显示效果。有关mDrawMatrix的相应操作与赋值,请参考上篇文章所描述的内容。


通过源码我们可以看到,执行完了相应的矩阵操作后,紧接着就是绘制:

mDrawable.draw(canvas);

draw()方法的源码如下:

public abstract void draw(@NonNull Canvas canvas);

这是Drawable类中的方法。可以看到,因为有”abstract”我们知道这是一个抽象方法,当中是不存在方法体的。
再看注释:

Draw in its bounds (set via setBounds) respecting optional effects such as alpha (set via setAlpha) and color filter (set via setColorFilter).* @param canvas The canvas to draw into

这个方法是运用【透明度】【颜色滤镜】等效果在【边界】内绘制相关内容。
实际上,Drawable类中有一个

setBounds(int left, int top, int right, int bottom)

方法来设定绘制的边界(实质上是一个矩形)。
在制定完绘制的边界后,就调用Drawable.draw()在刚才指定的边界区域之内绘制图像。


再来看最后一个方法:restoreToCount(int saveCount);

public void restoreToCount(int saveCount) {        boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();        native_restoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow);    }

很明显,这个方法和之前的canvas.save()方法相辅相成。

  • canvas.save()保存了当前画布的状态后,
  • 我们又在canvas绘制了内容,
  • 待绘制结束后,随即调用canvas.restoreToCount(int saveCount)来恢复canvas的状态。

这样看来,canvas.restoreToCount(int saveCount)与canvas.save()都是在保存canvas的状态,怎么没什么区别呢?
没关系,那肯定是我们想的不够细致。
看到restoreToCount()方法中还有一个int 形参saveCount了么??
实际上,在一次操作中我们可以多次保存canvas的状态,举个栗子:

 canvas.translate(500,500);  // 第一次保存Canvas的状态 ===> 状态1 int save1 = canvas.save();  canvas.scale(2, 2); // 第二次保存Canvas的状态 ===> 状态2 int save2 = canvas.save();  canvas.translate(100,100);  // 第三次保存Canvas的状态 ===> 状态3 int save3 = canvas.save();  canvas.restore(); // 返回最近保存的save状态,即状态3 canvas.restoreToCount(save1);//返回到指定的状态,此处为状态1

看完上述栗子之后,相信大家对此已经比较清楚了。


Ok。到这里,我们就简单的看完了ImageView的onDraw()的方法了。
这就是ImageView 最基本的【绘制】过程。要想透彻的理解ImageView,仅仅知道”三步论”可能还远远不够。没关系,接下来我们在一起看看ImageView还有什么神秘的内容,毕竟源码可是有1588行啊。

接下来,继续努力!

原创粉丝点击