1. 程式人生 > >Android5.0 Lollipop DisplaySettings分析

Android5.0 Lollipop DisplaySettings分析

1.本文是接接上一篇Setting啟動分析

2.本文主要是對DisplaySettings進行分析

1.總概況

 

本章主要分析點選左圖顯示,顯示中圖,點選互動屏保後跳轉到右圖的過程。

2.載入SettingsActivity

在setting中的選項為都在R.xml.dashboard_categories中,點選後會載入fragment,本次研究的DisplaySettings如下:

<dashboard-tile
                android:id="@+id/display_settings"
                android:title="@string/display_settings"
                android:fragment="com.android.settings.DisplaySettings"
                android:icon="@drawable/ic_settings_display"
                />
可知點選後會呼叫com.android.settings.DisplaySettings這個fragment。但是點選處理的程式碼在哪裡呢,一般情況下可以從SettingsActivity、DashboardSummary等程式碼中找,但是本次點選事件的響應是在DashboardTileView類的onclick函式中。
 @Override
    public void onClick(View v) {
        if (mTile.fragment != null) {
            Utils.startWithFragment(getContext(), mTile.fragment, mTile.fragmentArguments, null, 0,
                    mTile.titleRes, mTile.getTitle(getResources()));
        } else if (mTile.intent != null) {
            getContext().startActivity(mTile.intent);
        }
    }
很明顯,mTile.fragment是"com.android.settings.DisplaySettings",不是null。進入Utils.startWithFragment()函式。
public static void startWithFragment(Context context, String fragmentName, Bundle args,
            Fragment resultTo, int resultRequestCode, int titleResId,
            CharSequence title) {
        startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,
                null /* titleResPackageName */, titleResId, title, false /* not a shortcut */);
    }
跳轉一下
    public static void startWithFragment(Context context, String fragmentName, Bundle args,
            Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId,
            CharSequence title, boolean isShortcut) {
        Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName,
                titleResId, title, isShortcut);
        if (resultTo == null) {
            context.startActivity(intent);
        } else {
            resultTo.startActivityForResult(intent, resultRequestCode);
        }
    }
先看onBuildStartFragmentIntent函式
    public static Intent onBuildStartFragmentIntent(Context context, String fragmentName,
            Bundle args, String titleResPackageName, int titleResId, CharSequence title,
            boolean isShortcut) {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClass(context, SubSettings.class);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,
                titleResPackageName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut);
        return intent;
    }
就是返回一個intent,可以看出該intent其實是一個操作,啟動一個SettingsActivity,並且把一個fragmentName放在引數中。然後就startActivity(intent);啟動SettingsActivity。

繼續進入SettingsActivity的onCreate函式,本次是將主要的函式列出:

 	getMetaData();
        final Intent intent = getIntent();//獲取intent
        final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);//獲取fragment名字
        final ComponentName cn = intent.getComponent();
        final String className = cn.getClassName();
        mIsShowingDashboard = className.equals(Settings.class.getName());//本次的class是SettingsActivity,所以是false
<span style="white-space:pre">	</span>setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);//本次載入的是R.layout.settings_main_prefs
 <span style="white-space:pre">	</span>setTitleFromIntent(intent);
        Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
        switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);//重點

首先看settings_main_prefs

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_height="match_parent"
              android:layout_width="match_parent">

    <LinearLayout
            android:orientation="vertical"
            android:layout_height="0px"
            android:layout_width="match_parent"
            android:layout_weight="1">

        <com.android.settings.widget.SwitchBar android:id="@+id/switch_bar"
                  android:layout_height="?android:attr/actionBarSize"
                  android:layout_width="match_parent"
                  android:background="@drawable/switchbar_background"
                  android:theme="?attr/switchBarTheme"
                />

        <FrameLayout
                android:id="@+id/main_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="?attr/preferenceBackgroundColor"
                />

    </LinearLayout>

    <RelativeLayout android:id="@+id/button_bar"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:layout_weight="0"
                    android:visibility="gone">

        <Button android:id="@+id/back_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:layout_alignParentStart="true"
                android:text="@*android:string/back_button_label"
                />

        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true">

            <Button android:id="@+id/skip_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/skip_button_label"
                    android:visibility="gone"
                    />

            <Button android:id="@+id/next_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/next_button_label"
                    />

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>
其中有一個id為main_content的FrameLayout,容易猜出這個framelayout就是放fragment的。 重點分析switchToFragment
private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
            boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
        if (validate && !isValidFragment(fragmentName)) {
            throw new IllegalArgumentException("Invalid fragment for this activity: "
                    + fragmentName);
        }
        Fragment f = Fragment.instantiate(this, fragmentName, args);//通過靜態方法例項化一個fragment
        FragmentTransaction transaction = getFragmentManager().beginTransaction();//獲取fragment操作集合
        transaction.replace(R.id.main_content, f);//將原容器中的內容替換為f
        if (withTransition) {
            TransitionManager.beginDelayedTransition(mContent);
        }
        if (addToBackStack) {
        	//在commit()方法之前,你可以呼叫addToBackStack(),把這個transaction加入back stack中去,這樣按返回鍵時會出現被替換的fragment
            transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
        }
        if (titleResId > 0) {
            transaction.setBreadCrumbTitle(titleResId);
        } else if (title != null) {
            transaction.setBreadCrumbTitle(title);
        }
        transaction.commitAllowingStateLoss();
        getFragmentManager().executePendingTransactions();//呼叫commit()方法並不能立即執行transaction中包含的改變動作,commit()方法把transaction加入activity的UI執行緒佇列中。

      // 但是,如果覺得有必要的話,可以呼叫executePendingTransactions()方法來立即執行commit()提供的transaction。
        return f;
    }
和上一篇一樣的,這樣就載入了一個fragment,至此就可以得到中圖所示的效果。

3.點選跳轉的過程分析

點選中圖所示的元件就可以跳轉到具體的設定介面,下面進入該fragment研究其實現原理。

進入oncreate函式:

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
..........
        addPreferencesFromResource(R.xml.display_settings);//載入xml檔案
        mScreenSaverPreference = findPreference(KEY_SCREEN_SAVER);//獲取Preference元件

        mScreenTimeoutPreference = (ListPreference) findPreference(KEY_SCREEN_TIMEOUT);<span style="font-family: Arial, Helvetica, sans-serif;">//獲取ListPreference元件</span>
        final long currentTimeout = Settings.System.getLong(resolver, SCREEN_OFF_TIMEOUT,
                FALLBACK_SCREEN_TIMEOUT_VALUE);
        mScreenTimeoutPreference.setValue(String.valueOf(currentTimeout));
        mScreenTimeoutPreference.setOnPreferenceChangeListener(this);
        disableUnusableTimeouts(mScreenTimeoutPreference);
        updateTimeoutPreferenceDescription(currentTimeout);

        mFontSizePref = (WarnedListPreference) findPreference(KEY_FONT_SIZE);
        mFontSizePref.setOnPreferenceChangeListener(this);
        mFontSizePref.setOnPreferenceClickListener(this);

        if (isAutomaticBrightnessAvailable(getResources())) {
            mAutoBrightnessPreference = (SwitchPreference) findPreference(KEY_AUTO_BRIGHTNESS);
            mAutoBrightnessPreference.setOnPreferenceChangeListener(this);
        } else {
            removePreference(KEY_AUTO_BRIGHTNESS);
        } ........
}
先看載入的佈局檔案,再看獲取的元件
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
        android:title="@string/display_settings"
        settings:keywords="@string/keywords_display">

        <PreferenceScreen
                android:key="brightness"
                android:title="@string/brightness"
                settings:keywords="@string/keywords_display_brightness_level">
            <intent android:action="android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
        </PreferenceScreen>

        <SwitchPreference
                android:key="auto_brightness"
                android:title="@string/auto_brightness_title"
                settings:keywords="@string/keywords_display_auto_brightness"
                android:summary="@string/auto_brightness_summary"
                android:persistent="false" />

        <PreferenceScreen
                android:key="wallpaper"
                android:title="@string/wallpaper_settings_title"
                settings:keywords="@string/keywords_display_wallpaper"
                android:fragment="com.android.settings.WallpaperTypeSettings" />

        <ListPreference
                android:key="screen_timeout"
                android:title="@string/screen_timeout"
                android:summary="@string/screen_timeout_summary"
                android:persistent="false"
                android:entries="@array/screen_timeout_entries"
                android:entryValues="@array/screen_timeout_values" />

        <PreferenceScreen
                android:key="screensaver"
                android:title="@string/screensaver_settings_title"
                android:fragment="com.android.settings.DreamSettings" />
.................
</PreferenceScreen>
結合中圖可以看出每一個PreferenceScreen、ListPreference或者SwitchPreference都是一個設定項在oncreate函式中也取出了每個元件,然後進行設定,設定過程比較簡單。關鍵是確定點選的響應函式按常理還是從三個點入手,activity、fragment和Preference。

先看activity

implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragment.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener,
        SearchView.OnQueryTextListener, SearchView.OnCloseListener,
        MenuItem.OnActionExpandListener 
繼承了很多,關鍵看點選之類的事件,在這看不出來。

看fragment:

public class DisplaySettings extends SettingsPreferenceFragment implements
        Preference.OnPreferenceChangeListener, OnPreferenceClickListener, Indexable
繼承了SettingsPreferenceFragment

public class SettingsPreferenceFragment extends PreferenceFragment implements DialogCreatable

看PreferenceFragment

 /**
     * Interface that PreferenceFragment's containing activity should
     * implement to be able to process preference items that wish to
     * switch to a new fragment.
     */
    public interface OnPreferenceStartFragmentCallback {
        /**
         * Called when the user has clicked on a Preference that has
         * a fragment class name associated with it.  The implementation
         * to should instantiate and switch to an instance of the given
         * fragment.
         */
        boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
    }
英文也比較容易理解,在PreferenceFragment中明確說明 這個介面必須被包含PreferenceFragment的activity實現,才能點選preference items時跳轉到新的fragment。
當點選preference時,他代表的fragment就會被啟動也就是說,在app中也可以實現該功能,只要activity實現了該介面。
    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
        // Override the fragment title for Wallpaper settings
        int titleRes = pref.getTitleRes();
        if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
            titleRes = R.string.wallpaper_settings_fragment_title;
        } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
                && UserHandle.myUserId() != UserHandle.USER_OWNER) {
            if (UserManager.get(this).isLinkedUser()) {
                titleRes = R.string.profile_info_settings_title;
            } else {
                titleRes = R.string.user_info_settings_title;
            }
        }
        startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
                null, 0);
        return true;
    }

    public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
            CharSequence titleText, Fragment resultTo, int resultRequestCode) {
        String title = null;
        if (titleRes < 0) {
            if (titleText != null) {
                title = titleText.toString();
            } else {
                // There not much we can do in that case
                title = "";
            }
        }
        Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode,
                titleRes, title, mIsShortcut);
    }

最後還是呼叫startWithFragment這個過程可以看上面的分析。

至此圖中的三個過程分析結束。