View绘制Drawable原理分析记录
Drawable是Android开发中的通用可绘制对象,View类默认针对Drawable进行一些必要的绘制,如背景,此文旨在记录分析View绘制Drawable时的关键细节
1.XML布局文件的Background
通常,我们在XML文件中设置View的background如下:
android:background="@drawable/drawable_bg"/*设置其他Drawable背景*/
android:background="#ff124a54"/*设置颜色背景*/
在展开阐述之前,我们先明确一下,android:background属性值在实际操作中都会实例化为Drawable,参考官方文档,常用的属性值与Drawable对象关系如下:
- “#ffffffff”=>ColorDrawable
- “@drawable/shape_bg”=>GradientDrawable(假设shape_bg.xml文件为顶层为shape标签)
- “@drawable/bmp_bg”=>BitmapDrawable(假设bmp_bg为.png/.jpg/.bmp文件)
- “@drawable/9patch_bg”=>NinePatchDrawable(假设9patch_bg为.9.png文件)
2.Drawable的ConstantState
2.1什么是ConstantState
如果把Drawable比作一个绘制容器,那么ConstantState就是容器中真正的内容,Drawable的源码如下:
/**
* This abstract class is used by {@link Drawable}s to store shared constant state and data
* between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
* share a unique bitmap stored in their ConstantState.
*
* <p>
* {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
* from this ConstantState.
* </p>
*
* Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
* {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
* Drawable.
*/
public static abstract class ConstantState {
/**
* Create a new drawable without supplying resources the caller
* is running in. Note that using this means the density-dependent
* drawables (like bitmaps) will not be able to update their target
* density correctly. One should use {@link #newDrawable(Resources)}
* instead to provide a resource.
*/
public abstract Drawable newDrawable();
/**
* Create a new Drawable instance from its constant state. This
* must be implemented for drawables that change based on the target
* density of their caller (that is depending on whether it is
* in compatibility mode).
*/
public Drawable newDrawable(Resources res) {
return newDrawable();
}
/**
* Return a bit mask of configuration changes that will impact
* this drawable (and thus require completely reloading it).
*/
public abstract int getChangingConfigurations();
/**
* @hide
*/
public Bitmap getBitmap() {
return null;
}
}
/**
* Return a {@link ConstantState} instance that holds the shared state of this Drawable.
*q
* @return The ConstantState associated to that Drawable.
* @see ConstantState
* @see Drawable#mutate()
*/
public ConstantState getConstantState() {
return null;
}
可以看出,ConstantState其实是Drawable中的静态抽象类,并且getConstantState()方法默认返回null,这我们可以推测ConstantState类和getConstantState()方法会被具体的Drawable实现类继承和重写。这样不难想象,由于不同Drawbale如BitmapDrawable、GradientDrawable这些自身存储的绘制内容数据原则上是不一样的,这就意味着Drawable为了易于扩展,ConstantState对象应该会存储属性可变的数据。
参考BitmapDrawable源码,BitmapDrawable中确实有BitmapState继承于ConstantState:
final static class BitmapState extends ConstantState {
Bitmap mBitmap;/*可以看出,BitmapState里真正存放了Bitmap对象,而不是在其他地方*/
int mChangingConfigurations;
int mGravity = Gravity.FILL;
Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
Shader.TileMode mTileModeX = null;
Shader.TileMode mTileModeY = null;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
boolean mRebuildShader;
boolean mAutoMirrored;
BitmapState(Bitmap bitmap) {
mBitmap = bitmap;
}
BitmapState(BitmapState bitmapState) {
this(bitmapState.mBitmap);
mChangingConfigurations = bitmapState.mChangingConfigurations;
mGravity = bitmapState.mGravity;
mTileModeX = bitmapState.mTileModeX;
mTileModeY = bitmapState.mTileModeY;
mTargetDensity = bitmapState.mTargetDensity;
mPaint = new Paint(bitmapState.mPaint);
mRebuildShader = bitmapState.mRebuildShader;
mAutoMirrored = bitmapState.mAutoMirrored;
}
@Override
public Bitmap getBitmap() {
return mBitmap;
}
@Override
public Drawable newDrawable() {
return new BitmapDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new BitmapDrawable(this, res);
}
@Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
}
2.2Drawable与ConstantState的关系
引用
http://www.curious-creature.com/2009/05/02/drawable-mutations/(国外)
http://blog.youkuaiyun.com/zhaoyw2008/article/details/45562835(国内)
由同一个Resource实例化生成的多个Drawable公用一个ConstanteState,这是出于对内存节省策略的考虑。那么当我们需要修改多个Drawable中的其中一个的属性时,就会出现修改后的Drawable影响到了其他Drawable的情况,针对这种情况,我们的想法自然是复制一份ConstantState从而避免相互间的干扰。
从根源上复制Drawable的方法可以使用:
/*假设dr为原有Drawable*/
Drawable newDr = dr.getConstantState().newDrawable();
/*或者*/
Drawable newDr = dr.getConstantState().newDrawable().mutate();
3.View绘制Drawable(以绘制StateListDrawable为例)
1. 用户点击View触发onTouch()
2. 触发setPress(true)方法,同时执行以下代码:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
...
/**
* Call this to force a view to update its drawable state. This will cause
* drawableStateChanged to be called on this view. Views that are interested
* in the new state should call getDrawableState.
*
* @see #drawableStateChanged
* @see #getDrawableState
*/
public void refreshDrawableState() {
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
drawableStateChanged();/*改变drawable状态*/
ViewParent parent = mParent;
if (parent != null) {
parent.childDrawableStateChanged(this);/*如果该View之上仍有父View,则一直递归通知*/
}
}
/**
* This function is called whenever the state of the view changes in such
* a way that it impacts the state of drawables being shown.
*
* <p>Be sure to call through to the superclass when overriding this
* function.
*
* @see Drawable#setState(int[])
*/
protected void drawableStateChanged() {
Drawable d = mBackground;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());/*获取当前Drawable状态(pressed),刷新mBackground的状态*/
}
}
...
}
可以知道Drawable最终调用setState()方法更改了当前状态
3. Drawable更改状态并触发View绘制自身
public class Drawable{
...
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);/*状态发生变化,传入新的stateSet进行通知*/
}
return false;
}
...
}
public class StateListDrawable extends DrawableContainer {
...
@Override
protected boolean onStateChange(int[] stateSet) {
int idx = mStateListState.indexOfStateSet(stateSet);
if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
+ Arrays.toString(stateSet) + " found " + idx);
if (idx < 0) {
/*根据传入的状态,找到StateListDrawable中匹配的第一个状态索引*/
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}
if (selectDrawable(idx)) {/*选择匹配状态的Drawable*/
return true;
}
return super.onStateChange(stateSet);
}
public boolean selectDrawable(int idx) {
if (idx == mCurIndex) {
return false;
}
...
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(idx);
mCurrDrawable = d;
mCurIndex = idx;
...
} else {
mCurrDrawable = null;
mInsets = Insets.NONE;
mCurIndex = -1;
}
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
animate(true);
invalidateSelf();// 请求绘制自己
}
};
} else {
unscheduleSelf(mAnimationRunnable);
}
// Compute first frame and schedule next animation.
animate(true);
}
invalidateSelf();// 请求绘制自己
return true;
}
...
}
4.View开始绘制draw()
经过上面一系列的Drawable状态切换和匹配Drawale,最终我们有了需要重新绘制的pressed状态下的Drawable,此时请求绘制自身并最终会调用View的draw()方法
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
public void draw(Canvas canvas) {
...
// draw方法会先绘制背景
// 不透明才绘制背景
if (!dirtyOpaque) {
final Drawable background = mBackground;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);//开始绘制的动作
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);//开始绘制的动作
canvas.translate(-scrollX, -scrollY);
}
}
}
由于Drawable的draw()方法由其实现类完成,绘制动作各有不同,但最终还是调用canvas.draw…的方法去绘制,这里就不展开讨论了。
至此,View绘制Drawable的流程就结束了!
4375



被折叠的 条评论
为什么被折叠?



