-
Tint的本质就是调用了Drawable的
setColorFilter来设置了PorterDuffColorFilter. -
设置Tinit的流程中一般都不是直接使用Color, 而是使用
ColorStateList, 根据不同的状态获取不同的 Color, 从而设置不同的Tint颜色, 所以在看Tint相关的代码中, ColorStateList 非常常见。 -
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就互不影响了。 -
通过Resource的 getDrawable 方法根据 resource id来生成Drawable的时候, 最后都会调用到 Drawable的静态方法 createFromXXX, 这些静态方法会根据传入的参数来调用BitmapFactory或者ImageDecoder来对图片进行解码,然后把解码之后的Bitmap包装成Drawable返回, 常见的就是 BitmapDrawable 和 NinePathDrawable.
-
得到Drawable之后, 我们会通过View的
setBackgroundDrawable等方法来进行显示, 最终会调用到View类的onDraw方法, 然后调用到该Drawable 的draw方法, 最终完成绘制。以 BitmapDrawable 为例, 最后调用的就是Canvas的drawBitmap方法进行的绘制 -
我们在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;
}
}
- 自定义Drawable中有两个要注意的地方, 一个是一定要
setBound, 这个用来设置该Drawable要绘制的位置和大小, 一般是相对于在draw中传进来的 Canvas 而言的, 这个Bound是 Drawable要展现给用户看的位置和大小, 是要展示在界面上的大小。 第二个是要注意 Drawable的 固有宽高,也就是内在宽高,getIntrinsicWidth和getInstrinsicHeight, 他反映了Drawable在不考虑任何外部因素时候的大小, 和展示无关。 比如 BitmapDrawable中的内在宽高就是Bitmap的实际宽高, 对于 ShapeDrawable 来说, 如果在xml中设置width和height, 则就是这两个值,否则默认返回-1, 表示没有内在宽高。
一般情况下, Drawable没有内在大小的概念,他的大小取决于View的大小以及布局参数等。
- 根据Resource Id找资源的大体流程如下
-
Android加载完Drawable之后, 默认会对Drawable进行缩放, 根据屏幕实际的DPI和资源对应的DPI进行缩放, 比如屏幕是440DPI(大于320,所以属于xxhdpi), 资源会从xxhdpi加载的, 但是xxhdpi对应的是480dpi, 那么Drawable加载完成之后就会进行一些缩小, 缩小的倍率就是440/480。
-
Android的阴影主要是由两个属性来控制, 一个是 elevation, 来设置Z轴的高度, Z轴越高, 阴影越大。一个是 translationZ, 这个和 translationX, translationY含义是一模一样的, 也就是说Z轴的总体高度是由 elevation + translationZ 来决定的。 elevation是为了模拟有背景且有高度的物体,被光照打光投射出来的阴影, 所以要求View一定要有Background, 否则 elevation属性无效, 同理, translationZ 也是一样, 必须有Background才可以生效。经过测试,使用
<vector>设置的背景,在设置elevation还是没有阴影,其他的背景都可以有阴影 这两个属性设置的阴影只是一种绘制效果, 完全不占用View的布局大小。默认阴影的颜色是在theme中指定的,我们可以通过更改
setSpotShadowColor和setSpotShadowColor来更改阴影的颜色,或者在theme中更改阴影的透明度<item name="android:ambientShadowAlpha">0.05</item> <item name="android:spotShadowAlpha">0</item>。或者通过自定义View的OutlineProvider来自定义View的阴影 -
我们经常要对一个数字进行32位对齐什么的操作, 通常都是
((length+32)/32)*32. 但是也可以使用(length+31) & ~31这种更有效率的方法 -
所有创建Bitmap的方法, 大体路径如下
- 正则表达式中
[],除了[]\-^这五个字符之外,其余的字符都表示字面含义。这五个字符如果想要表示其字面含义,需要使用\进行转义。 另外,-这个字符串,要表示0-9这种连字符的话,不能写在[]的开始或者结尾,比如[-9]中,-就还是表示其字面含义,也就是说-必须真正的连接一个范围,才能表示连字符的含义,否则表示的还是字面意思。 还有就是^只有在[]开头才表示否定的含义,在[]中其他的任何位置都表示其字面含义。