从源码上剖析Android View绘制Drawable的原理
来源:互联网 发布:网络中断 123.207 编辑:程序博客网 时间:2024/04/30 01:15
一、引言
对于Drawable,相比每个Android 开发者都无比熟悉,在开发过程中我们经常setBackground设置背景,那么对于Drawable你了解多少呢?对于View是怎样把Drawable绘制出来又了解多少呢?对View根据不同状态绘制不同的背景又了解多少呢?也就是我们经常使用的selector,今天我们从源码上来深度剖析这些原理,从本质上卸下Drawable的神秘面纱。
二、背景介绍
在源码路径:frameworks/base/graphics/java/android/graphics/drawable/Drawable.java
进入Drawable.java里面去看源码,你会看到这样一段描述文字:
A Drawable is a general abstraction for "something that can be drawn." Most 1. often you will deal with Drawable as the type of resource retrieved for 2. drawing things to the screen; the Drawable class provides a generic API for 3. dealing with an underlying visual resource that may take a variety of forms. 4. Unlike a {@link android.view.View}, a Drawable does not have any facility to 5. receive events or otherwise interact with the user.
也就是说Drawable是Android开发中的通用可绘制对象,View类默认针对Drawable进行一些必要的绘制,如背景。
三、Drawable的核心分析
1、先来介绍背景选择器selector
对Android开发有经验的同学,对 节点的使用一定很熟悉,该节点的作用就是定义一组状态资源图片,使其能够在不同的状态下更换某个View的背景图片。比如demo_selector.xml
<?xml version="1.0" encoding="utf-8" ?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 触摸时并且当前窗口处于交互状态 --> <item android:state_pressed="true" android:state_window_focused="true" android:drawable= "@drawable/pic1" /> <!-- 触摸时并且没有获得焦点状态 --> <item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/pic2" /> <!--选中时的图片背景--> <item android:state_selected="true" android:drawable="@drawable/pic3" /> <!--获得焦点时的图片背景--> <item android:state_focused="true" android:drawable="@drawable/pic4" /> <!-- 窗口没有处于交互时的背景图片 --> <item android:drawable="@drawable/pic5" /> </selector>
其实这个xml文件会被Android框架解析成StateListDrawable类对象。通常我们在XML文件中设置View的background如下:
android:background="@drawable/drawable_bg"/*设置其他Drawable背景*/android:background="#ff124a54"/*设置颜色背景*/
对于background我们应该明确一下几点知识: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
在用代码生成Drawable的时候,相信你会经常看看ConstantState的身影,那么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(); } /** * Create a new Drawable instance from its constant state. This must be * implemented for drawables that can have a theme applied. */ public Drawable newDrawable(Resources res, Theme theme) { return newDrawable(null); } /** * Return a bit mask of configuration changes that will impact * this drawable (and thus require completely reloading it). */ public abstract int getChangingConfigurations(); /** * @return Total pixel count * @hide */ public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { return 0; } /** @hide */ protected final boolean isAtlasable(Bitmap bitmap) { return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888; } /** * Return whether this constant state can have a theme applied. */ public boolean canApplyTheme() { return false; } } /** * Return a {@link ConstantState} instance that holds the shared state of this Drawable. * * @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对象应该会存储属性可变的数据。
参考StateListDrawable的源码,确实是有DrawableContainer继承ConstantState的身影。
static class StateListState extends DrawableContainerState { int[] mThemeAttrs; int[][] mStateSets; StateListState(StateListState orig, StateListDrawable owner, Resources res) { super(orig, owner, res); if (orig != null) { // Perform a shallow copy and rely on mutate() to deep-copy. mThemeAttrs = orig.mThemeAttrs; mStateSets = orig.mStateSets; } else { mThemeAttrs = null; mStateSets = new int[getCapacity()][]; } } void mutate() { mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null; final int[][] stateSets = new int[mStateSets.length][]; for (int i = mStateSets.length - 1; i >= 0; i--) { stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null; } mStateSets = stateSets; } int addStateSet(int[] stateSet, Drawable drawable) { final int pos = addChild(drawable); mStateSets[pos] = stateSet; return pos; } int indexOfStateSet(int[] stateSet) { final int[][] stateSets = mStateSets; final int N = getChildCount(); for (int i = 0; i < N; i++) { if (StateSet.stateSetMatches(stateSets[i], stateSet)) { return i; } } return -1; } @Override public Drawable newDrawable() { return new StateListDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { return new StateListDrawable(this, res); } @Override public boolean canApplyTheme() { return mThemeAttrs != null || super.canApplyTheme(); } @Override public void growArray(int oldSize, int newSize) { super.growArray(oldSize, newSize); final int[][] newStateSets = new int[newSize][]; System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize); mStateSets = newStateSets; } }
那么Drawable跟ConstantState之间有什么关系呢?
大家可以看看国外这篇文章http://www.curious-creature.com/2009/05/02/drawable-mutations/
其实两者之间的关系如下图所示:
由同一个Resource实例化生成的多个Drawable公用一个ConstanteState,这是出于对内存节省策略的考虑。那么当我们需要修改多个Drawable中的其中一个的属性时,就会出现修改后的Drawable影响到了其他Drawable的情况,针对这种情况,我们的想法自然是复制一份ConstantState从而避免相互间的干扰。
从根源上复制Drawable的方法可以使用:/*假设dr为原有Drawable*/Drawable newDr = dr.getConstantState().newDrawable();/*或者*/Drawable newDr = dr.getConstantState().newDrawable().mutate();
例如,我在Switch.java中有这样子的定义:
if (mTrackDrawable != null) { mTrackDrawable.setCallback(this); if (mTrackDrawable.getConstantState() != null) { mTrackOnDrawable = mTrackDrawable.getConstantState().newDrawable().mutate(); mTrackOnDrawable.setState(new int[]{android.R.attr.state_checked}); } if (mTrackDrawable.getConstantState() != null) { mTrackOffDrawable = mTrackDrawable.getConstantState().newDrawable().mutate(); mTrackOffDrawable.setState(new int[]{-android.R.attr.state_checked}); } }
3、StateListDrawable类简介
类功能说明:该类定义了不同状态值下与之对应的图片资源,即我们可以利用该类保存多种状态值,多种图片资源。
常用方法为:
public void addState (int[] stateSet, Drawable drawable) 功能: 给特定的状态集合设置drawable图片资源
还记得我在前面介绍Selector的时候有说过XML最终会转换成StateListDrawable类,所以我们还是以demo_selector.xml来分析。
//初始化一个空对象StateListDrawable stalistDrawable = new StateListDrawable();//获取对应的属性值 Android框架自带的属性 attrint pressed = android.R.attr.state_pressed;int window_focused = android.R.attr.state_window_focused;int focused = android.R.attr.state_focused;int selected = android.R.attr.state_selected;stalistDrawable.addState(new int []{pressed , window_focused}, getResources().getDrawable(R.drawable.pic1));stalistDrawable.addState(new int []{pressed , -focused}, getResources().getDrawable(R.drawable.pic2);stalistDrawable.addState(new int []{selected }, getResources().getDrawable(R.drawable.pic3);stalistDrawable.addState(new int []{focused }, getResources().getDrawable(R.drawable.pic4);//没有任何状态时显示的图片,我们给它设置我空集合stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);
上面的“-”负号表示对应的属性值为false
当我们为某个View使用其作为背景色时,会根据状态进行背景图的转换。
public boolean isStateful () 功能: 表明该状态改变了,对应的drawable图片是否会改变。 注:在StateListDrawable类中,该方法返回为true,显然状态改变后,我们的图片会跟着改变
说到状态那么不得不说在Android中View的各种状态, 一般来说,Android框架为View定义了四种不同的状态,这些状态值的改变会引发View相关操作,例如:更换背景图片、是否触发点击事件等;我们来看下图:(注明:图片表格是借鉴网上资源)
注意:selected不同于focus状态,通常在AdapterView类群下例如ListView或者GridView会使某个View处于
selected状态,并且获得该状态的View处于高亮状态。而一个窗口只能有一个视图获得焦点(focus),而一个窗口可以有多个视图处于”selected”状态中。
总结:focused状态一般是由按键操作引起的;
pressed状态是由触摸消息引起的;
selected则完全是由应用程序主动调用setSelected()进行控制。
4、View如何根据状态值的改变去绘制/显示对应的背景图?
当View任何状态值发生改变时,都会调用refreshDrawableList()方法去更新对应的背景Drawable对象。
源码路径:frameworks\base\core\java\android\view\View.java
/** * 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; //改变drawable状态 drawableStateChanged(); ViewParent parent = mParent; if (parent != null) { //如果该View之上仍有父View,则一直递归通知 parent.childDrawableStateChanged(this); } }
该方法主要功能是根据当前的状态值去更换对应的背景Drawable对象。重点看看drawableStateChanged()
/** * 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> * If the View has a StateListAnimator, it will also be called to run necessary state * change animations. * <p> * Be sure to call through to the superclass when overriding this function. * * @see Drawable#setState(int[]) */ @CallSuper protected void drawableStateChanged() { //获取当前Drawable状态(pressed),刷新mBackground的状态 final int[] state = getDrawableState(); final Drawable bg = mBackground; if (bg != null && bg.isStateful()) { bg.setState(state); } final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (fg != null && fg.isStateful()) { fg.setState(state); } if (mScrollCache != null) { final Drawable scrollBar = mScrollCache.scrollBar; if (scrollBar != null && scrollBar.isStateful()) { scrollBar.setState(state); } } if (mStateListAnimator != null) { mStateListAnimator.setState(state); } }
该方法获得当前的状态属性— 整型集合 ; 调用Drawable类的setState方法去获取资源,最后进去看看setState()方法的具体实现:
public boolean setState(final int[] stateSet) { if (!Arrays.equals(mStateSet, stateSet)) { mStateSet = stateSet; return onStateChange(stateSet); } return false; }
判断状态值是否发生了变化,如果发生了变化,就调用onStateChange()方法进一步处理。
@Override protected boolean onStateChange(int[] stateSet) { final boolean changed = super.onStateChange(stateSet); int idx = mStateListState.indexOfStateSet(stateSet); if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states " + Arrays.toString(stateSet) + " found " + idx); if (idx < 0) { idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); } return selectDrawable(idx) || changed; }
根据新的状态值,从StateListDrawable实例对象中,找到第一个完全吻合该新状态值的索引下标处
继而,调用selectDrawable()方法去获取索引下标的当前Drawable对象 具体查找算法在 mStateListState.indexOfStateSet(stateSet) 里实现了。基本思路是:查找第一个能完全吻合该新状态的索引下标,如果找到了,则立即返回。
private int indexOfStateSet(int[] stateSet) { int[][] stateSets = this.mStateSets; int N = this.getChildCount(); for(int i = 0; i < N; ++i) { if(StateSet.stateSetMatches(stateSets[i], stateSet)) { return i; } } return -1; }
再来看看selectDrawable的具体实现
public boolean selectDrawable(int idx) { if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { //获取对应索引位置的Drawable对象 Drawable d = mDrawableContainerState.mDrawables[idx]; ... mCurrDrawable = d; //mCurrDrawable即使当前Drawable对象 mCurIndex = idx; ... } else { ... } //请求该View刷新自己,这个方法我们稍后讲解。 invalidateSelf(); return true; }
该函数的主要功能是选择当前索引下标处的Drawable对象,并保存在mCurrDrawable中。
5、View如何绘制Drawable
经过上面一系列的Drawable状态切换和匹配Drawale,最终我们有了需要重新绘制的pressed状态下的Drawable,此时请求绘制自身并最终会调用View的draw()方法。
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…的方法去绘制。
6、关于Drawable.Callback接口
/** * Implement this interface if you want to create an animated drawable that * extends {@link android.graphics.drawable.Drawable Drawable}. * Upon retrieving a drawable, use * {@link Drawable#setCallback(android.graphics.drawable.Drawable.Callback)} * to supply your implementation of the interface to the drawable; it uses * this interface to schedule and execute animation changes. */ public static interface Callback { /** * Called when the drawable needs to be redrawn. A view at this point * should invalidate itself (or at least the part of itself where the * drawable appears). * * @param who The drawable that is requesting the update. */ public void invalidateDrawable(Drawable who); /** * A Drawable can call this to schedule the next frame of its * animation. An implementation can generally simply call * {@link android.os.Handler#postAtTime(Runnable, Object, long)} with * the parameters <var>(what, who, when)</var> to perform the * scheduling. * * @param who The drawable being scheduled. * @param what The action to execute. * @param when The time (in milliseconds) to run. The timebase is * {@link android.os.SystemClock#uptimeMillis} */ public void scheduleDrawable(Drawable who, Runnable what, long when); /** * A Drawable can call this to unschedule an action previously * scheduled with {@link #scheduleDrawable}. An implementation can * generally simply call * {@link android.os.Handler#removeCallbacks(Runnable, Object)} with * the parameters <var>(what, who)</var> to unschedule the drawable. * * @param who The drawable being unscheduled. * @param what The action being unscheduled. */ public void unscheduleDrawable(Drawable who, Runnable what); }
对于CallBack接口,主要看看invalidateDrawable(Drawable who)方法,这个方法的功能是如果Drawable对象的状态发生了变化,会请求View重新绘制,因此我们对应于该View的背景Drawable对象能够重新”绘制“出来。
7、自定义View绘制背景Drawable
public class MyView extends View { private Context mContext = null; private Drawable mBackground = null; private boolean mSizeChanged = true; //视图View布局(layout)大小是否发生变化 public MyView(Context context) { super(context); mContext = context; initStateListDrawable(); // 初始化图片资源 } // 初始化图片资源 private void initStateListDrawable() { //有两种方式获取我们的StateListDrawable对象: // 1、代码构建一个StateListDrawable对象 StateListDrawable statelistDrawable = new StateListDrawable(); int pressed = android.R.attr.state_pressed; int windowfocused = android.R.attr.state_window_focused; int enabled = android.R.attr.state_enabled; int stateFoucesd = android.R.attr.state_focused; //匹配状态时,是一种优先包含的关系。 // "-"号表示该状态值为false .即不匹配 statelistDrawable.addState(new int[] { pressed, windowfocused }, mContext.getResources().getDrawable(R.drawable.btn_power_on_pressed)); statelistDrawable.addState(new int[]{ -pressed, windowfocused }, mContext.getResources().getDrawable(R.drawable.btn_power_on_nor)); mBackground = statelistDrawable; //必须设置回调,当改变状态时,会回掉该View进行invalidate()刷新操作. mBackground.setCallback(this); //取消默认的背景图片,因为我们设置了自己的背景图片了,否则可能造成背景图片重叠。 this.setBackgroundDrawable(null); // 获取方式二、、使用XML获取StateListDrawable对象 // mBackground = mContext.getResources().getDrawable(R.drawable.tv_background); } protected void drawableStateChanged() { Drawable d = mBackground; if (d != null && d.isStateful()) { d.setState(getDrawableState()); } super.drawableStateChanged(); } //验证图片是否相等 , 在invalidateDrawable()会调用此方法,我们需要重写该方法。 protected boolean verifyDrawable(Drawable who) { return who == mBackground || super.verifyDrawable(who); } //draw()过程,绘制背景图片... public void draw(Canvas canvas) { if (mBackground != null) { if(mSizeChanged) { //设置边界范围 mBackground.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()); mSizeChanged = false ; } if ((getScrollX() | getScrollY()) == 0) //是否偏移 { mBackground.draw(canvas); //绘制当前状态对应的图片 } else { canvas.translate(getScrollX(), getScrollY()); mBackground.draw(canvas); //绘制当前状态对应的图片 canvas.translate(-getScrollX(), -getScrollY()); } } super.draw(canvas); } public void onDraw(Canvas canvas) { ... }}
- 从源码上剖析Android View绘制Drawable的原理
- 从Android源码分析View绘制
- android View绘制源码分析(上)
- View绘制Drawable原理分析记录
- 从源码上深度剖析View invalidate 、 postInvalidate、requestLayout
- android View 绘制原理
- android view绘制原理
- 【Android控件原理】控件View的绘制
- Android View绘制原理的初步理解
- android自定义View的绘制原理
- Android源码之View的绘制
- Android从源码解析三:View绘制流程
- Android View的绘制之 从源码了解measure的过程。
- 从源码角度分析Android View的绘制机制(一)
- Android View绘制流程(结合源码分析)上
- View的绘制原理
- View的绘制原理
- Android View学习笔记(三):Scroller的原理剖析及使用(上)
- linux dmi
- java.util.ConcurrentModificationException
- Java异常处理中throw与throws的用法区别
- 【Python扩展阅读EasyGui 学习文档【超详细中文版】】
- 课件幻灯片ppt转换成word的好方法
- 从源码上剖析Android View绘制Drawable的原理
- 针对Eclipse卡死,在progress 窗口有很多等待执行的任务
- Android Gson深入分析
- Windows安装Python解释器并且运行代码
- C#中?的使用
- linux进程间通讯-消息队列
- [LeetCode]Unique Paths
- git碰到的一些小问题
- Android中BroadcastReceiver的基本使用