PreferenceActivity UI 優化修改
雖然PreferenceActivity的UI比較搓,但是由於其良好的封裝性和實用性,所以在一些場景還是有一定的使用價值。所以如何能優化它的UI讓它和你程式相配就十分必要了。畢竟對於程式設計師來說能懶點就懶點,哈哈。
首先,
public abstract class PreferenceActivity extends ListActivity implements PreferenceManager.OnPreferenceTreeClickListener, PreferenceFragment.OnPreferenceStartFragmentCallback
這就表明可以替換背景,可以替換Divider,Selector。
它的adapter是PreferenceGroupAdapter,見於PreferenceScreen:
public void bind(ListView listView) {
listView.setOnItemClickListener(this);
listView.setAdapter(getRootAdapter());
onAttachedToActivity();
}
這個方法在PreferenceActivity裡被掉,用於載入adapter。
package android.preference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.os.Handler; import android.preference.Preference.OnPreferenceChangeInternalListener; import android.view.View; import android.view.ViewGroup; import android.widget.Adapter; import android.widget.BaseAdapter; import android.widget.ListView; /** * An adapter that returns the {@link Preference} contained in this group. * In most cases, this adapter should be the base class for any custom * adapters from {@link Preference#getAdapter()}. * <p> * This adapter obeys the * {@link Preference}'s adapter rule (the * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an * adapter via {@link Preference#getAdapter()}). * <p> * This adapter also propagates data change/invalidated notifications upward. * <p> * This adapter does not include this {@link PreferenceGroup} in the returned * adapter, use {@link PreferenceCategoryAdapter} instead. * * @see PreferenceCategoryAdapter */ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener { private static final String TAG = "PreferenceGroupAdapter"; /** * The group that we are providing data from. */ private PreferenceGroup mPreferenceGroup; /** * Maps a position into this adapter -> {@link Preference}. These * {@link Preference}s don't have to be direct children of this * {@link PreferenceGroup}, they can be grand children or younger) */ private List<Preference> mPreferenceList; /** * List of unique Preference and its subclasses' names. This is used to find * out how many types of views this adapter can return. Once the count is * returned, this cannot be modified (since the ListView only checks the * count once--when the adapter is being set). We will not recycle views for * Preference subclasses seen after the count has been returned. */ private ArrayList<PreferenceLayout> mPreferenceLayouts; private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout(); /** * Blocks the mPreferenceClassNames from being changed anymore. */ private boolean mHasReturnedViewTypeCount = false; private volatile boolean mIsSyncing = false; private Handler mHandler = new Handler(); private Runnable mSyncRunnable = new Runnable() { public void run() { syncMyPreferences(); } }; private static class PreferenceLayout implements Comparable<PreferenceLayout> { private int resId; private int widgetResId; private String name; public int compareTo(PreferenceLayout other) { int compareNames = name.compareTo(other.name); if (compareNames == 0) { if (resId == other.resId) { if (widgetResId == other.widgetResId) { return 0; } else { return widgetResId - other.widgetResId; } } else { return resId - other.resId; } } else { return compareNames; } } } public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) { mPreferenceGroup = preferenceGroup; // If this group gets or loses any children, let us know mPreferenceGroup.setOnPreferenceChangeInternalListener(this); mPreferenceList = new ArrayList<Preference>(); mPreferenceLayouts = new ArrayList<PreferenceLayout>(); syncMyPreferences(); } private void syncMyPreferences() { synchronized(this) { if (mIsSyncing) { return; } mIsSyncing = true; } List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size()); flattenPreferenceGroup(newPreferenceList, mPreferenceGroup); mPreferenceList = newPreferenceList; notifyDataSetChanged(); synchronized(this) { mIsSyncing = false; notifyAll(); } } private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) { // TODO: shouldn't always? group.sortPreferences(); final int groupSize = group.getPreferenceCount(); for (int i = 0; i < groupSize; i++) { final Preference preference = group.getPreference(i); preferences.add(preference); if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) { addPreferenceClassName(preference); } if (preference instanceof PreferenceGroup) { final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference; if (preferenceAsGroup.isOnSameScreenAsChildren()) { flattenPreferenceGroup(preferences, preferenceAsGroup); } } preference.setOnPreferenceChangeInternalListener(this); } } /** * Creates a string that includes the preference name, layout id and widget layout id. * If a particular preference type uses 2 different resources, they will be treated as * different view types. */ private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) { PreferenceLayout pl = in != null? in : new PreferenceLayout(); pl.name = preference.getClass().getName(); pl.resId = preference.getLayoutResource(); pl.widgetResId = preference.getWidgetLayoutResource(); return pl; } private void addPreferenceClassName(Preference preference) { final PreferenceLayout pl = createPreferenceLayout(preference, null); int insertPos = Collections.binarySearch(mPreferenceLayouts, pl); // Only insert if it doesn't exist (when it is negative). if (insertPos < 0) { // Convert to insert index insertPos = insertPos * -1 - 1; mPreferenceLayouts.add(insertPos, pl); } } public int getCount() { return mPreferenceList.size(); } public Preference getItem(int position) { if (position < 0 || position >= getCount()) return null; return mPreferenceList.get(position); } public long getItemId(int position) { if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID; return this.getItem(position).getId(); } public View getView(int position, View convertView, ViewGroup parent) { final Preference preference = this.getItem(position); // Build a PreferenceLayout to compare with known ones that are cacheable. mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); // If it's not one of the cached ones, set the convertView to null so that // the layout gets re-created by the Preference. if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) { convertView = null; } return preference.getView(convertView, parent); } @Override public boolean isEnabled(int position) { if (position < 0 || position >= getCount()) return true; return this.getItem(position).isSelectable(); } @Override public boolean areAllItemsEnabled() { // There should always be a preference group, and these groups are always // disabled return false; } public void onPreferenceChange(Preference preference) { notifyDataSetChanged(); } public void onPreferenceHierarchyChange(Preference preference) { mHandler.removeCallbacks(mSyncRunnable); mHandler.post(mSyncRunnable); } @Override public boolean hasStableIds() { return true; } @Override public int getItemViewType(int position) { if (!mHasReturnedViewTypeCount) { mHasReturnedViewTypeCount = true; } final Preference preference = this.getItem(position); if (preference.hasSpecifiedLayout()) { return IGNORE_ITEM_VIEW_TYPE; } mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout); if (viewType < 0) { // This is a class that was seen after we returned the count, so // don't recycle it. return IGNORE_ITEM_VIEW_TYPE; } else { return viewType; } } @Override public int getViewTypeCount() { if (!mHasReturnedViewTypeCount) { mHasReturnedViewTypeCount = true; } return Math.max(1, mPreferenceLayouts.size()); } }
它的getView裡掉的是Preference的getView。Preference類似於View,是所有相關UI類的基類。以下是和UI相關的重要程式碼。
public View getView(View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = onCreateView(parent);
}
onBindView(convertView);
return convertView;
}
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
final ViewGroup widgetFrame = (ViewGroup) layout
.findViewById(com.android.internal.R.id.widget_frame);
if (widgetFrame != null) {
if (mWidgetLayoutResId != 0) {
layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
} else {
widgetFrame.setVisibility(View.GONE);
}
}
return layout;
}
所以基本上每個Preference UI控制元件最多和mLayoutResId和mWidgetLayoutResId相關。
首先查詢它們的佈局,比如PreferenceCategory,attr為com.android.internal.R.attr.preferenceCategoryStyle,
對應主題中的style為<item name="preferenceCategoryStyle">@android:style/Preference.Category</item>。
<style name="Preference.Category"> <item name="android:layout">@android:layout/preference_category</item> <!-- The title should not dim if the category is disabled, instead only the preference children should dim. --> <item name="android:shouldDisableView">false</item> <item name="android:selectable">false</item> </style>
如下列出所有的attr: <declare-styleable name="Theme"> ...... <attr name="preferenceScreenStyle" format="reference"/> <!-- Default style for PreferenceCategory. --> <attr name="preferenceCategoryStyle" format="reference"/> <!-- Default style for Preference. --> <attr name="preferenceStyle" format="reference"/> <!-- Default style for informational Preference. --> <attr name="preferenceInformationStyle" format="reference"/> <!-- Default style for CheckBoxPreference. --> <attr name="checkBoxPreferenceStyle" format="reference"/> <!-- Default style for YesNoPreference. --> <attr name="yesNoPreferenceStyle" format="reference"/> <!-- Default style for DialogPreference. --> <attr name="dialogPreferenceStyle" format="reference"/> <!-- Default style for EditTextPreference. --> <attr name="editTextPreferenceStyle" format="reference"/> <!-- Default style for RingtonePreference. --> <attr name="ringtonePreferenceStyle" format="reference"/> <!-- The preference layout that has the child/tabbed effect. --> <attr name="preferenceLayoutChild" format="reference"/> </declare-styleable> 對應的style為: <style name="Theme"> ...... <!-- Preference styles --> <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item> <item name="preferenceCategoryStyle">@android:style/Preference.Category</item> <item name="preferenceStyle">@android:style/Preference</item> <item name="preferenceInformationStyle">@android:style/Preference.Information</item> <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item> <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item> <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item> <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item> <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item> <item name="preferenceLayoutChild">@android:layout/preference_child</item> </style>
我沒搞清楚的是這個style是在什麼地方設進去的。所以如果要修改PreferenceCategory的UI,只需從系統原始碼中拷貝出這個佈局XML,修改它的title的id:@+android:id/title為@android:id/title,同時
<PreferenceCategory
android:layout="@layout/preference_category"
android:title="你好1" >
由於PreferenceCategory是直接載入preference_category的,所以替換了預設的preference.xml,所以就算你配上android:widgetLayout也沒用。
修改CheckBoxPreference的UI需要:layout->preference.xml widgetLayout->preference_widget_checkbox.xml,同時修改id。
拿到佈局檔案後,字型,字型大小,顏色等佈局元素你想怎麼弄就怎麼弄了。
需要注意的是,如果你想要儲存那寫選中的資料,必須要對preference UI 控制元件設定key。因為儲存XML時候必須要有key。