有多种因素会导致触发refreshDrawableState,当前只考虑其中一种setPressed即设置视图是否处于被按下状态。其他会触发此方法的有focusChanged等。
原理是定义不同状态的图片,系统进行状态监听例如在onTouchEvent中判断当前在什么状态,再根据之前提供的图片进行设置并重绘显示效果。
以下是根据代码一个具体的流程,其中解释的并不是太多,当前也都比较简单都是直接的方法调用顺序。
先来看看View.setPressed源码
public void setPressed(boolean pressed) { if (pressed) { mPrivateFlags |= PRESSED ; } else { mPrivateFlags &= ~PRESSED; } refreshDrawableState(); dispatchSetPressed(pressed); }
其中调用了View.refreshDrawableState,下面查看一下相关代码View.refreshDrawableState与 View.drawableStateChanged:
一
public void refreshDrawableState() { // 用于判断是否发生状态变化 mPrivateFlags |= DRAWABLE_STATE_DIRTY ; // 状态改变后进行绘制 drawableStateChanged(); ViewParent parent = mParent; if (parent != null) { // 父视图如果存在的话执行 parent.childDrawableStateChanged( this); } } // 仅在ViewGroup覆写 protected void drawableStateChanged() { // 当前视图的背景图 Drawable d = mBGDrawable; if (d != null && d.isStateful()) { // 为当前drawable设置当前drawable状态索引 d.setState(getDrawableState()); } } // 获取视图的当前状态 public final int[] getDrawableState() { if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) { return mDrawableState; } else { // 将视图状态转换为一个int[]数组 // DrawableState可以识别此数组 mDrawableState = onCreateDrawableState(0); mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY; return mDrawableState; } }
二 以上在View中调用Drawable相应方法Drawable.setState 和 Drawable.onStateChange
public boolean setState(final int[] stateSet) { if (!Arrays.equals( mStateSet, stateSet)) { mStateSet = stateSet; return onStateChange(stateSet); } return false ; } protected boolean onStateChange (int[] state) { return false; }
三 步骤二中的调用的方法是一个回调,其具体实现在其子类中,此处仅罗列针对其子类StateListDrawable的具体实现代码StateListDrawable.onStateChange和 StateListDrawable.selectDrawable
@Override protected boolean onStateChange( int[] stateSet) { int idx = mStateListState.indexOfStateSet(stateSet); if (idx < 0) { idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); } if (selectDrawable(idx)) { return true ; } return super .onStateChange(stateSet); } public boolean selectDrawable( int idx) { ...... // 触发绘制请求 invalidateSelf(); return true ; }
四 StateListDrawable以上方法流程中调用的方法invalidateSelf,由其父类中进行具体实现Drawable.Callback.invalidateSelfe和Drawable.Callback.invalidateDrawable
public void invalidateSelf() { // mCallback是在何处赋赋值? if (mCallback != null) { mCallback.invalidateDrawable(this); } } // 全局搜索发现此处对其值进行赋值,那何处引用了此方法呢? public final void setCallback(Callback cb) { mCallback = new WeakReference<Callback>(cb); } public void invalidateDrawable (Drawable who);
五 以上步骤需要调用其setCallback传入cb才行,具体什么地方传入的呢?具体是在View.setBackgroundDrawable中
@Deprecated public void setBackgroundDrawable(Drawable background) { ... // 此处设置Callback,当前View就是其Callback background.setCallback(this); ... mBGDrawable = background; }
六 从步骤五可以获知drawable中的Callback就是当前View自身,View实现Drawable.Callback
public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,AccessibilityEventSource { public void invalidateDrawable(Drawable drawable) { if (verifyDrawable(drawable)) { final Rect dirty = drawable.getBounds(); final int scrollX = mScrollX; final int scrollY = mScrollY; // 看到了熟悉的invalidate,之后就是走正常的invalidate流程 invalidate(dirty. left + scrollX, dirty.top + scrollY, dirty. right + scrollX, dirty.bottom + scrollY); } } }
第六步中进行具体的请求刷新
参考资料:
《Android 内核剖析》 柯元丹 著 13.6.3 refreshDrawableList
Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解
代码实现ColorStateList及StateListDrawable