android 7.0中Settings新功能全面解析

Settings N预览

imageimage

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的整体结构,参见下图。 
image

在Settings N中,则将dashboard这部分的逻辑抽取了出来,放在/frameworks/base/packages/SettingsLib/目录下。N中不再使用dashboard_categories.xml这个文件来描述Settings各选项的架构,而且将Dashboard的初始化放在SettingsLib中来处理,首先看下面的图片:

Settings AndroidManifest.xml 
image

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";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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>
  • 1
  • 2
  • 3
  • 4

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";
  • 1
  • 2
  • 3
  • 4
  • 5

image

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是如何处理的 
image

上面的getCategories方法主要分为两个部分来看,首先通过PackageManager获得各个Category的信息保存到ArrayList中,接着对ArrayList中的数据按照优先级进行排序,这样主界面拿到这些数据就可以显示了。

Dashboard category的整体布局

image 
看上面这幅图,从上而下分别是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();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

recountItems方法会在构建布局的时候多次调用,这个方法里面会在这里加入多个layout布局文件。

如上面的代码的注释标明部分

//(1)condition_card.xml
//(2)dashboard_spacer.xml
//(3)suggestion_header.xml
//(4)dashboard_category.xml
//(5)dashboard_tile.xml
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里使用countItem方法将各个布局加入到List中去,分别是下面三个集合

private final List<Object> mItems = new ArrayList<>();
private final List<Integer> mTypes = new ArrayList<>();
private final List<Integer> mIds = new ArrayList<>();
  • 1
  • 2
  • 3

在将这些布局文件加入到List中去后,然后在onBindViewHolder去获取List中的内容,从而展示在页面上,这部分的逻辑就不再介绍了,大家有兴趣的可以去看看。

Settings Drawer的实现

image

N中的Settings使用DrawerLayout为Settings界面加入了侧滑菜单的功能。我们对比下M平台和N平台的Settings Activity的结构就大概明白了。 
image

android N在在SettingsActivity上面构建了一个SettingsDrawerActivity,侧滑的功能则是在SettingsDrawerActivity中实现的,SettingsActivity位于SettingsLib下面。

接下来我们看看SettingsDrawerActivity这个类: 
image 
在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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接着来看左侧Drawer的ListView的数据是如何加载的,这部分的逻辑由SettingsDrawerAdapter来实现。 
image

如上截图,在SettingsDrawerAdapter的updateCategories方法中,添加最上面的home的图片和文件后,然后遍历装有DashboardCategory的集合,取出里面的DashboardCategory和其中的Tile存放到对应的集合中去,用于显示到页面上去。

Settings中的Condition

7.0中的Settings加入的Condition可以显示设置有些item的状态,并且提供快捷开关,在单击后,可以跳转到相应的Settings 页面。

在上文介绍DashboardCategory的整体布局的时候,介绍了Condition部分加载的文件是condition_card.xml文件 
image

如上图和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>
  • 1
  • 2
  • 3
  • 4


接着来看看Condition的继承层次: 
image

我们拿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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

默认情况下这些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();
       }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Settings中的Suggestion

suggestion能够在设置的主页面显示一些建议项,相当于为一些常用的功能界面添加入口,用户通过点击这些建议项可以跳到相应的页面进行操作,并且用户可以手动移除这些建议项。

如下面的截图,Suggestion页面分为两个 
suggestion_header.xml和suggestion_tile.xml两个布局组成。 
image

关于Suggestion的配置信息: 
Suggestion默认的数量为2个,如上图所示,这个常量的设置是在DashboardAdapter.java里面。

 private static final int DEFAULT_SUGGESTION_COUNT = 2;
  • 1

另外这些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>
  • 1
  • 2

这里会通过SuggestionParser.java中new出来的SuggestionOrderInflater来解析这个文件,Suggestion相关的很多解析操作都是由SuggestionParser.java来处理的。

在SuggestionParser有以下的配置: 
image

这个类中定义的常量会在Settings的AndroidManifest.xml使用。 
image

如上图中定义的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.
  • 1
  • 2
  • 3
  • 4

这个属性允许Suggestion在特定的天数后显示,并且在被拒绝后重新显示。

0,10表示该Suggestion会立即显示,但是如果用户删除后,会在10天后再次显示。
10,30则表示在10天后显示,然后在30天之后再次显示。
  • 1
  • 2
  • 3
  • 4

以上就是对于android7.0 Settings的一些新功能的分析,其实这部分还有很多东西没有详细地去分析,这部分只是做了简单的介绍。

猜你喜欢

转载自blog.csdn.net/qq_32390877/article/details/80420491