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

来源:互联网 发布:淘宝软文代写怎么赚钱 编辑:程序博客网 时间:2024/06/07 01:57

ImageView源码简析


前面几篇文章我们重点看了ImageView的构造器与”三步论”。这次,我们在一起看看ImageView中的其它方法吧。
除了三步论,好戏还有很多呀。


因为在【Android_View】ImageView源码简析笔记(一)中我们曾经一起阅读过部分其它方法的源码,以此为开始。那么这里我们必然是从Step 2开始了。


其它方法简析(2)

1.1 ImageView中的Drawable

1.1.1 获取Drawable资源

很明显,这个方法返回的mDrawable是我们整个ImageView操作的基础。

 public Drawable getDrawable() {        if (mDrawable == mRecycleableBitmapDrawable) {            // 如果考虑到缓存,我们需要将当前的引用清空            mRecycleableBitmapDrawable = null;        }        return mDrawable;    }

对于这个可被回收的位图资源–【mRecycleableBitmapDrawable】

private BitmapDrawable mRecycleableBitmapDrawable = null;

可以看到,这是ImageView中的成员变量。为BitmapDrawable类型,而

public class BitmapDrawable extends Drawable {…}

所以可以很清楚看到它的类型关系。
浏览整个ImageView代码,可以发现mRecycleableBitmapDrawable一共有1处有效赋值的地方:
* 1、首先是在setImageBitmap()中:

public void setImageBitmap(Bitmap bm) {        mDrawable = null;        if (mRecycleableBitmapDrawable == null) {        //【新建Bitmap并赋值给mRecycleableBitmapDrawable】            mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);        } else {            mRecycleableBitmapDrawable.setBitmap(bm);        }        setImageDrawable(mRecycleableBitmapDrawable);    }

这个方法的作用是设置一个Bitmap作为ImageView的显示内容。

Bitmap实际上就是我们常说的【位图】。(ps:与之相对,android L(5.0)之后,带引入了VectorDrawable即【矢量图】的支持)

来看setImageDrawable()方法:

public void setImageDrawable(@Nullable Drawable drawable) {        if (mDrawable != drawable) {            mResource = 0;            mUri = null;            final int oldWidth = mDrawableWidth;            final int oldHeight = mDrawableHeight;            //对ImageView的mDrawable变量进行赋值,并设置初始的宽高及其他属性            updateDrawable(drawable);            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {                //完全重绘                requestLayout();            }            //UI线程中重绘,仅在onDraw()过程中            invalidate();        }    }

这个方法是将一个Drawable对象,设置为ImageView的内容。
起作用的,主要是updateDrawable()方法。我们在ImageView源码简析笔记(二)一起阅读过源码过它的源码,这里不再多做解释。

综上所述:
1、当mRecycleableBitmapDrawable为空时,我们新建BitmapDrawable对象,并将传入的Bitmap引入其中;当其不为空时,我们直接通过setBitmap()方法引入Bitmap对象。上述则是两种不同情景下,mRecycleableBitmapDrawable的赋值情况。
2、最后,通过setImageDrawable()实现View对当前缓存对象的引用。


1.1.2添加(设置)图片的状态(State)

public void setImageState(int[] state, boolean merge) {        mState = state;        mMergeState = merge;        if (mDrawable != null) {            refreshDrawableState();            resizeFromDrawable();        }    }

方法开始处两个赋值操作的操作对象都是ImageView的成员变量。

    private int[] mState = null;    private boolean mMergeState = false;

除了上述方法外,ImageView类中关于mState的操作做要集中于方法:

@Override    public int[] onCreateDrawableState(int extraSpace) {        if (mState == null) {            return super.onCreateDrawableState(extraSpace);        } else if (!mMergeState) {            return mState;        } else {            return mergeDrawableStates(super.onCreateDrawableState(extraSpace + mState.length), mState);        }    }

方法的主要作用为:

Generate the new {@link android.graphics.drawable.Drawable} state for this view.

即:为当前View设置图像状态。在很多自定义View的情况下,我们可以使用这个变量完成相应的设计目的。观察上述代码,我们发现返回值在三种不同的情形有三种情况:

  • 首先,当mState为空时,需要调用父类View的onCreateDrawableState()方法。

  • 其次,当mMergeState变量取值为false时,返回【mState】。

  • 最后一种情况则是返回mergeDrawableStates()的结果值。这个方法属于View类。merge的意思大家都知道:合并。这个方法就是将一些自己定义的状态合并到原有的系统状态中。第一个参数【super.onCreateDrawableState(extraSpace +mState.length)】指的是【baseState】,也就是基础状态。而第二个参数为【additionalState】,也就是需要添加的我们自定义的状态。

refreshDrawableState()方法属于View类。调用之后,强制视图更新其可绘制的状态。在这里就是,我们添加了mState状态后调用这个方法,使得控件可以显示我们自己添加的state

public void refreshDrawableState() {        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;        //只要view中State的改变影响了drawable的显示状态就立刻调用        drawableStateChanged();        ViewParent parent = mParent;        if (parent != null) {            //通知ViewParent子View的状态的改变            parent.childDrawableStateChanged(this);        }    }

resizeFromDrawable()方法属于ImageView类。

private void resizeFromDrawable() {        final Drawable d = mDrawable;        if (d != null) {            int w = d.getIntrinsicWidth();            if (w < 0) w = mDrawableWidth;            int h = d.getIntrinsicHeight();            if (h < 0) h = mDrawableHeight;            if (w != mDrawableWidth || h != mDrawableHeight) {                mDrawableWidth = w;                mDrawableHeight = h;                requestLayout();            }        }    }

这个方法的作用是:添加状态后,如果成员变量mDrawable的尺寸发生了变化(变为初始值-1即没有了固有的尺寸),则将其宽高重新设置为mDrawableWidth与mDrawableHeight,最终再重新测量绘制。


补充一个关于【固有尺寸–intrinsic】的介绍,拿Intrinsic width举例:

Intrinsic width is the width at which the drawable would like to be laid out, including any inherent padding. If the drawable has no intrinsicwidth, such as a solid color, this method returns -1.

首先这是属于Drawable类中的概念。
【固有宽度】是Drawable对象将要被布局的宽度,包含了所有的固有边距属性。如果类似于纯色块并没有【固有宽度】,通常值为-1。


【☆☆☆】啰里啰嗦的看了一堆代码,可能到现在还看不清楚这个方法到底有何作用,老规矩,我们举个栗子:
大家都知道,在xml文件中定义Drawable时我们可以调用一个【android:state_focused】来设置Drawable的state(状态),比如是否获取焦点:

<item android:state_focused="false" android:state_selected="true">

在xml文档中的确可以灵活而又自如的应用上述的写法添加我们需要的属性,但是有没有发现,在java代码中我们并没有相关的方法完成上述示例,比如google并没有给我们提供一个ImageView.setFocused()。那么此时,就需要用到主角【setImageState()】了。比如我们可以这样写Java代码:

ImageView.setImageState(View.FOCUSED,true);

显而易见。有了这段代码,我们就为定义的imageview添加了一个获取焦点的属性,和上述的xml文档的效果一毛一样的效果!

类似的还有这样的一些属性:

name summary android:state_pressed 是否按下 android:state_focused 是否取得焦点,如用户是否选中了一个TextView。 android:state_hovered 光标是否悬停 android:state_selected 是否被选中 android:state_checkable 组件是否可以被点击 android:state_checked 是否被点击了(前提是相应点击事件) android:state_enabled 能够接受触摸或者点击事件 android:state_activated 是否被激活 android:state_window_focused 应用程序是否在前台可见

知道了这些,我们可以更加灵活的使用ImageView以完成我们的任务。


1.1.3Drawable状态的改变

@Override    protected void drawableStateChanged() {        super.drawableStateChanged();        final Drawable drawable = mDrawable;        if (drawable != null && drawable.isStateful()                && drawable.setState(getDrawableState())) {//mDrawable不为空】且当前状态改变后会影响mDrawable显示的内容(外观)】且拥有固有的图片状态            invalidateDrawable(drawable);        }    }

在查看了上述代码以及View.drawableStateChanged()可以知道,这个方法主要是在View状态的改变影响了Drawable的显示状态时调用。
在第一篇文章中我们曾经一起看过invalidateDrawable(drawable)方法。
也知道这个方法主要是根据传入的形参drawable的宽高信息更改mDrawableWidth与mDrawableHeight两个成员变量的值,最后再根据更改过后的宽、高重新绘制。
那么在这里可知,当View的状态改变影响了mDrawable,则重新测量、绘制新的Drawable值。

1.1.4Drawable”热点”的改变

 @Override    public void drawableHotspotChanged(float x, float y) {        super.drawableHotspotChanged(x, y);        if (mDrawable != null) {            mDrawable.setHotspot(x, y);        }    }

在看这个方法之前,首先我们需要了解什么叫做【Hotspot】。
ImageView中并没有对这个方法过多解释。那么我们继续查看View与Drawable的源代码。
在View类中:

public void drawableHotspotChanged(float x, float y) {        if (mBackground != null) {            mBackground.setHotspot(x, y);        }        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {            mForegroundInfo.mDrawable.setHotspot(x, y);        }        dispatchDrawableHotspotChanged(x, y);    }

该方法主要有以下两处调用:

  • 1
private void setPressed(boolean pressed, float x, float y) {            ...            drawableHotspotChanged(x, y);            ...            }
  • 2 View.onTouchEvent()中
        ...        case MotionEvent.ACTION_MOVE:                    drawableHotspotChanged(x, y);        ...

而在Drawable类中,与之相关的以下几个方法:

  • 1 设置波纹作用点
public void setHotspot(float x, float y) {}
  • 2 设置作用边界
public void setHotspotBounds(int left, int top, int right, int bottom) {}

到这里,还是不清楚Hotspot到底是个啥。没关系,我们再看。
View类中还有这样的一个方法:

public void dispatchDrawableHotspotChanged(float x, float y) {    }

还是不懂,没办法,只能Stack Overflow大法好了!

Added in API Level 21, this teaches the drawable a “hot spot”, and RippleDrawable apparently uses this as the emanation point for the ripple effect. setHotspot() take a pair of float values, presumably with an eye towards using setHotspot() inside of an OnTouchListener, as the MotionEvent reports X/Y positions of the touch event with float values.

看完之后,大体上可以【猜】出来了吧!
应该是和Android 21 之后添加的波纹效果有关。
这个”热点”Hotspot 实质上就是波纹效果的发起点。
【资料较少,只能这样了】
不过至少我们看过上面列举的那些方法了啊,以后见了就知道干啥了!


讲到这里,结合第一篇文章中介绍过的几个方法,我们基本上已经看完了ImageView中所有与Drawable有关的方法
不信来看,没图说个XX:
这里写图片描述

上面图片中的方法我们基本覆盖到了。可能讲的不是很深或者理解的并不很透彻,没关系,我们接下来还会再回来看看、改改,多次加深印象!

别走开,后面继续…