Drawable对于Android开发工程师来说非常熟悉,最常用的用法是在drawable目录里放入png或其他格式的图片,然后在代码里就可以用resources访问到如:

// 访问test图片资源
getResources().getDrawable(R.drwable.test);

这里不是要讲Drawable资源怎么使用,而是来看一下这个类实现的一些原理以及它相关的一些子类的实现原理。
Drawable类在SDK中是这么描述的:

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.
In addition to simple drawing, Drawable provides a number of generic
mechanisms for its client to interact with what is being drawn:

SDK中描述得比较清楚,Drawable是一个可以被画的抽象类,它仅仅是处理可以画的东西,它不像View,它没有接收事件等跟用户交互的机制。实际上,我们通常看到的控件中的视觉实现就是由Drawable来实现的,最常见的便是在控件中使用bitmap资源。

首先介绍Drawable和View之间的关系:

一、Drawable和View的关系

说到这个,得了解到Android控件的绘制过程,Android控件的绘制起点是ViewRootImpl的performTraversals:

private void performTraversals() { // ......int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); // ......mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());// ...... mView.draw(canvas); // ......
}

关于ViewRootImpl怎么创建的,可以去了解下Activity的启动过程,上面的代码mView是就是 Activity顶层的View,在绘制前会先测量控件大小和计算控件位置,接着就调用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.*/@CallSuperpublic void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:**      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)*/// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;// ......// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);// Step 5, draw the fade effect and restore layersfinal Paint p = scrollabilityCache.paint;final Matrix matrix = scrollabilityCache.matrix;final Shader fade = scrollabilityCache.shader;// ......// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);if (debugDraw()) {debugDrawFocus(canvas);}}

控件的draw分为几个步骤,具体可以看源码的注释,这其中的步骤3会调用onDraw方法,这就是一般在自定义View的时候要实现的方法,可以看到在绘制过程最终是要将内容绘制到Canvas上,我们再来看跟Drawable相关的方法之一,drawBackground:

/**
* Draws the background onto the specified canvas.
*
* @param canvas Canvas on which to draw the background
*/
private void drawBackground(Canvas canvas) {final Drawable background = mBackground;if (background == null) {return;}// ......final int scrollX = mScrollX;final int scrollY = mScrollY;if ((scrollX | scrollY) == 0) {background.draw(canvas);} else {canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);}
}

这里可以看到在绘制背景的时候是调用drawable的draw方法,drawable的draw方法是一个抽象方法,具体的实现由它的子类来实现,可以看一下最常用的BitmapDrawable类的实现:

@Override
public void draw(Canvas canvas) {final Bitmap bitmap = mBitmapState.mBitmap;// ......updateDstRectAndInsetsIfDirty();final Shader shader = paint.getShader();final boolean needMirroring = needMirroring();if (shader == null) {if (needMirroring) {canvas.save();// Mirror the bitmapcanvas.translate(mDstRect.right - mDstRect.left, 0);canvas.scale(-1.0f, 1.0f);}canvas.drawBitmap(bitmap, null, mDstRect, paint);if (needMirroring) {canvas.restore();}} else {updateShaderMatrix(bitmap, paint, shader, needMirroring);canvas.drawRect(mDstRect, paint);}// ......
}

可以看到最终是调用了canvas的drawBitmap方法。

小结一下:
Drawable是抽象了绘制逻辑的类,它在View中充当绘制的功能,跟绘制有关的逻辑都可以放到drawable中来实现,而最终的绘制都是通过canvas来处理的。

二、Drawable有哪些子类

上面分析了Drawable类与View之间的关系和绘制实现原理,因为Drawable是一个抽象类,下面我们来看一下Drawable有哪些子类,用好Drawable其实可以非常灵活地实现一些特别的效果。
Drawable在android.graphic包下类,打开这个包就可以看到它下面的所有的子类,子类有20多种,由于篇幅关系不一一介绍,有很多子类可能很多人并没有在代码中真实使用过,但是其实已经不知不觉的用了很久,比如.9 png是NinePatchDrawable,有按下状态的是StateListDrawable等等,因为这些子类基本上都跟drawable中放的xml文件对应起来了,可以看一下Resources的getDrawable方法,它会调loadDrawable和loadDrawableForCookie(API:28),最终会调到DrawableInfator的inflateFromTag:

// Resources.java
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,int density, @Nullable Resources.Theme theme)throws NotFoundException {// .......final Drawable.ConstantState cs;// ......Drawable dr;boolean needsNewDrawableAfterCache = false;if (cs != null) {dr = cs.newDrawable(wrapper);} else if (isColorDrawable) {dr = new ColorDrawable(value.data);} else {dr = loadDrawableForCookie(wrapper, value, id, density);}// .......}
/**
* Loads a drawable from XML or resources stream.
*
* @return Drawable, or null if Drawable cannot be decoded.
*/
@Nullable
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,try {// ......try {if (file.endsWith(".xml")) {final XmlResourceParser rp = loadXmlResourceParser(file, id, value.assetCookie, "drawable");dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);rp.close();} else {final InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);AssetInputStream ais = (AssetInputStream) is;dr = decodeImageDrawable(ais, wrapper, value);}} finally {stack.pop();}} catch (Exception | StackOverflowError e) {// ......throw rnf;}return dr;
}// DrawableInflater.java
@NonNull
@SuppressWarnings("deprecation")
private Drawable inflateFromTag(@NonNull String name) {switch (name) {case "selector":return new StateListDrawable();case "animated-selector":return new AnimatedStateListDrawable();case "level-list":return new LevelListDrawable();case "layer-list":return new LayerDrawable();case "transition":return new TransitionDrawable();case "ripple":return new RippleDrawable();case "adaptive-icon":return new AdaptiveIconDrawable();case "color":return new ColorDrawable();case "shape":return new GradientDrawable();case "vector":return new VectorDrawable();case "animated-vector":return new AnimatedVectorDrawable();case "scale":return new ScaleDrawable();case "clip":return new ClipDrawable();case "rotate":return new RotateDrawable();case "animated-rotate":return new AnimatedRotateDrawable();case "animation-list":return new AnimationDrawable();case "inset":return new InsetDrawable();case "bitmap":return new BitmapDrawable();case "nine-patch":return new NinePatchDrawable();case "animated-image":return new AnimatedImageDrawable();default:return null;}
}

可以看到,当你调用resouces.getDrawable的时候,实际上是解析了对应的xml资源并实例化对应的Drawable对象的。

三、Drawable资源复用设计

不知道你有没留意到loadDrawable方法里有个dr = cs.newDrawable(wrapper);cs是Drawable.ConstantState对象,这个是什么呢,这里就要介绍一下Drawable资源复用的设计了:
大家都知道手机的内存资源是很宝贵的,在很多场景下都要避免资源的重复创建,比如有两个ImageView想显示同一个图片资源,如果两个ImageView都decode一下生成两个bitmap,这肯定是浪费的,在我们常用的一些图片加载库时通常是使用内存缓存一保存bitmap对象来防止内存浪费,但在没有使用图片加载库的时候比如通常我们用getResources获得drawable或者就在layout里指定drawable资源作为background时,这两个ImageView使用的bitmap其实是同一份,这样就避免了浪费,这是如何实现的呢,这就是ConstantState来做的,Drawable里有个ConstantState内部类,定义如下:

/**
* 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.
*
*/
public static abstract class ConstantState {public abstract @NonNull Drawable newDrawable();/*** Creates a new Drawable instance from its constant state using the* specified resources. This method should be implemented for drawables* that have density-dependent properties.* <p>* The default implementation for this method calls through to* {@link #newDrawable()}.** @param res the resources of the context in which the drawable will*            be displayed* @return a new drawable object based on this constant state*/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 boolean canApplyTheme() {return false;}
}

这个类是用来在Drawable之间共享资源的,比如刚才举的两个ImageView的例子,他们使用background的Drawable对象是不同的,但他们的内部资源是一样的,在要用同一份资源来用在不同地方的时候,会使用ConstantState的newDrawable方法来创建Drawable对象,这也就是Resources.loadDrawble那段用newDrawable创建Drawable的代码,这样就达到了不同地方使用的Drawable但内享的资源是一样的,达到了资源复用的目的。这个类是一个抽象类,不同的Drawable子类实现对应的方法,比如BitmapDrawable共享的是bitmap而ColorDrawable共享的是color,NinePatchDrawable共享的是NinePatchState,里面保存了NinePatch对象表达.9png的配置信息,其他的Drawable则共享的是他们自己的状态。这里你可能会有个问题是:Drawable共享了这些状态,如果修复了这个状态就会修改所有的地方?是的,但如果想达到不同地方有不同的显示效果比如bitmap有的想加个透明度有的不想加,那么就要调用Drawable的mutate方法来重新生成状态并创建并的Drawable来达到目的。

总结

Drawable类是一个非常用的类,它使绘制逻辑和View实现了解耦,而ConstantState的设计实现的资源的共享,它实现原理值得我们深入学习和研究。用好了Drawable有时能在控件的绘制上做到事半功倍的效果。

深入理解Android中的Drawable类相关推荐

  1. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  2. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  3. Android中的Drawable(三)

    文章收藏的好句子:一个人也许无法改变外界,但可以改变自己. 目录 1.TransitionDrawable 2.InsetDrawable 3.ScaleDrawable 1.TransitionDr ...

  4. java线程画动图闪,Android中利用画图类和线程画出闪烁的心形,android心形,package com....

    Android中利用画图类和线程画出闪烁的心形,android心形,package com.package com.tt.view;import android.content.Context;imp ...

  5. Android中的Looper类

    简介 android中的looper类,是用来封装消息循环和消息队列的一个类,用于在Android线程中进行消息处理.handler可以看作是工具类,用于向消息队列中插入消息. looper类的作用 ...

  6. 彻底理解 Android 中的阴影

    如果我们想创造更好的 Android App,我相信我们需要遵循 Material Design 的设计规范.一般而言,Material Design 是一个包含光线,材质和投影的三维环境.如果我们想 ...

  7. 彻底理解 Android 中的阴影 1

    如果我们想创造更好的 Android App,我相信我们需要遵循 Material Design 的设计规范.一般而言,Material Design 是一个包含光线,材质和投影的三维环境.如果我们想 ...

  8. 如何理解Android中的xmlns

    作为一名 Android 开发,我想大家对xmlns并不会陌生,因为在写布局文件(如下代码所示)的时候经常会碰到,虽然很多人对其含义并不是特别了解(比如说我).好吧,今天我们就来挖一挖这神奇的xmln ...

  9. 深入理解Android中View

    文章目录 [隐藏] 一.View是什么? 二.View创建的一个概述: 三.View的标志(Flag)系统 四.MeasureSpec 五.几个重要方法简介 5.1 onFinishInflate() ...

最新文章

  1. OpenCV 中的 Scalar 类、Vec类
  2. selector + drawable 多状态图形
  3. springmvc path请求映射到bean 方法的流程
  4. MySQL记住密码_技术分享 | mysqlsh 命令行模式 密码保存
  5. 云服务器与传统服务器的优势差异
  6. android控件的隐藏与显示
  7. 怎么做手机的上下滑动_diy滴胶手机壳到底怎么做呢?
  8. 功率谱 魏凤英统计程序_单通道语音增强之统计信号模型
  9. java的IO知识梳理
  10. showdoc修改json转表格格式
  11. python乱码大赛_FishC工作室《零基础学python》全套课后题.doc
  12. Android app 开发环境搭建
  13. VC-MFC程序设计精讲
  14. GitHub 微信公众号爬虫推荐
  15. 一些实用型的工具及网站
  16. Stanford Algorithms: Design and Analysis, Part 1 [Final Exam]
  17. 天天向上的力量---python持续的力量
  18. 5G通信中的TDL模型
  19. Omi 官方插件系列 - omi-transform 介绍
  20. RK3588平台开发系列讲解(DisplayPort篇)DP相关模式说明

热门文章

  1. 文章生成器写出来的原创文章
  2. android项目实战—博学谷 界面设计
  3. Python爬虫,私活接单记录,假日到手5500,美滋滋
  4. MessageBox所有图标样例
  5. 微信公众号隐藏分享按钮和复制链接
  6. Vue.js学习笔记(三):隐藏a标签鼠标悬浮状态下浏览器左下角出现的链接地址
  7. 自定义c# MessageBox 弹出框中的button的Text
  8. 超级超级详细的实体关系抽取数据预处理代码详解
  9. FarPoint自动换行
  10. PHP基础-如何给图片添加水印