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这个过程可以看上面的分析。

至此图中的三个过程分析结束。






















猜你喜欢

转载自blog.csdn.net/ywlyg/article/details/50152707