android 7.0中Settings新功能全面解析
Posted JasonGaoH
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 7.0中Settings新功能全面解析相关的知识,希望对你有一定的参考价值。
Settings N预览
android N 在Settings中作了一些调整,如上面的截图。
- 增加了侧滑菜单,采用v4下的DrawerLayout来实现;
- 在Settings主界面增加了Condition,能够在设置列表中显示状态;
- 在Settings主界面增加了Suggestion。
Dashboard category数据的加载
首先来看下Settings的Dashboard category,dashboard的中文意思指的是仪表板,在Settings中指的是Settings中显示的选项,如WLAN,Bluetooth这样的,参见上面的预览图片。
在android M中,dashboard的加载是放在SettingsActivity中,而且Settings/res/xml/dashboard_categories.xml这个文件专门用来描述dashboard的整体结构,参见下图。
在Settings N中,则将dashboard这部分的逻辑抽取了出来,放在/frameworks/base/packages/SettingsLib/目录下。N中不再使用dashboard_categories.xml这个文件来描述Settings各选项的架构,而且将Dashboard的初始化放在SettingsLib中来处理,首先看下面的图片:
Settings AndroidManifest.xml
SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
在TileUtils中定义的Actions,用于标记Activity属于哪一个Dashboard category
/**
* Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
*/
private static final String SETTINGS_ACTION =
"com.android.settings.action.SETTINGS";
private static final String OPERATOR_SETTINGS =
"com.android.settings.OPERATOR_APPLICATION_SETTING";
private static final String OPERATOR_DEFAULT_CATEGORY =
"com.android.settings.category.wireless";
private static final String MANUFACTURER_SETTINGS =
"com.android.settings.MANUFACTURER_APPLICATION_SETTING";
private static final String MANUFACTURER_DEFAULT_CATEGORY =
"com.android.settings.category.device";
Categories定义在Settings/res/values/donottranslate.xml中,分为四个大的Category,如下代码
Settings/res/values/donottranslate.xml
<string name="category_key_wireless">com.android.settings.category.wireless</string>
<string name="category_key_device">com.android.settings.category.device</string>
<string name="category_key_personal">com.android.settings.category.personal</string>
<string name="category_key_system">com.android.settings.category.system</string>
TileUtils.java中定义的Meta Data
Name of the meta-data item that should be set in the AndroidManifest.xml
to specify the icon、the title、the summary that should be displayed for the preference.
public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
Meta data会在AndroidManifest.xml进行配置,在TileUtils.java中加载Dashboard Category的时候,会通过PackageManager获得各个Activity的信息后,再动态的更新到页面上。(另外,我发现对于这些Dashboard的icon,title和Summary有的在AndroidManifest.xml中有配置meta-data有的却没有,我感觉这里应该用的是Activity节点下的icon,title(lablel),这部分如果要彻底搞清楚需要看PackageManager解析AndroidManifest.xml的逻辑,这里不作深究)。
接下来看在TileUtils.java代码中是对于Dashboard是如何处理的
上面的getCategories方法主要分为两个部分来看,首先通过PackageManager获得各个Category的信息保存到ArrayList中,接着对ArrayList中的数据按照优先级进行排序,这样主界面拿到这些数据就可以显示了。
Dashboard category的整体布局
看上面这幅图,从上而下分别是Condition,Suggestion和各个显示的Item项。
接下来我们来看这部分在代码中是如何构建的?
DashboardAdapter.java中的recountItems方法
private void recountItems()
reset();
boolean hasConditions = false;
for (int i = 0; mConditions != null && i < mConditions.size(); i++)
boolean shouldShow = mConditions.get(i).shouldShow();
hasConditions |= shouldShow;
//(1)condition_card.xml
countItem(mConditions.get(i), R.layout.condition_card, shouldShow, NS_CONDITION);
boolean hasSuggestions = mSuggestions != null && mSuggestions.size() != 0;
//(2)dashboard_spacer.xml
countItem(null, R.layout.dashboard_spacer, hasConditions && hasSuggestions, NS_SPACER);
//(3)suggestion_header.xml
countItem(null, R.layout.suggestion_header, hasSuggestions, NS_SPACER);
resetCount();
if (mSuggestions != null)
int maxSuggestions = getDisplayableSuggestionCount();
for (int i = 0; i < mSuggestions.size(); i++)
//(3)suggestion_tile.xml
countItem(mSuggestions.get(i), R.layout.suggestion_tile, i < maxSuggestions,
NS_SUGGESTION);
resetCount();
for (int i = 0; mCategories != null && i < mCategories.size(); i++)
DashboardCategory category = mCategories.get(i);
//(4)dashboard_category.xml
countItem(category, R.layout.dashboard_category, mIsShowingAll, NS_ITEMS);
for (int j = 0; j < category.tiles.size(); j++)
Tile tile = category.tiles.get(j);
//(5)dashboard_tile.xml
countItem(tile, R.layout.dashboard_tile, mIsShowingAll
|| ArrayUtils.contains(DashboardSummary.INITIAL_ITEMS,
tile.intent.getComponent().getClassName()), NS_ITEMS);
notifyDataSetChanged();
recountItems方法会在构建布局的时候多次调用,这个方法里面会在这里加入多个layout布局文件。
如上面的代码的注释标明部分:
//(1)condition_card.xml
//(2)dashboard_spacer.xml
//(3)suggestion_header.xml
//(4)dashboard_category.xml
//(5)dashboard_tile.xml
这里使用countItem方法将各个布局加入到List中去,分别是下面三个集合
private final List<Object> mItems = new ArrayList<>();
private final List<Integer> mTypes = new ArrayList<>();
private final List<Integer> mIds = new ArrayList<>();
在将这些布局文件加入到List中去后,然后在onBindViewHolder去获取List中的内容,从而展示在页面上,这部分的逻辑就不再介绍了,大家有兴趣的可以去看看。
Settings Drawer的实现
N中的Settings使用DrawerLayout为Settings界面加入了侧滑菜单的功能。我们对比下M平台和N平台的Settings Activity的结构就大概明白了。
android N在在SettingsActivity上面构建了一个SettingsDrawerActivity,侧滑的功能则是在SettingsDrawerActivity中实现的,SettingsActivity位于SettingsLib下面。
接下来我们看看SettingsDrawerActivity这个类:
在SettingsDrawerActivity的onCreate方法中会加载settings_with_drawer这个文件。这个文件则是对左侧Drawer的布局文件的描述。如下code:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorPrimaryDark">
<!-- The main content view -->
<LinearLayout
android:id="@+id/content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/actionBarStyle">
<Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:navigationContentDescription="@*android:string/action_bar_up_description"
android:theme="?android:attr/actionBarTheme"
style="?android:attr/toolbarStyle"
android:background="?android:attr/colorPrimary" />
</FrameLayout>
<FrameLayout
android:id="@+id/content_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/actionBarStyle" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:background="?android:attr/windowBackground" />
</LinearLayout>
<!-- The navigation drawer -->
<ListView android:id="@+id/left_drawer"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="?android:attr/colorBackground" />
</android.support.v4.widget.DrawerLayout>
接着来看左侧Drawer的ListView的数据是如何加载的,这部分的逻辑由SettingsDrawerAdapter来实现。
如上截图,在SettingsDrawerAdapter的updateCategories方法中,添加最上面的home的图片和文件后,然后遍历装有DashboardCategory的集合,取出里面的DashboardCategory和其中的Tile存放到对应的集合中去,用于显示到页面上去。
Settings中的Condition
7.0中的Settings加入的Condition可以显示设置有些item的状态,并且提供快捷开关,在单击后,可以跳转到相应的Settings 页面。
在上文介绍DashboardCategory的整体布局的时候,介绍了Condition部分加载的文件是condition_card.xml文件
如上图和xml文件相对应,分别表明了各个控件的id。
condition_card.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="?android:attr/colorAccent"
android:elevation="2dp"
android:clickable="true"
android:focusable="true">
<LinearLayout
android:id="@+id/collapsed_group"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:gravity="center">
<ImageView
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:tint="?android:attr/textColorPrimaryInverse" />
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimaryInverse" />
<ImageView
android:id="@+id/expand_indicator"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="16dp"
android:tint="?android:attr/textColorPrimaryInverse"/>
</LinearLayout>
<LinearLayout
android:id="@+id/detail_group"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingStart="72dp"
android:visibility="gone"
android:orientation="vertical">
<!-- TODO: Don't set alpha here, and do proper themeing that
handles night mode -->
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="16dp"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:alpha=".7"
android:textColor="?android:attr/textColorPrimaryInverse" />
<!-- TODO: Better background -->
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height=".25dp"
android:background="@android:color/white" />
<com.android.internal.widget.ButtonBarLayout
android:id="@+id/buttonBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
style="?attr/buttonBarStyle"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<Button
android:id="@+id/first_action"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:alpha=".8"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimaryInverse"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/second_action"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha=".8"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimaryInverse"
style="?android:attr/buttonBarButtonStyle" />
</com.android.internal.widget.ButtonBarLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
接着来看看Condition的继承层次:
我们拿AirplaneModeCondition来举例,在Settings的AndroidManifest.xml中注册了如下的Receiver:
<receiver
android:name=".dashboard.conditional.AirplaneModeCondition$Receiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE" />
</intent-filter>
</receiver>
默认情况下这些Condition是关闭的,即enabled的。在这个Receiver中,会去接收这个广播,当Condition的状态改变的时候会去更新状态。
//AirplaneModeCondition.java
@Override
public void refreshState()
setActive(WirelessUtils.isAirplaneModeOn(mManager.getContext()));
public static class Receiver extends BroadcastReceiver
@Override
public void onReceive(Context context, Intent intent)
if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction()))
ConditionManager.get(context).getCondition(AirplaneModeCondition.class)
.refreshState();
Settings中的Suggestion
suggestion能够在设置的主页面显示一些建议项,相当于为一些常用的功能界面添加入口,用户通过点击这些建议项可以跳到相应的页面进行操作,并且用户可以手动移除这些建议项。
如下面的截图,Suggestion页面分为两个
suggestion_header.xml和suggestion_tile.xml两个布局组成。
关于Suggestion的配置信息:
Suggestion默认的数量为2个,如上图所示,这个常量的设置是在DashboardAdapter.java里面。
private static final int DEFAULT_SUGGESTION_COUNT = 2;
另外这些Suggestion是以一种顺序来显示的,这个部分的配置是在suggestion_ordering.xml中配置的。
<optional-steps>
<step category="com.android.settings.suggested.category.LOCK_SCREEN" />
<step category="com.android.settings.suggested.category.EMAIL" />
<step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
multiple="true" />
<step category="com.android.settings.suggested.category.HOTWORD" />
<step category="com.android.settings.suggested.category.DEFAULT"
multiple="true" />
<step category="com.android.settings.suggested.category.SETTINGS_ONLY"
multiple="true" />
</optional-steps>
这里会通过SuggestionParser.java中new出来的SuggestionOrderInflater来解析这个文件,Suggestion相关的很多解析操作都是由SuggestionParser.java来处理的。
在SuggestionParser有以下的配置:
这个类中定义的常量会在Settings的AndroidManifest.xml使用。
如上图中定义的com.android.settings.require_feature的meta-data节点表示该Suggestion的显示需要特定的feature支持,对于FingerprintEnrollSuggestionActivity这个Suggestion的显示则需要指纹的支持。
另外对于META_DATA_DISMISS_CONTROL则控制着当前Suggestion的显示时机。正如上面截图的注释描述。
Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
For instance:
0,10
Will appear immediately, but if the user removes it, it will come back after 10 days.
Another example:
10,30
Will only show up after 10 days, and then again after 30.
这个属性允许Suggestion在特定的天数后显示,并且在被拒绝后重新显示。
0,10表示该Suggestion会立即显示,但是如果用户删除后,会在10天后再次显示。
10,30则表示在10天后显示,然后在30天之后再次显示。
以上就是对于android7.0Settings的一些新功能的分析,其实这部分还有很多东西没有详细地去分析,这部分只是做了简单的介绍。
另外,再去看源码的时候,发现Google的设计真的是厉害,而且自己很多时候都是从源码的功能去理解,其实从架构,性能方面考虑,源码都是非常优秀的,有很多值得学习的地方。
以上是关于android 7.0中Settings新功能全面解析的主要内容,如果未能解决你的问题,请参考以下文章
Android 7.0 Settings Summary 小记
具透丨Android 7.0 Nougat 正式版来了,这是最全面的上手指南