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。


猜你喜欢

转载自f059074251.iteye.com/blog/1672151
今日推荐