写点什么

Android 应用层开发 Drawable 的一些叨叨絮 (1),androidstudio 中文社区

发布于: 2 小时前

关于 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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


, 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();

评论

发布
暂无评论
Android 应用层开发 Drawable 的一些叨叨絮(1),androidstudio中文社区