Android 应用层开发 Drawable 的一些叨叨絮

Posted 工匠若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 应用层开发 Drawable 的一些叨叨絮相关的知识,希望对你有一定的参考价值。

1 背景

博客也该开张了,懒惰都是惯的。既然这样,那就拿一个简单问题来叨叨絮吧;故事的起因是这样的,群里有一哥们仿写别人自定义控件,没整明白 Drawable 咋回事,然后群里炸开了锅,为了维护群里的世界和平,随决定叨叨絮一下 android Drawable。关于 Android Drawable 可绘制对象资源的介绍大家可以去看看官方文档即可;而关于 Drawable 的使用细节其实你在 Drawable.java 的开篇大段注释中可以找寻到秘笈,所以说,如果你想玩转 Android 自定义控件 Drawable 系列,请务必先去拜读搞明白上面提到的两个秘笈,否则最直接的后果就是天下大乱。
(我不喜欢蹭刷访问量和博文篇数,所以没必要把这个知识点分几篇博客来写,所以看时别闲长,这已经是我的习惯了。)

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

2 Drawable 源码浅析

关于 Android Drawable 抽象类有多少个实现子类或者这些实现子类怎么使用及原理就不多 BB 了,为了世界和平,我需要重点强调的是给你一个抽象 Drawable,还我一个自定义 Drawable 的技能;不过这里有一点兼容性问题要注意:所有 Android 版本都是兼容通过 Java 实例化继承 Drawable 自定义的,但是只有 API 24 开始才允许在 XML 中使用自定义的 Customer Drawable

理解 Drawable 的精髓其实就是理解下面这一段话而已:

A Drawable is a general abstraction for "something that can be drawn." 
Most often you will deal with Drawable as the type of resource retrieved for drawing things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms. 
Unlike a @link android.view.View, a Drawable does not have any facility to receive events or otherwise interact with the user.

上面这段话简单粗暴理解就是说,Drawable 是一个抽象类,提供了一些 API 方法去处理各种资源的绘制,但是又不具备 View 的事件与交互处理能力。额,再简单粗暴一点认为就是一个辅助绘制工具类,把各种东西都封装搞好以后直接给Canvas去画。既然是工具类,说白了就是个模板,你就把它类比 View 或者 Paint 来看吧,不过这玩意要切记兼容性问题,下面列到的源码解释中很多方法存在兼容性问题,请自己解决或者使用 DrawableCompat 即可。
(下面源码是基于 android-24 解释的,略多,不过我们一般自定义 Drawable 时只会用其中几个方法,看不下去的可以直接跳到下一个小节即可,这一段源码解释完全可以当作日后自定义的参考手册即可。)

public abstract class Drawable 
    ......
    /**
     * 画在setBounds设置的区域内,子类必须实现,canvas就是要被绘制的地方,譬如View的onDraw的canvas。
     * 要绘制状态效果的话可由setAlpha和setColorFilter等方法控制。
     * 具体被调用样例可以去ImageView draw中瞅瞅,或者去看看之前文章分析的View在draw中绘制Background Drawable时调用。
     */
    public abstract void draw(@NonNull Canvas canvas);

    /**
     * 指定绘制矩形边界区域,在draw方法调用时用到其设置的值。
     * 当该方法调用后新旧bounds发生变化时会触发onBoundsChange方法,不设置默认边界均为0,故很多人自定义Drawable发现不显示的可能因素之一在此。
     */
    public void setBounds(int left, int top, int right, int bottom) ...
    public void setBounds(@NonNull Rect bounds) ...
    protected void onBoundsChange(Rect bounds) ...

    /**
     * 给调用者通过bounds参数或者返回值复制返回一个setBounds设置的边界,外界修改不影响已经设置的Bounds,值拷贝。
     */
    public final void copyBounds(@NonNull Rect bounds) ...
    public final Rect copyBounds() ...

    /**
     * 给调用者通过返回一个setBounds设置的边界,切记外界修改会影响已经设置的Bounds,尽量不要改。
     */
    public final Rect getBounds() ...
    public Rect getDirtyBounds() ...

    /**
     * 在系统Configurate change时调用该方法来处理Drawable状态,这一组方法不常用。
     * 系统config改变的参数类型参见:android.content.pm.ActivityInfo
     */
    public void setChangingConfigurations(@Config int configs) ...
    public @Config int getChangingConfigurations() ...

    /**
     * 当使用bitmap位图时可进行滤波处理,类似我们自定义时使用Paint的setAntiAlias()和setBitmapFilter(true)一样,
     * 一个用来防边缘锯齿,一个用来对位图滤波处理;当自定义的Drawable不是bitmap位图时该设置失效。
     */
    public void setFilterBitmap(boolean filter) 
    public boolean isFilterBitmap() ...

    /**
     * 回调接口类,若要实现自定义动画drawable,可以通过setCallBack(Callback)实现对动画的调度和执行。
     */
    public interface Callback 
        /**
         * 当drawable重画时触发,who为要重画的drawable。
         */
        void invalidateDrawable(@NonNull Drawable who);

        /**
         * 预先安排动画的下一帧,也可通过Handler.postAtTime(Runnable, Object, long)实现。
         */
        void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);

        /**
         * 删除预先安排的动画某帧,也可通过Handler.removeCallbacks(Runnable, Object)实现。
         */
        void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
    

    /**
     * 给需要动画的Drawable设置回调。(已经修改为弱引用了,防止以前低版本Drawable的CallBack内存泄漏,锅锅锅~)
     */
    public final void setCallback(@Nullable Callback cb) ...
    public Callback getCallback() ...

    /**
     * 当通过setCallback设置回调后调用该方法会触发回调Callback.invalidateDrawable(@NonNull Drawable who)实现。
     * 当没有设置CallBack时该方法无任何效果。
     */
    public void invalidateSelf() ...

    /**
     * 当通过setCallback设置回调后调用该方法会触发回调Callback.scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when)实现。
     * 当没有设置CallBack时该方法无任何效果。
     */
    public void scheduleSelf(@NonNull Runnable what, long when) ...

    /**
     * 当通过setCallback设置回调后调用该方法会触发回调Callback.unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what)实现。
     * 当没有设置CallBack时该方法无任何效果。
     */
    public void unscheduleSelf(@NonNull Runnable what) ...

    /**
     * 获取与设置RTL或者LTR布局方向,譬如让Drawable支持阿拉伯语布局,可以参见View的LAYOUT_DIRECTION_LTR和LAYOUT_DIRECTION_RTL。
     * 当RTL/LTR布局动态变化时触发onLayoutDirectionChanged。
     * 好处就是如果我们的自定义Drawable要支持阿拉伯等布局方向切换,我们不用定义两个Drawable了,通过这一组方法实现即可。
     */
    public @View.ResolvedLayoutDir int getLayoutDirection() ...
    public final boolean setLayoutDirection(@View.ResolvedLayoutDir int layoutDirection) ...
    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) ...

    /**
     * 给当前Drawable设置和获取alpha透明度,setAlpha为抽象方法,子类必须实现,默认一般单态Drawable时重写直接返回255即可。
     * 有时候我们自定义Drawable时会在内部定义一个Paint,所以setAlpha实现也可以直接为mPaint.setAlpha(alpha);
     */
    public abstract void setAlpha(@IntRange(from=0,to=255) int alpha);
    public int getAlpha() return 0xFF;

    /**
     * @hide 被匿了,给将来准备的货,这就尴尬了,类比Paint的Xfermode吧。
     */
    public void setXfermode(@Nullable Xfermode mode) ...

    /**
     * 设置滤镜效果,类似Paint的setColorFilter()方法,譬如我们常干的一件事:
     * textview.getBackground().setColorFilter(new LightingColorFilter(0xAAFFCCEE, 0xFF00BBAA));
     * 不懂的去前面翻自定义博客文章吧,取消滤镜效果直接传null即可。
     * 有时候我们自定义Drawable时会在内部定义一个Paint,所以setAlpha实现也可以直接为mPaint.setColorFilter(colorFilter);
     */
    public abstract void setColorFilter(@Nullable ColorFilter colorFilter);

    /**
     * 设置滤镜效果,同上setColorFilter(@Nullable ColorFilter colorFilter);
     * 其实就是setColorFilter(new PorterDuffColorFilter(color, mode));的封装。
     */
    public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) ...
    public @Nullable ColorFilter getColorFilter() ...
    public void clearColorFilter() ...

    /**
     * Drawable的Tint变色处理,setTint为setTintList的封装而已,注意API兼容问题,一般可用DrawableCompat的。
     * 实际用处譬如我们自定义的selector drawable,getResources().getColorStateList(R.color.selector_XXX));
     * 注意:如果设置了setColorFilter,则setTint和setTintList将无效。
     */
    public void setTint(@ColorInt int tintColor) ...
    public void setTintList(@Nullable ColorStateList tint) ...

    /**
     * 设置上面Tint变色时的PorterDuff.Mode模式,默认为PorterDuff.Mode.SRC_IN。
     * 注意:如果设置了setColorFilter,则setTintMode将无效。
     */
    public void setTintMode(@NonNull PorterDuff.Mode tintMode) 

    /**
     * 设置热点坐标,5.0加入,坑爹。
     * RippleDrawable就是一个以波纹效果来显示状态变化的Drawable,其中波纹的位置就是这玩意设置的。
     */
    public void setHotspot(float x, float y) 
    public void setHotspotBounds(int left, int top, int right, int bottom) 
    public void getHotspotBounds(@NonNull Rect outRect) ...

    /**
     * 设置和获取状态,有变化时会触发onStateChange。譬如:
     * @link android.R.attr#state_focused
     * @link android.R.attr#state_pressed
     * 在View状态改变的时候,会调用Drawable的setState函数。
     */
    public boolean setState(@NonNull final int[] stateSet) ...
    protected boolean onStateChange(int[] state) ...
    public @NonNull int[] getState() ...

    /**
     * 上面setState只能定义有限的几种状态,如果需要更多的状态,就可以使用图像级别资源。
     * 图像级别资源文件中可以定义任意多个图像级别,可以通过该方法来切换不同状态的图像。
     */
    public final boolean setLevel(@IntRange(from=0,to=10000) int level) ...
    public final @IntRange(from=0,to=10000) int getLevel() ...
    protected boolean onLevelChange(int level) ...

    /**
     * 设置或者获取Drawable是否可见。
     */
    public boolean setVisible(boolean visible, boolean restart) ...
    public final boolean isVisible() 

    /**
     * 返回当前Drawable透明或者半透明或者不透明等,默认不清楚时直接返回TRANSLUCENT是最好的选择。
     * @link android.graphics.PixelFormat:
     * @link android.graphics.PixelFormat#UNKNOWN,
     * @link android.graphics.PixelFormat#TRANSLUCENT,
     * @link android.graphics.PixelFormat#TRANSPARENT, or
     * @link android.graphics.PixelFormat#OPAQUE.
     */
    public abstract @PixelFormat.Opacity int getOpacity();

    /**
     * 返回Drawable的真实宽高,包含padding等,如果没有宽高(譬如纯Color)则返回-1即可。
     */
    public int getIntrinsicWidth() ...
    public int getIntrinsicHeight() ...

    /**
     * 返回建议的最小宽高,一般这个宽高决定了自定义View在有些情况下的宽高。
     */
    public int getMinimumWidth() ...
    public int getMinimumHeight() ...

    /**
     * 返回Drawable的padding,没有padding时return false,反之。
     */
    public boolean getPadding(@NonNull Rect padding) ...

    /**
     * 如果有多个控件同时使用某一资源且要改变该资源的状态,我们就需要用mutate方法。
     * 使用mutate方法是为了更改一个资源状态时其它引用该资源的控件不被改变,因为默认情况下,所有从同一资源(R.drawable.XXX)
     * 加载的Drawable实例都共享一个共用的状态ConstantState,如果我们更改了一个实例的状态,其余的实例都会接收到相同的更改变化。
     * @see ConstantState
     * @see #getConstantState()
     */
    public @NonNull Drawable mutate() return this;

    /**
     * 通过inputstream、xml、FilePath创建一个Drawable。
     */
    public static Drawable createFromStream(InputStream is, String srcName) ...
    public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName) ...
    public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) ...
    public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException ...
    public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme) throws XmlPullParserException, IOException ...
    public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException ...
    public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException ...
    public static Drawable createFromPath(String pathName) ...

    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs) throws XmlPullParserException, IOException ...
    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException ...
    void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r, @NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs, @AttrRes int visibleAttr) throws XmlPullParserException, IOException ...

    /**
     * 每一个Drawable对象都关联一个ConstantState对象,目的是为了保存Drawable对象的一些恒定不变数据,
     * 为了节约内存,Android系统对于从同一个res创建的Drawable对象会共享同一个ConstantState对象。
     */
    public static abstract class ConstantState 
        public abstract @NonNull Drawable newDrawable();
        public @NonNull Drawable newDrawable(@Nullable Resources res) return newDrawable();
        public @NonNull Drawable newDrawable(@Nullable Resources res, @Nullable @SuppressWarnings("unused") Theme theme) return newDrawable(res);
        public abstract @Config int getChangingConfigurations();
        public int addAtlasableBitmaps(@NonNull Collection<Bitmap> atlasList) return 0;
        protected final boolean isAtlasable(@Nullable Bitmap bitmap) ...
        public boolean canApplyTheme() return false;
    

    /**
     * 返回一个当前Drawable的ConstantState,参见mutate方法。
     */
    public @Nullable ConstantState getConstantState() return null;
    ......

确实闷逼!上面涉及 Paint 和 Canvas 的注释不懂的可以参考我这篇博文《Android应用自定义View绘制方法手册》。不过这和自定义 View 一样,为了强大,所以提供的方法都很基础专一,只有这样才能自由组合为所欲为;不过对于我们大多数自定义需求来说使用 Drawable 的方法是十分有限的,所以不要被那么多方法蒙蔽了双眼。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 Drawable 调用流程浅析

能耐心看完上面的是真爱,有了上面的解释我想你此刻一定在想,我自定义 View 好歹也有个调用流程、好歹也有个规律可循的结构啊,这自定义 Drawable 怎么是完全闷逼的,这就对了,这一小节就是打算通过源码给你揭开 Drawable 的调用流程,以此让你在自定义 Drawable 时做到胸有成竹。

为了让大家浅显易懂的知道 Drawable 的作用和用法,有了上面 Drawable 的分析外我们还需要给一个使用样例分析;其实关于 Drawable 最好的使用样例是 ImageView,鉴于 ImageView 源码的庞大复杂性,我们这里选择之前有铺垫分析的 View 为样例,看过《Android应用层View绘制流程与源码分析》的同学都知道,在 Android 中每个 View 都至少有一个 Drawable 成员变量,尤其在基类 View 中有一个背景 Drawable。我们来看看这段暧昧的代码:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource 
    ......
    private Drawable mBackground;
    ......

    //View构造方法
    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) 
        ......
        //获取View的属性
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
        //定义一个局部背景变量Drawable
        Drawable background = null;
        ......
        //获取到设置的background属性,也就是我们平时给各个控件在xml中设置的android:background属性值
        case com.android.internal.R.styleable.View_background:
        background = a.getDrawable(attr);
        break;
        ......
        //当设置有背景Drawable属性时调用setBackground方法
        if (background != null) 
            setBackground(background);
        
        ......
    

首先我们会发现在 Android 任何 View 控件中都有一个 Background 属性,框架会在 View 构造方法中获取这个属性 Drawable,然后传入 setBackground(background) 方法,这方法大家一定都很熟悉吧,我们通常总是通过各种 setBackgroundXXX 方法来给控件设置背景,用的就是这个系列方法,所以我们就直接去看看这个系列方法最终归处 setBackgroundDrawable(Drawable background) 方法,如下:

    //几种设置背景的方法终归都是这个
    public void setBackgroundDrawable(Drawable background) 
        ......
        //每次设置新的background后就进行复位操作
        if (mBackground != null) 
            if (isAttachedToWindow()) 
                //Drawable设置为不可见(这部就用上上面Drawable的分析了么)
                mBackground.setVisible(false, false);
            
            //Drawable回调断开(这部就用上上面Drawable的分析了么)
            mBackground.setCallback(null);
            //移除Drawable绘制队列,实质触发回调的该方法(这部就用上上面Drawable的分析了么)
            unscheduleDrawable(mBackground);
        

        if (background != null) 
            //当有设置背景Drawable
            ......
            //给Drawable设置View当前的布局方向(这部就用上上面Drawable的分析了么)
            background.setLayoutDirection(getLayoutDirection());
            //判断当前Drawable是否设置有padding(这部就用上上面Drawable的分析了么,有padding则返回true)
            if (background.getPadding(padding)) 
                //依据Drawable的这个padding去给当前View相关padding属性建议修改
                ......
            
            //比较上次旧的(可能没有)和现在设置的Drawable的最小宽高,发现不一样就预示着我们接下来需要对View再次layout,做标记
            if (mBackground == null
                    || mBackground.getMinimumHeight() != background.getMinimumHeight()
                    || mBackground.getMinimumWidth() != background.getMinimumWidth()) 
                requestLayout = true;
            
            //把要新设置的Drawable正式赋值给View的mBackground成员
            mBackground = background;
            //判断当状态改变时当前Drawable是否需要切换图片,一般在StateListDrawable实现中为true(这部就用上上面Drawable的分析了么)
            if (background.isStateful()) 
                //设置Drawable状态(这部就用上上面Drawable的分析了么)
                background.setState(getDrawableState());
            
            //如果View已经attached to window了就把Drawable设置为可见(这部就用上上面Drawable的分析了么)
            if (isAttachedToWindow()) 
                background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
            
            //设置Drawable的callback,在View继承关系中有实现Drawable的callback
            background.setCallback(this);
            ......
         else 
            //当没有设置背景Drawable时清空背景Drawable,然后设置View的重新layout标志
            mBackground = null;
            ......
            requestLayout = true;
        
        ......
        //需要重新布局,触发重新布局
        if (requestLayout) 
            requestLayout();
        
        //通知重新绘制刷新操作
        mBackgroundSizeChanged = true;
        invalidate(true);
        invalidateOutline();
    

可以看见,每逢我们对控件通过 xml 或者 java 设置 background 后触发的其实就是上面这一堆操作,实质最后触发的就是 layout 或者 draw。我们知道 View 默认的 onLayout 和 onDraw 是空实现,所以我们顺着流程去看看 View 的 draw 方法,如下:

    public void draw(Canvas canvas) 
        /*
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        ......
        drawBackground(canvas);
        ......
    

有点意思,再来看看 drawBackground(canvas) 方法,如下:

    private void drawBackground(Canvas canvas) 
        ......
        //实质调用了Drawable的setBounds方法,把当前View测量好的矩形区域顶点赋值给Drawable,说明接下来Drawable绘制区域与View大小相同。
        //该方法实质:mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
        setBackgroundBounds();
        //有意思!
        //简单粗暴理解就是View要是能滑动,化到哪Drawable背景画到哪(其实就是每次滑动都按可见区域绘制Drawable,因为canvas已经被依据滑动平移了)
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) 
            background.draw(canvas);
         else 
            canvas.translate(scrollX, scrollY);
            //这不就是Drawable的draw方法么,和前面分析的一样,最后View框架会调用我们Drawable的draw方法,传入的是当前View的canvas而已。
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        
    

握草,View 与 Drawable 的暧昧几乎真相大白了,由此更加验证了背景知识简单粗暴的介绍—— Drawable 就是一个框架工具,配合给 View 绘制来用的而已,所以通过上面两大节源码分析,我想你至少脑袋中已经产生了下面这个抽象的不能再抽象的对比图,然后也能恍然大悟吧。

这时候机智的人肯定又会问,那我设置完 background 的 Drawable 后 selector 那种状态切换又是咋回事呢?这儿只能回答你,自己动手去 View 中翻翻 state 相关的代码吧,这儿懒得说了,很简单的,核心就是 Android 中 Drawable 的一个特殊实现子类而已。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

4 Drawable 自定义实战

都说了,作为程序员和老大 PK 的真谛是 “Talk is easy, show me the code!”,上面几个小节 BB 了那么多源码,估计很多人都看不到这里就关闭博文了,没事,下面不 BB 了,我们直接来实战,这样你再通过实战的例子回过头去看看上面的源码分析,你会发现你已经完全领悟了。

下面先给出自定义 Drawable 的一般套路模板(通过自定义 Drawable 画一个黑圆为例),大家日后自定义按照这个模板思路来套即可,效果如下:

模板套路代码结构如下:

//自定义 Drawable 基本模板(实现一个黑圆Drawable)
public class RoundDrawable extends Drawable 
    private Paint mPaint;

    public RoundDrawable() 
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    

    @Override
    public void draw(Canvas canvas) 
        Rect rect = getBounds();
        int width = rect.right - rect.left;
        int heigh = rect.bottom - rect.top;
        canvas.drawCircle(width/2, heigh/2, Math.min(width/2, heigh/2), mPaint);
    

    @Override
    public void setAlpha(int i) 
        mPaint.setAlpha(i);
    

    @Override
    public void setColorFilter(ColorFilter colorFilter) 
        mPaint.setColorFilter(colorFilter);
    

    @Override
    public int getOpacity() 
        return PixelFormat.TRANSLUCENT;
    
//自定义 View 使用自定义 Drawable
public class CustomerView extends View 
    private Drawable mDrawable;

    public CustomerView(Context context) 
        this(context, null);
    

    public CustomerView(Context context, AttributeSet attrs) 
        super(context, attrs);
        init();
    

    private void init() 
        //在自定义View中实例化一个自定义Drawable
        mDrawable = new RoundDrawable();
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //自定义View获取大小后设置给自定义Drawable
        mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom());
    

    @Override
    protected void onDraw(Canvas canvas) 
        //把我们的自定义Drawable画到自定义View上
        mDrawable.draw(canvas);
    

套路啊,除过以上这个标准模式以外,我们还可以在 Android 标准控件中使用自定义的 Drawable,譬如给 ImageView 设置 setImageDrawable(customerDrawable) 也是可以的,不过注意提前设置 Bounds 大小。看完上面的套路你可能会想,这明明通过自定义 View 自己就能搞定,为毛还要自定义一个 Drawale,哈哈,淡定,你仔细想想哪个更加灵活呢,自己琢磨下吧!

套路就是标准,有了上面这几个模板套路,你会发现自定义 Drawable 其实核心就那几个方法,你要是想进阶更加有追去的模仿,不妨去看看 Android 标准实现的那些继承 Drawable 的 XXXDrawable,你会发现归一思想以后都是上面的模板套路,只是依据自己特点实现了自己的特点而已;有一个最值得观摩的官方实现就是android.support.v4.graphics.drawable.RoundedBitmapDrawable,短小精干,300 行代码,很适合模仿自定义 Drawable 学习。

有了源码流程浅析,有了套路模板,有了来龙去脉,我们还缺一个具备实用价值的自定义实例,如下实例源码( 点我 https://github.com/yanbober/DreamDrawable 找到 )演示了几种自定义 Drawable 的姿势,效果如下:

至于细节就不多说了,相信你依据模板套路是能理解的,不扯了,我去打球去了,运动最重要。

觉得对您有帮助,不妨打开支付宝扫码鼓励一下,谢谢!

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

以上是关于Android 应用层开发 Drawable 的一些叨叨絮的主要内容,如果未能解决你的问题,请参考以下文章

Android 应用开发中如何自定义 Drawable 背景?

Android Drawable的9种子类 介绍

Android Drawable的9种子类 介绍

Android中的Drawable资源

Android中的Drawable资源

Android Drawable - Shape Drawable使用详解(附图)