资源变化
在Android 8.0及以前,导航栏NavigationBarView上的每个KeyButton的图标资源几乎都是用的PNG图片,这类资源使用通常需要在不同分辨率的drawable-xxx目录下面都加入相应的文件,这就导致一些客制化需求的修改也变得比较费事。
矢量图在不同分辨率下经过放大或缩小后,图片的质量都不会下降,使用矢量图替代PNG资源可以降低应用内存的占用。在9.0之前的版本,Google已经在逐步使用Vector矢量图来替换PNG资源,比如导航栏上最右边的切换输入法的按钮,在8.0版本上已经改为了矢量图,而到了9.0版本上导航栏的所有按钮资源都已经被替换成矢量图。
<!-- back按钮的Vector资源 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="?attr/singleToneColor"
android:pathData="M6.49,14.86c-0.66-0.39-0.66-1.34,0-1.73l6.02-3.53l5.89-3.46C19.11,5.73,20,6.26,20,7.1V14v6.9 c0,0.84-0.89,1.37-1.6,0.95l-5.89-3.46L6.49,14.86z" />
</vector>
黑白色按钮
其实这一点的变化并不大,之前已经提到在8.0版本上已经有图标开始使用矢量图了,所以后续9.0上也是用同样的做法修改的。
我们知道Android 8.0上谷歌引入了一个新的flag:View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,应用可以通过使用这个flag来轻松切换导航栏上的按钮的颜色(黑 - 白):SystemUI上负责显示按钮的是一类KeyButtonDrawable,这个类继承自LayerDrawable,它会把drawable资源层叠显示,我们设置的白色与黑色的drawable会被一个在上一个在下的放在这个容器中,当需要切换颜色时,KeyButtonDrawable会通过分别设置两边的alpha透明度的方式来实现。
在8.0及8.1版本上,NavigationBarView需要传入两个drawable资源,一个黑色的一个白色的:
private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
...
if (oldConfig.densityDpi != newConfig.densityDpi
|| oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
...
mHomeDefaultIcon = getDrawable(ctx,
R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
mRecentIcon = getDrawable(ctx,
R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
...
}
}
而在9.0上由于这些Vector的fillColor用的是attr值,之后可根据不同attr下获得相应的Context来设置不同的color,所以只需要传入一个xml的drawable资源即可:
private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
...
if (oldConfig.densityDpi != newConfig.densityDpi
|| oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
mBackIcon = getBackDrawable(lightContext, darkContext);
mRecentIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_recent);
...
}
}
private KeyButtonDrawable getDrawable(Context lightContext, Context darkContext,
@DrawableRes int icon, boolean hasShadow) {
return KeyButtonDrawable.create(lightContext, lightContext.getDrawable(icon),
darkContext.getDrawable(icon), hasShadow);
}
并且在使用了矢量图后,9.0也不再需要保留箭头朝下的back按钮图标资源(软键盘弹出后back键会变成箭头朝下),KeyButtonDrawable内部已经实现了用于图标旋转的方法:定义rotateDegrees值后调用invalidateSelf(),然后在draw()方法里根据rotateDegrees来旋转Canvas。
屏幕旋转
在绝大部分时候 —— 尤其是躺在床上的时候,为了避免屏幕突然被旋转,我们可能都会将屏幕自动旋转功能关闭。毕竟,我们并非时时都需要横屏观看内容。
但一旦需要让屏幕旋转时,我们又得手动在快捷设置中打开这个开关,长此以往,实在有些麻烦。
面对这种情况,Android 9.0 提供了一个更有「灵性」的解决方案:在屏幕旋转方向锁定的情况下,系统依然会检测你的设备方向;这时当你将手机横过来之后,屏幕内导航栏区域就会出现一个「自动旋转」开关,点击就能打开并旋转屏幕内容:
自动旋转按钮与其他常见按钮不太一样,它是AnimatedVectorDrawable,这是一个动态的矢量图,其xml资源文件需要使用animated-vector标签,并在里面定义一系列动画效果,就是上图gif里演示的小手机旋转的动画。关于AnimatedVectorDrawable,有兴趣的同学可以自行搜索了解一下。
public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
if (mNavigationBarView == null) return;
// At any point the the button can become invisible because an a11y service became active.
// Similarly, a call to make the button visible may be rejected because an a11y service is
// active. Must account for this.
ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
final boolean currentlyVisible = mNavigationBarView.isRotateButtonVisible();
// Rerun a show animation to indicate change but don't rerun a hide animation
if (!visible && !currentlyVisible) return;
View view = rotBtn.getCurrentView();
if (view == null) return;
KeyButtonDrawable kbd = rotBtn.getImageDrawable();
if (kbd == null) return;
// The KBD and AVD is recreated every new valid suggestion because of style changes.
AnimatedVectorDrawable animIcon = null;
if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
}
// Handle the visibility change and animation
if (visible) { // Appear and change (cannot force)
// Stop and clear any currently running hide animations
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
mRotateHideAnimator.cancel();
}
mRotateHideAnimator = null;
// Reset the alpha if any has changed due to hide animation
view.setAlpha(1f);
// Run the rotate icon's animation if it has one
if (animIcon != null) {
animIcon.reset();
animIcon.start();
}
...
}
}
布局变化
Android 9.0 导航栏上最Cool的变化,自然是新的导航手势及“药丸”按钮。
由于新增了一种导航手势,自然NavigationBarInflaterView加载的布局也多了一种。
8.0的加载默认布局:
protected String getDefaultLayout() {
return mContext.getString(R.string.config_navBarLayout);
}
9.0的加载默认布局:
protected String getDefaultLayout() {
final int defaultResource = mOverviewProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;
return mContext.getString(defaultResource);
}
当使用了Quickstep的布局时,导航栏就会变成这种效果:
关于9.0上的手势的使用方法,可移步至以下文章:
https://baijiahao.baidu.com/s?id=1600087701707774194&wfr=spider&for=pc