Drawable备忘

117 阅读5分钟
  1. Tint的本质就是调用了Drawable的setColorFilter来设置了PorterDuffColorFilter.

  2. 设置Tinit的流程中一般都不是直接使用Color, 而是使用 ColorStateList, 根据不同的状态获取不同的 Color, 从而设置不同的Tint颜色, 所以在看Tint相关的代码中, ColorStateList 非常常见。

  3. Drawable本质上是一个可以绘制的对象, 或者说最常见的就是一个可以绘制的图片, 底层的Bitmap是一样的, 如果Drawable对应的绘制状态,比如Filter等也是一样的(在绝大多数情况下, 这些状态都是一样的), 所以整个Drawable绘制出来的图形也是一样的。 也就是说我们可以认为 Drawable 最终绘制出来的图像由两部分决定: 第一部分是底层的Bitmap, 第二部分是Drawable的绘制状态,被抽象成ConstantState内部类。 所以为了性能, Android decode出来的每一张图片对应的Bitmap, 封装成的Drawable都被缓存起来了, 缓存的就是这个Drawable的绘制状态ConstantState, 所以我们对同一张图片反复decode得到的不同的Drawable实例, 其实对应的都是同一个ConstantState, 所以当我们通过一个Drawable实例去更改ConstantState的时候, 就会影响到其他Drawable实例。 为了解决这个问题, Drawable提供了一个mutate 方法, 这个方法对于同一个Drawable只能调用一次, 该方法会根据现有的ConstantState来生成一个新的ConstantState, 并且用这个ConstantState来生成一个新的Drawable, 这样这两个Drawable就互不影响了。

  4. 通过Resource的 getDrawable 方法根据 resource id来生成Drawable的时候, 最后都会调用到 Drawable的静态方法 createFromXXX, 这些静态方法会根据传入的参数来调用BitmapFactory或者ImageDecoder来对图片进行解码,然后把解码之后的Bitmap包装成Drawable返回, 常见的就是 BitmapDrawable 和 NinePathDrawable.

  5. 得到Drawable之后, 我们会通过View的 setBackgroundDrawable等方法来进行显示, 最终会调用到View类的 onDraw 方法, 然后调用到该Drawable 的 draw方法, 最终完成绘制。以 BitmapDrawable 为例, 最后调用的就是 CanvasdrawBitmap方法进行的绘制

  6. 我们在xml中可以使用的 drawable对应的标签如下

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;
    }
}

  1. 自定义Drawable中有两个要注意的地方, 一个是一定要setBound, 这个用来设置该Drawable要绘制的位置和大小, 一般是相对于在draw中传进来的 Canvas 而言的, 这个Bound是 Drawable要展现给用户看的位置和大小, 是要展示在界面上的大小。 第二个是要注意 Drawable的 固有宽高,也就是内在宽高, getIntrinsicWidthgetInstrinsicHeight, 他反映了Drawable在不考虑任何外部因素时候的大小, 和展示无关。 比如 BitmapDrawable中的内在宽高就是Bitmap的实际宽高, 对于 ShapeDrawable 来说, 如果在xml中设置width和height, 则就是这两个值,否则默认返回-1, 表示没有内在宽高。

一般情况下, Drawable没有内在大小的概念,他的大小取决于View的大小以及布局参数等。

  1. 根据Resource Id找资源的大体流程如下

image.png

  1. Android加载完Drawable之后, 默认会对Drawable进行缩放, 根据屏幕实际的DPI和资源对应的DPI进行缩放, 比如屏幕是440DPI(大于320,所以属于xxhdpi), 资源会从xxhdpi加载的, 但是xxhdpi对应的是480dpi, 那么Drawable加载完成之后就会进行一些缩小, 缩小的倍率就是440/480。

  2. Android的阴影主要是由两个属性来控制, 一个是 elevation, 来设置Z轴的高度, Z轴越高, 阴影越大。一个是 translationZ, 这个和 translationX, translationY含义是一模一样的, 也就是说Z轴的总体高度是由 elevation + translationZ 来决定的。 elevation是为了模拟有背景且有高度的物体,被光照打光投射出来的阴影, 所以要求View一定要有Background, 否则 elevation属性无效, 同理, translationZ 也是一样, 必须有Background才可以生效。经过测试,使用<vector>设置的背景,在设置elevation还是没有阴影,其他的背景都可以有阴影 这两个属性设置的阴影只是一种绘制效果, 完全不占用View的布局大小。

    默认阴影的颜色是在theme中指定的,我们可以通过更改setSpotShadowColorsetSpotShadowColor来更改阴影的颜色,或者在theme中更改阴影的透明度<item name="android:ambientShadowAlpha">0.05</item> <item name="android:spotShadowAlpha">0</item>。或者通过自定义View的OutlineProvider来自定义View的阴影

  3. 我们经常要对一个数字进行32位对齐什么的操作, 通常都是 ((length+32)/32)*32. 但是也可以使用(length+31) & ~31这种更有效率的方法

  4. 所有创建Bitmap的方法, 大体路径如下

image.png

  1. 正则表达式中[],除了[]\-^这五个字符之外,其余的字符都表示字面含义。这五个字符如果想要表示其字面含义,需要使用\进行转义。 另外,-这个字符串,要表示0-9这种连字符的话,不能写在[]的开始或者结尾,比如[-9]中,-就还是表示其字面含义,也就是说-必须真正的连接一个范围,才能表示连字符的含义,否则表示的还是字面意思。 还有就是^只有在[]开头才表示否定的含义,在[]中其他的任何位置都表示其字面含义。