View绘制Drawable原理分析记录

原创 2015年07月09日 12:41:32

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.csdn.net/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的流程就结束了!

自定义view与自定义drawable在设置图像中的使用

自定义View 先使用BitmapFactory.decodeResource()来获取一个位图Bitmap,其中第一个参数为资源,第二个参数为要修改的图片 然后使用Shader对其进行操作 对于Bi...
  • molu_chase
  • molu_chase
  • 2016年07月03日 12:06
  • 1621

探究drawable图片的加载原理和缩放规律

版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+] 前言Android中的度量单位探究drawable图片的加载后语 自定义View系列教程00–...
  • qq_35114086
  • qq_35114086
  • 2016年10月15日 20:00
  • 1088

Android应用层View绘制流程与源码分析

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】1 背景还记得前面《Android应用setContentView与LayoutInflate...
  • yanbober
  • yanbober
  • 2015年05月31日 16:30
  • 105268

GPU 过度绘制总结

什么是过度绘制   然后随着UI对象不断升级,渲染流程也变得越来越复杂,列如说绘制图像,就是把图片上传到CPU存储器然后传递到GPU中进行渲染,路径使用是完全另外一码事,你需要在GPU中创建一系列的多...
  • qianxiangsen
  • qianxiangsen
  • 2016年08月12日 15:45
  • 1257

Canvas和Drawable 绘制组件

将博客搬至CSDN
  • snowgeneral
  • snowgeneral
  • 2014年10月23日 11:28
  • 1988

从源码上剖析Android View绘制Drawable的原理

一、引言对于Drawable,相比每个Android 开发者都无比熟悉,在开发过程中我们经常setBackground设置背景,那么对于Drawable你了解多少呢?对于View是怎样把Drawabl...
  • monkey646812329
  • monkey646812329
  • 2016年10月27日 17:35
  • 409

android自定义View的绘制原理

每天我们都会使用很多的应用程序,尽管他们有不同的约定,但大多数应用的设计是非常相似的。这就是为什么许多客户要求使用一些其他应用程序没有的设计,使得应用程序显得独特和不同。如果功能布局要求非常定制化,已...
  • zhangcanyan
  • zhangcanyan
  • 2016年12月25日 11:47
  • 406

View的绘制原理

1.初识ViewRoot和DecorViewViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来...
  • u014316462
  • u014316462
  • 2016年07月28日 13:14
  • 1258

Android UI详解之View绘制原理

Android View绘制原理详解     Android系统的视图结构的设计也采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类,由此就得到了视图...
  • UStory
  • UStory
  • 2015年01月06日 14:32
  • 1653

使用shape图形资源制作引导页面的提示圆点

在app引导界面通常有引导界面提示小圆点,下面简单介绍一下利用shape图形资源如何实现: 本文引导界面是使用的ViewPager,当ViewPager滑动时候可以动态的改变小圆点的颜色来提示用户 ...
  • zw382701145
  • zw382701145
  • 2016年02月03日 10:31
  • 1910
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:View绘制Drawable原理分析记录
举报原因:
原因补充:

(最多只允许输入30个字)