Chris Banes讲解Android Support Library 22.1

英文原文: https://chris.banes.me/2015/04/22/support-libraries-v22-1-0/

转载译文请保留出处: http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0424/2778.html 

Support Libraries v22.1.0

话说,我已经很长时间没有更新博客了。昨天Android Support Library 22.1 发布,这可能是我们对兼容库所做的非官方平台的最大发行版本。

在开始之前,建议你阅读Ian的官方博客 ,(这里是译文 ),大致描绘了这个版本的所有特性。

本文将主要讲解我们做这些工作的原因以及方法,特别是跟我的工作相关的部分(因为我正好知道这些东西的来龙去脉)。

AppCompat

首先我们来讲讲本次发行版本修改最大的AppCompat,第一个要讲的,就是重构、、、

重构

以往使用AppCompat的唯一入口就是ActionBarActivity类,迫使你使用固定死的一套Activity结构,使得像PreferenceActivity这样的类无法利用AppCompat的特性。

现在,我们将ActionBarActivity里面的内部逻辑都抽离出来,然后封装成一个单一的代理(delegate) api供调用,AppCompatDelegate 。 AppCompatDelegate可以被任意带有Window.Callback的安卓对象创建,比如Activity或者Dialog的子类。你可以通过他的静态方法create()创建: 

AppCompatDelegate.create(this, null);

但是在使用delegate的时候有一些约束,你必须调用它暴露的每一个回调方法(比如onCreate()),但是这也不复杂,而且你还可以将他们写在一个基类中。只要你按照此要求,你就可以将AppCompat的功能应用在任意的Activity子类中。如果你打算使用AppCompatDelegate,我强烈建议你参考一下AppCompatActivity的源码,他是整合AppCompatDelegate的绝佳教材。

但是大多数人都不需要这么底层的自定义,直接使用我们的AppCompatActivity就足够了(就像之前使用ActionBarActivity一样)。

注:”你必须调用它暴露的每一个回调方法“的意思其实是,在onCreate()中必须调用delegate().onCreate(savedInstanceState);在 onStop中必须调用delegate().onStop();以此类推。

对话框

在重构一节中,我已经提到了对话框的事情,你应该可以从中猜到我们为对话框加了哪些东西。在完成重构的工作之后,对话框就是水到渠成的事情了。因为从绘制的观点来看,Activity和Dialog之间只有很小的差别。

也就是说,我们已经结束了自AppCompat v21以来最受诟病的要求之一:material风格的对话框。

现在我们可以在任意试用了Theme.AppCompat.Dialog(或者类似)的地方使用新的AppCompatDialog。AppCompat还有自己的material风格的AlertDialog。你只需使用将类型换成 android.support.v7.app.AlertDialog 就可以了。不用管其余的事情。

需要注意的是AppCompat的AlertDialog并没有实现framework版本的所有功能。它只实现了材料设计中那些比较重要的特性。

android:theme

在阅读这个小节之前,确保你已经看了_Theme 与 Style_ 这篇文章,它解释了我们将要讨论的一些基本概念。

在AppCompat v21中,我们提供了设置Toolbar 主题的快捷方法:app:theme

而在22.1.0版本,我们将这个功能扩展成了可以为布局中的任意view使用主题。同时以前的`app:theme`替换成了android:theme,这样的好处是可以在compat和framework之间无缝切换(如果你不想使用`compat`,而使用`framework`的主题,不需要一个个的将app:替换成android:,反之亦然)。

但是,还有更好的,那就是view会自动继承父view的主题设置,这一点在api 11以上的设备中有效,下面是一个简单的例子:

<Toolbar
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    <!-- 这个TextView继承了他的父亲Toolbar的主题 -->
    <TextView android:text="I'm light!" />
</Toolbar>

对于运行API v10或者更老版本的设备,你仍然可以使用android:theme,不过无法继承父view的主题,这就意味着你要么重新思考你的布局,要么为所有的子view设置android:theme(这确实很没效率)。

如果感兴趣,可以看看LayoutInflater.Factory2是如何让继承父亲主题功能成为现实的

Widgets

如果你读了Ian的文章,你会发现widget的着色功能现在已经公开了(还添加了更多的新功能)。 非常棒,不过随之改变的是:我们不会再修改该平台主题的默认控件样式了。也就是说如果你使用AppCompat的控件(不论显式还是隐式的),那么你在 v21 版本之前的设备上只能获取到material样式。在实际使用中其实没有什么不同,因为AppCompat的控件是自动置入的。

对于这个问题,包括我在内的很多人都看不懂,Chris Banes在评论部分给出了解释:

new EditText(...) // 在Lollipop 以上的设备是Material 主题
new AppCompatEditText(...) // 任意地方都是Material 主题
<edittext/> // 任意地方都是Material 主题

这样我们就解决了使用material样式但是控件没有着色的问题。

Theme window features

AppCompat现在对于主题的window flag是非常严格的,更接近于从framework中得到的东西。

这背后的主要原因是为了支持上面我们提到的对话框新特性。它们大量使用了windowNoTitle 标识,而以前的AppCompat中并没有多大关注。

在更新了v22.1.0之后,你可能会发现如下的异常:

IllegalArgumentException: AppCompat does not support the current theme features

可以查看我在StackOverflow上关于修改这个主题问题的答案:http://stackoverflow.com/q/29790070/474997

v4

support-v4在兼容包中属于元老级别,它仍然在被添加新的功能。

ColorUtils

ColorUtils 从Palette中移出来放到了support-v4中,作为一个公共类使用。它包含了一些对颜色处理非常有用的东西。比如,你可以为一个背景色上的文字计算最小透明度:

int backgroundColor = ...;
int textColor = Color.WHITE;
float minContrastRatio = 4.5f; // We want a minimum contrast ration of 1:4.5
int minAlpha = ColorUtils.calculateMinimumAlpha(
        textColor, backgroundColor, minContrastRatio);
if (minAlpha != -1) {
    // There is an alpha value which has enough contrast, use it!
    return ColorUtils.setAlphaComponent(textColor, minAlpha);
}

这个类还有一些好东西,比如计算颜色组成与亮度的工具,可以在java文档中查看更多的信息。

Drawable tinting

Lollipop中为Drawable添加的着色方法对于资源文件的动态着色是非常有用的。在v21中,AppCompat有自己的内部的实现方式,现在我们将这个实现放到了support-v4的 DrawableCompat 中,知道其使用方法是很重要的:

Drawable drawable = ...;
// Wrap the drawable so that future tinting calls work
// on pre-v21 devices. Always use the returned drawable.
drawable = DrawableCompat.wrap(drawable);
// We can now set a tint
DrawableCompat.setTint(drawable, Color.RED);
// ...or a tint list
DrawableCompat.setTintList(drawable, myColorStateList);
// ...and a different tint mode
DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_OVER);

在调用了DrawableCompat.wrap() 之后,不要期望返回的结果还和原来你传递的一致,要重新获得原始的Drawable需要调用DrawableCompat.unwrap()方法。DrawableCompat的内部将Drawable封装成一个特殊的“可着色drawable”,可以根据指定的色彩改变颜色。这个特性方便我们处理ColorStateList的初始化。

Palette

Palette在这个版本也有一些改变,第一件事就是使用Builder 类来实例化Palette。我们发现过去一直在为Palette添加太多的碎片代码,让api看起来很复杂。Builder是让api更舒服的一个好方式(有吗?不觉得)。

第二个(也是最重要)的改变是产生Palette的效率得到了大大的提高。在获取Palette的时候,颜色质量这一步是代价最高的,它需要将一张图片中所有像上的颜色取出来然后将颜色深度降低到较小的值(通常是16).

在这个版本中,我们回归到了传统的方式来优化颜色质量的问题。诸如分配更少的对象,使用更恰当的数据结构,减小算法的复杂程度。这些举措让速度获得了大幅的提升。

下面是我们做的一个测试。可以看到在ART模式下速度大概提升了5-6倍,在Dalvik模式下更高。

Device22.022.1.0Speedup
Nexus 647ms8ms~6x
Nexus 555ms11ms~5x
Nexus One1200ms120ms~10x

这个结果并不是很科学,只是给了一个大致的估算,不过可以通过它评估速度提升的效果。