随着Material Design的普及,很多开发人员都会面临App的Material适配。如果你的App不只是针对5.0以上设备的话(多数情况也必须做兼容), 那么下面的经验总结将会对你有所帮助。当然,有些公司的App不会改成Material Design,但如果你以前使用AppCompatV7的话,升级到21后,你必然面临和以前不一样的使用方式,了解新的方式也是必须的。
言归正传,官方给我们的适配方案是AppCompatV7,新发布的22.1.1适配包相对于22又进行了较大的改动,同时对Material适配更加强大,因此本文主要介绍基于22.1.1版本的适配流程。
compile 'com.android.support:appcompat-v7:22.1.1'
这里需要说明的是使用19、20及其以下版本仍然是Holo风格,使用21和22版本都会有Material的效果。
引用完库之后,首先要面对的是配置主题。否则如果你以前使用AppCompat的话,运行之后会发现App惨不忍睹。
Theme主要有以下几种分类:
如果以前使用ActionBar Holo风格时使用的就是AppCompat,那么这些地方是不需要更改的。
注: Material下的ActionBar会比之前更大,这点可在之后的ActionMode讨论中看到。
Theme配置和原先有些不一样,配置示例如下:
<style name="Theme.App" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Main theme colors --> <!-- your app branding color for the app bar --> <item name="colorPrimary">@color/theme_primary</item> <!-- darker variant for the status bar and contextual app bars --> <item name="colorPrimaryDark">@color/theme_primary_dark</item> <!-- theme UI controls like checkboxes and text fields --> <item name="colorAccent">@color/theme_color_accent</item> </style>
先上官方解释图:
图上的各参数都可以直接配置到主题中生效。其中colorPrimaryDark仅在Lollipop以上机器生效。
colorAccent会改变光标颜色、checkbox背景色等。基本上可以理解为控件的主色调。
以Checkbox为例:官方默认是绿色的
改变colorAccent为蓝色后
Material可以让你轻松订制Staus Bar。
android:statusBarColor
属性来改变,默认从 android:colorPrimaryDark
中获取。 Window.setStatusBarColor()
现在AppCompat对窗口主题的flag要求更严格。主要原因是为了支持Dialog,大量使用了 AppCompat 之前并没有重视的 windowNoTitle 标志。
升级到v22.1.0以后(包括本文讲述的22.1.1),你可能遇到下面的异常:
Caused by: java.lang.IllegalArgumentException: AppCompat does not support the current theme features at android.support.v7.app.AppCompatDelegateImplV7.ensureSubDecor(AppCompatDelegateImplV7.java:360) at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:246) at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:106)
解决办法有两种:
如果不能这样做(或许你需要同时支持ActionBar和NoActionBar,其实也可以通过第一种方式来解决,可能colorPrimary之类的需要多配置一遍),你可以采用:
<style name="MyTheme" parent="Theme.AppCompat"> ... </style> <style name="MyTheme.NoActionBar" parent="MyTheme"> <!-- Both of these are needed --> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style>
最新的22.1.1版本,ActionBarActivity已经废弃。开始采用AppCompatActivity。如果你以前使用的是ActionBarActivity,建议替换掉,不需要更改其他代码。
(特别重要的AppCompatDelegate登场,具体代码可以查看AppCompatActivity实现,这里可以简单替换下快速适配,其实一般情况下也不需要自己来写AppCompatDelegate)
OK,到这里,其实你的App差不多就能正常运行了,只是有些细节方面还需要继续完善。你可能已经注意到你的Dialog还不是Material风格,那么我们继续来看Dialog。
AppCompat之前的21、22版本都没有实现Material Dialog。 在22.1.x发布时,这个问题终于解决了。
AppCompatDialog是AppCompat themed Dialog的 Base class.目前他的子类只有AlertDialog,但已经足够使用。
使用方式也很简单,直接将AlertDialog改为 android.support.v7.app
包下的AlertDialog即可。
其他使用方式一样,不需要做任何改动。
官方至今没有做到完全的适配。
对比图:
4.x设备上
5.x设备上
可以看到 PreferenceCategory
和 Preference
在4.x设备上底部都有横线,5.x设备上都没有。
也可以看到CheckBoxPreference是已经适配了的。
为了能让Preference适配的更加彻底,推荐下常用的第三方适配库: Android-MaterialPreference
但是作者并没有去写DialogPreference一类的,比如常见的ListPreference。
其实这里是有解决办法的。上面已经写到了新版的AlertDialog,配合 How can I change the appearance of ListPreference Dialog 这篇帖子,就不难实现。
但也可以看到有人讨论了 Material规范中提到的实现方式 ,当然也有人根据Google规范进行了 实现 ,这里可以根据需求来自行选择实现方式。
关于Preference需要说明的是:
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.supportv7.app; import android.content.res.Configuration; import android.os.Bundle; import android.preference.PreferenceActivity; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatDelegate; import android.support.v7.widget.Toolbar; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; /** * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls * to be used with AppCompat. * * This technique can be used with an {@link android.app.Activity} class, not just * {@link android.preference.PreferenceActivity}. */ public abstract class AppCompatPreferenceActivity extends PreferenceActivity { private AppCompatDelegate mDelegate; @Override protected void onCreate(Bundle savedInstanceState) { getDelegate().installViewFactory(); getDelegate().onCreate(savedInstanceState); super.onCreate(savedInstanceState); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); getDelegate().onPostCreate(savedInstanceState); } public ActionBar getSupportActionBar() { return getDelegate().getSupportActionBar(); } public void setSupportActionBar(@Nullable Toolbar toolbar) { getDelegate().setSupportActionBar(toolbar); } @Override public MenuInflater getMenuInflater() { return getDelegate().getMenuInflater(); } @Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); } @Override public void setContentView(View view) { getDelegate().setContentView(view); } @Override public void setContentView(View view, ViewGroup.LayoutParams params) { getDelegate().setContentView(view, params); } @Override public void addContentView(View view, ViewGroup.LayoutParams params) { getDelegate().addContentView(view, params); } @Override protected void onPostResume() { super.onPostResume(); getDelegate().onPostResume(); } @Override protected void onTitleChanged(CharSequence title, int color) { super.onTitleChanged(title, color); getDelegate().setTitle(title); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); getDelegate().onConfigurationChanged(newConfig); } @Override protected void onStop() { super.onStop(); getDelegate().onStop(); } @Override protected void onDestroy() { super.onDestroy(); getDelegate().onDestroy(); } public void invalidateOptionsMenu() { getDelegate().invalidateOptionsMenu(); } private AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, null); } return mDelegate; } }
Material中还有一个重要的特性是阴影的设置和波纹效果的实现,这里来粗略说明一下:
v21以后在View的xml中使用 android:elevation
属性,或者在代码中使用View的 setElevation()
方法。
还是需要使用.9图片的阴影来做。
注: ViewCompat.setElevation() sadly doesn't apply shadows in pre-Lollipop.
?android:attr/selectableItemBackground
?android:attr/selectableItemBackgroundBorderless
设置后,会从孩子往父亲找一个依附的色。如果View往上找的时候,亲生父亲没背景色,会继续向上查找直到最顶端。找到了最顶端的爷爷,这个时候才绘制。
然而,如果父亲的兄弟又绘制了颜色,且盖住了最顶端的绘制,会导致看不到效果。如果有一定的透明度,结果就显而易见了。
特别注意:
改变Theme中的 android:colorControlHighlight
属性。
<!-- A green ripple drawn atop a black rectangle. --> <ripple android:color="#ff00ff00"> <item android:drawable="@android:color/black" /> </ripple> <!-- A blue ripple drawn atop a drawable resource. --> <ripple android:color="#ff0000ff"> <item android:drawable="@drawable/my_drawable" /> </ripple>
android:color
中是点击响应色,也是波纹扩散色。
item中是正常状态下的显示。
一般使用时会和原有的selector配合,原有的selector负责5.0以下显示效果,新的selector内部含有ripple标签放在drawable-v21中,保证点击效果。
写到这里,我觉得对一个中国开发者的Material入门篇来说,还需要说明下魅族适配的问题
和魅族官方技术人员沟通过,不(pu)幸(tian)被(tong)告(qing)知(a):使用AppCompatV7 21以上,暂时无法进行SmartBar适配。
原因大概解释如下:
so,坐等魅族找到新的适配策略或者放弃SmartBar~~