关闭

View绘制Drawable原理分析记录

标签: android
286人阅读 评论(0) 收藏 举报
分类:

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

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:44325次
    • 积分:702
    • 等级:
    • 排名:千里之外
    • 原创:19篇
    • 转载:37篇
    • 译文:0篇
    • 评论:0条