android N 在 Settings 中作了一些调整,如上面的截图。
- 增加了侧滑菜单,采用 v4 下的 DrawerLayout 来实现;
- 在 Settings 主界面增加了 Condition,能够在设置列表中显示状态;
- 在 Settings 主界面增加了 Suggestion。
首先来看下 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
- /**
- * 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";
Settings/res/values/donottranslate.xml
- "category_key_wireless">com.android.settings.category.wireless
- "category_key_device">com.android.settings.category.device
- "category_key_personal">com.android.settings.category.personal
- "category_key_system">com.android.settings.category.system
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 的逻辑,这里不作深究)。
上面的 getCategories 方法主要分为两个部分来看,首先通过 PackageManager 获得各个 Category 的信息保存到 ArrayList 中,接着对 ArrayList 中的数据按照优先级进行排序,这样主界面拿到这些数据就可以显示了。
看上面这幅图,从上而下分别是 Condition,Suggestion 和各个显示的 Item 项。
接下来我们来看这部分在代码中是如何构建的?
- 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 mItems = new ArrayList<>();
- private final List mTypes = new ArrayList<>();
- private final List mIds = new ArrayList<>();
在将这些布局文件加入到 List 中去后,然后在 onBindViewHolder 去获取 List 中的内容,从而展示在页面上,这部分的逻辑就不再介绍了,大家有兴趣的可以去看看。
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 存放到对应的集合中去,用于显示到页面上去。
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();
- }
- }
- }
,相当于为一些常用的功能界面添加入口,用户通过点击这些建议项可以跳到相应的页面进行操作,并且用户可以手动移除这些建议项。
如下面的截图,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 的设计真的是厉害,而且自己很多时候都是从源码的功能去理解,其实从架构,性能方面考虑,源码都是非常优秀的,有很多值得学习的地方。
来源: http://www.bubuko.com/infodetail-1962706.html