Android5.0 Lollipop DisplaySettings分析
阿新 • • 發佈:2019-02-16
1.本文是接接上一篇Setting啟動分析
當點選preference時,他代表的fragment就會被啟動也就是說,在app中也可以實現該功能,只要activity實現了該介面。
最後還是呼叫startWithFragment這個過程可以看上面的分析。
2.本文主要是對DisplaySettings進行分析
1.總概況
本章主要分析點選左圖顯示,顯示中圖,點選互動屏保後跳轉到右圖的過程。
2.載入SettingsActivity
在setting中的選項為都在R.xml.dashboard_categories中,點選後會載入fragment,本次研究的DisplaySettings如下:
可知點選後會呼叫com.android.settings.DisplaySettings這個fragment。但是點選處理的程式碼在哪裡呢,一般情況下可以從SettingsActivity、DashboardSummary等程式碼中找,但是本次點選事件的響應是在DashboardTileView類的onclick函式中。<dashboard-tile android:id="@+id/display_settings" android:title="@string/display_settings" android:fragment="com.android.settings.DisplaySettings" android:icon="@drawable/ic_settings_display" />
很明顯,mTile.fragment是"com.android.settings.DisplaySettings",不是null。進入Utils.startWithFragment()函式。@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); } }
跳轉一下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這個過程可以看上面的分析。
至此圖中的三個過程分析結束。