Android应用Preference相关及源码浅析(Preference组件家族篇)

2-4-1 PreferenceActivity相关属性方法使用基础

翻墙点我查看。PreferenceActivity继承自ListActivity,这个类是Preference相关控件展示的基类,在Android 3.0以前推荐直接使用,3.0以后推荐和preferencefragment一起使用,所以你可以看见PreferenceActivity中有些方法现在已经是过时的了。

首先看下PreferenceActivity加载xml目录下的文件使用的方法,如下:

public class DemoActivity extends PreferenceActivity {

@Override

public void onBuildHeaders(List

target) {

super.onBuildHeaders(target);

//当大于等于3.0版本时推荐重写该方法加载xml,headers+fragments模式

loadHeadersFromResource(R.xml.preference_header, target);

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {

//当小于3.0版本时推荐重写该方法加载xml,当然大于时也可以用,只是不推荐而已

addPreferencesFromResource(R.xml.preference);

}

}

}

如下我们来看看PreferenceActivity相关的常用方法:

| method | description |

| — | — |

| public void addPreferencesFromIntent(Intent intent) | @deprecated,添加一个匹配intent的preferences activity。 |

| public void addPreferencesFromResource(int preferencesResId) | @deprecated,添加一个xml到activity。 |

| public Preference findPreference(CharSequence key) | @deprecated,查找一个指定key的Preference。 |

| public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) | 结束指定的fragment,参数返回类似activity。 |

| public PreferenceManager getPreferenceManager() | @deprecated,获取activity使用的PreferenceManager实例。 |

| public PreferenceScreen getPreferenceScreen() | @deprecated,获取当前activity的根布局视图。 |

| public boolean hasHeaders() | 返回当前activity是否显示了header list。 |

| public void invalidateHeaders() | 刷新已经显示的header list,会重新回调onBuildHeaders()。 |

| public boolean isMultiPane() | 是否同时显示headers和fragment。 |

| public void loadHeadersFromResource(int resid, List target) | 解析一个headers的xml然后添加到target列表里。 |

| public void onBuildHeaders(List target) | 一般需要重写,注意!这个函数可能不是总会被调用,例如,如果该Activity已被要求显示一个特定的Fragment而不需要头文件,就不需要构建Headers,所以不调运。 |

| public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, int titleRes, int shortTitleRes) | 构造一个显示Fragment的Intent对象。 |

| public void onContentChanged() | 当界面发生变化时回调。 |

| public void onHeaderClick(PreferenceActivity.Header header, int position) | 当选择Headers列表项时调用,默认实现调用startwithfragment或switchtoheader。 |

| public boolean onIsMultiPane() | 大屏下默认实现是true。 |

| public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) | 当单击某个具有与它相关联的gragment类名称时调用。 |

| public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) | @deprecated,当Preference控件被点击时,触发该方法。参数preference为点击的对象,返回值true代表点击事件已成功捕捉,无须执行默认动作或者返回上层调用,例如,不跳转至默认Intent。 |

| public void setListFooter(View view) | 给Headers list设置foot view。 |

| public void startPreferenceFragment(Fragment fragment, boolean push) | 起一个fragment,push决定是否入栈。 |

| public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode) | 依据是否multi-pane模式启动一个preference的fragment(如果是小屏会重启一个activity显示)。 |

| public void startWithFragment(……) | 启动一个新的fragment。 |

| public void switchToHeader(……) | 在大屏multi-pane模式下切换到fragment显示给定参数的fragment。 |

2-4-2 PreferenceFragment相关属性方法使用基础

翻墙点我查看。PreferenceFragment继承自Fragment,这个类是3.0以后推荐使用的,用来处理碎片化问题。

该类的常用方法和上面PreferenceActivity的介绍差不多,这里不再详细说明,只是PreferenceActivity的@deprecated方法在PreferenceFragment中不是@deprecated的而已。

2-4-3 PreferenceManager相关方法使用基础

翻墙点我查看。PreferenceManager继承自Object,这个类其实我们前一篇《Android应用Preference相关及源码浅析(SharePreferences篇)》获取Preference实例就该说明的,这里才说而已。

Android中得到SharedPreference的方式有四种:

  • ContextWrapper.getSharedPreferences(String name, int mode)

可以自己设置SharedPreference的名字与模式。

  • Activity.getPreferences(int mode)

name是Activity名字,不能设置。

  • PreferenceManager.getSharedPreferences()

通过PreferenceManager维护一个SharedPreference,我们可以调用PreferenceManager的API来设置name和mode,并且最终也是调用到ContextWrapper的getSharedPreferences。

  • PreferenceManager.getDefaultSharedPreferences(Context context)

得到的SharedPreference是某个包名下共享私有的,不能让其他的包访问,而且name和mode不能设置,最终也会调用到ContextWrapper的getSharedPreferences。

接下来简单看下PreferenceManager相关方法,如下:

| method | description |

| — | — |

| PreferenceManager.OnActivityDestroyListener | 当所依赖的activity销毁时回调接口。 |

| PreferenceManager.OnActivityResultListener | 当所依赖的activity得到返回result时回调接口。 |

| PreferenceManager.OnActivityStopListener | 当所依赖的activity停止时回调接口。 |

| public Preference findPreference(CharSequence key) | 通过key找到Preference。 |

| public static SharedPreferences getDefaultSharedPreferences(Context context) | 每个应用有一个默认的preferences文件,通过该方法获取。 |

| public SharedPreferences getSharedPreferences() | 通过PreferenceManager维护一个SharedPreference,可以调用PreferenceManager的API来设置name和mode。 |

| public int getSharedPreferencesMode() | 获取当前的mode。 |

| public String getSharedPreferencesName() | 获取当前的name。 |

| public static void setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain) | 更加灵活的设置默认值,注意readAgain参数。 |

| public static void setDefaultValues(Context context, int resId, boolean readAgain) | 设置默认值,注意readAgain参数。 |

| public void setSharedPreferencesMode(int sharedPreferencesMode) | 设置当前的mode。 |

| public void setSharedPreferencesName(String sharedPreferencesName) | 设置当前的name。 |

可以看见,这个类其实也没啥介绍的,重点关注下setDefaultValues的几个核心参数就行。如果我们的设置项很多,而且每项在代码中都需要设置默认缺省值,那就推荐使用setDefaultValues方法。在应用第一次运行时,从preference的xml中获取缺省值,并生成文件保存(如果已经有一个SharedPrefferences对象,也会进行更新,就像下面代码中三四行对调);不是第一运行就不会改现有保存值。

protected void onCreate(Bundle savedInstanceState) {

PreferenceManager.setDefaultValues(this, R.xml.default_value, false);

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

String option = prefs.getString(“key”, null);

}

好了,控件使用就到这里了。

2-5 Preference控件家族实例

关于Preference控件家族的使用比较简单,自定义网上也一大把,所以不再给出例子。如果你想看例子可以参考如下:

其他的相关用法参考API及网络例子。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

3 Preference组件源码设计简单分析


扯蛋了这么多,唉,叹个气继续吧,接下来就到了有意思的环节,源码结构简介。这里只是针对Preference控件特性介绍分析,不会过多追究View及Activity和Fragment细节,具体View及Activity和Fragment细节后面会写文章分析的。

3-1 PreferenceFragment源码浅析

首先还记得上面基础说了,PreferenceFragment使用第一步就是使用其内部方法addPreferencesFromResource或者addPreferencesFromIntent设置源。所以这里我们以addPreferencesFromResource为例来说明,如下源码:

//PreferenceFragment的方法

public void addPreferencesFromResource(int preferencesResId) {

//判断异常说明了该方法至少得在super.onCreate方法之后调运,以便初始化PreferenceManager

requirePreferenceManager();

//这个前面也介绍过的,设置根布局PreferenceScreen

setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),

preferencesResId, getPreferenceScreen()));

}

接着我们看下setPreferenceScreen方法源码,如下:

public void setPreferenceScreen(PreferenceScreen preferenceScreen) {

//设置根布局到PreferenceManager里

if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {

//空方法

onUnbindPreferences();

//设置标记,在onActivityCreated方法中有用

mHavePrefs = true;

//决定是否重设bind布局,核心都是为了执行bindPreferences方法

if (mInitDone) {

postBindPreferences();

}

}

}

到此接下来就是bind了,至于在这里通过Handler发消息bindPreferences还是在onActivityCreated自动调bindPreferences方法取决于你把addPreferencesFromResource方法写在那个生命周期方法里。如下我们直接来看bindPreferences方法,如下源码:

//这个方法是搭建显示的核心方法!!!!!!!!!

private void bindPreferences() {

//拿到PreferenceManager中存的根视图PreferenceScreen

final PreferenceScreen preferenceScreen = getPreferenceScreen();

if (preferenceScreen != null) {

//传递当前ListView到preferenceScreen的bind方法

preferenceScreen.bind(getListView());

}

//PreferenceFragment的空方法

onBindPreferences();

}

到此可以看见PreferenceFragment里bind最终是交给了PreferenceScreen的bind来关联PreferenceFragment的ListView与PreferenceScreen的ListAdapter。我们现在就来看下PreferenceScreen的bind源码,如下:

//PreferenceScreen类的方法

public void bind(ListView listView) {

//设置listview的item监听

listView.setOnItemClickListener(this);

//PreferenceScreen中bind的重点核心!!!!!!!!!!!!!给listview设置adapter

listView.setAdapter(getRootAdapter());

//一些register操作,忽略

onAttachedToActivity();

}

好了,我们还是来关注这个adapter咋来的吧,如下就是getRootAdapter方法源码:

public ListAdapter getRootAdapter() {

if (mRootAdapter == null) {

mRootAdapter = onCreateRootAdapter();

}

return mRootAdapter;

}

protected ListAdapter onCreateRootAdapter() {

return new PreferenceGroupAdapter(this);

}

终于真相快要大白了,PreferenceFragment的listview设置的adapter原来是PreferenceGroupAdapter。哈哈,我们继续来看看这个类,如下:

//hide类,专门用来Preference的list显示的adapter

public class PreferenceGroupAdapter extends BaseAdapter

implements OnPreferenceChangeInternalListener {

//省略相关属性定义

//构造方法,传入的是PreferenceScreen根布局

public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {

//sync设置相关list列表数据后通知listview刷新

syncMyPreferences();

}

private void syncMyPreferences() {

//通知listview刷新当前准备的Preference列表

notifyDataSetChanged();

}

//省略一堆方法

//notifyDataSetChanged后和普通adapter一样item绘制会回调getView方法

public View getView(int position, View convertView, ViewGroup parent) {

//拿到当前item的Preference组件

final Preference preference = this.getItem(position);

//调运Preference的getView方法得到当前item真正的view显示,这是核心!!!!!!!!!!!!

//关于Preference的getView方法下面分析Preference源码会说到的,或者你可以直接跳到Preference源码分析部分查看。

View result = preference.getView(convertView, parent);

return result;

}

}

到此你会发现,其实无非就是ListView和Adapter的关系,而Adapter的getView所得到的View由Preference提供而已,而Adapter由PreferenceScreen管理而已。

3-2 PreferenceActivity源码浅析

说到PreferenceActivity现在不推荐的addPreferencesFromResource方法时其实是没啥解释的,这种模式现在被官方推荐通过PreferenceFragment的addPreferencesFromResource来实现,所以也就是说关于PreferenceActivity的addPreferencesFromResource方法(也就是在PreferenceActivity中直接添加Preference组件)其显示原理和上面分析的PreferenceFragment是一样的,所以这里就不再过多解释了。

我们把重点放在loadHeadersFromResource方法上,也就是现在推荐的PreferenceActivity放置Headers模式。接下来就来分析分析吧。

public abstract class PreferenceActivity extends ListActivity implements

PreferenceManager.OnPreferenceTreeClickListener,

PreferenceFragment.OnPreferenceStartFragmentCallback {

//省略一堆方法

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//设置基础布局

setContentView(com.android.internal.R.layout.preference_list_content);

//获取一些ContentView里的控件实例

//判断是啥模式,左右展示还是单页

boolean hidingHeaders = onIsHidingHeaders();

mSinglePane = hidingHeaders || !onIsMultiPane();

//获取fragment参数(其实是PreferenceActivity中点击Header item重启PreferenceActivity时传递的)

String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);

Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);

int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);

int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);

if (savedInstanceState != null) {

… //忽略,非重点主线

} else {

if (initialFragment != null && mSinglePane) {

//SinglePane时有参数则替换显示Fragment

switchToHeader(initialFragment, initialArguments);

} else {

//核心方法之一!!!!!!!!!!!!

//记得上面基础使用介绍过吗?新的实现重写onBuildHeaders空方法,在其中

//调运loadHeadersFromResource方法加载header list xml文件

onBuildHeaders(mHeaders);

//如果存在header list则走这里(上面onBuildHeaders里会组织生成mHeaders的list结构)

if (mHeaders.size() > 0) {

//header-fragment左右各半屏模式

if (!mSinglePane) {

if (initialFragment == null) {

//设置显示header

Header h = onGetInitialHeader();

switchToHeader(h);

} else {

//设置显示header及fragment

switchToHeader(initialFragment, initialArguments);

}

}

}

}

}

if (initialFragment != null && mSinglePane) {

//当SinglePane加载的是Fragment时隐藏header,显示fragment

findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);

mPrefsContainer.setVisibility(View.VISIBLE);

} else if (mHeaders.size() > 0) {

//重点!!!!!!!!!!!!!!!!!这就是要分析的header的listview的adapter放置地

setListAdapter(new HeaderAdapter(this, mHeaders));

} else {

//这就是最原始的供已经不推荐的addPreferencesFromResource方式加载Preference组件了

//具体原理同上PreferenceFragment的加载显示原理了,不再分析

setContentView(com.android.internal.R.layout.preference_list_content_single);

}

//其他初始设置

}

}

通过上面的分析可以看见其实对于Header的adapter核心就是setListAdapter(new HeaderAdapter(this, mHeaders));这句代码。那我们就来看看这个内部类HeaderAdapter,源码如下:

//可以发现PreferenceActivity的内部类HeaderAdapter是继承自ArrayAdapter的,

//这个Adapter就是用来给推荐的Header list的listview提供数据的。

private static class HeaderAdapter extends ArrayAdapter

{

//Holder里只有最典型经典的三个组件

private static class HeaderViewHolder {

ImageView icon;

TextView title;

TextView summary;

}

private LayoutInflater mInflater;

//构造方法,不解释

public HeaderAdapter(Context context, List

objects) {

super(context, 0, objects);

mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

}

//最最核心方法!!!!!!Header list被显示到PreferenceActivity的listview关键点

@Override

public View getView(int position, View convertView, ViewGroup parent) {

HeaderViewHolder holder;

View view;

//再常见不过的Adapter数据加载ViewHolder写法了

if (convertView == null) {

//加载header的item布局,都是用的preference_header_item文件,如下会介绍

view = mInflater.inflate(com.android.internal.R.layout.preference_header_item,

parent, false);

holder = new HeaderViewHolder();

holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);

holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);

holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);

view.setTag(holder);

} else {

view = convertView;

holder = (HeaderViewHolder) view.getTag();

}

//一堆显示,通过getItem(position)拿到构造里传入的List

类型objects的item

// All view fields must be updated every time, because the view may be recycled

Header header = getItem(position);

holder.icon.setImageResource(header.iconRes);

holder.title.setText(header.getTitle(getContext().getResources()));

CharSequence summary = header.getSummary(getContext().getResources());

if (!TextUtils.isEmpty(summary)) {

holder.summary.setVisibility(View.VISIBLE);

holder.summary.setText(summary);

} else {

holder.summary.setVisibility(View.GONE);

}

return view;

}

}

可以看见这个adapter的getView中的item核心是加载了一个preference_header_item的xml文件,然后设置作为item的header。这个xml源码如下:

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:minHeight=“48dp”

android:background=“?android:attr/activatedBackgroundIndicator”

android:gravity=“center_vertical”

android:paddingRight=“?android:attr/scrollbarSize”>

<ImageView

android:id=“@+id/icon”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginLeft=“6dip”

android:layout_marginRight=“6dip”

android:layout_gravity=“center” />

<RelativeLayout

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginLeft=“2dip”

android:layout_marginRight=“6dip”

android:layout_marginTop=“6dip”

android:layout_marginBottom=“6dip”

android:layout_weight=“1”>

<TextView android:id=“@+android:id/title”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:singleLine=“true”

android:textAppearance=“?android:attr/textAppearanceMedium”

android:ellipsize=“marquee”

android:fadingEdge=“horizontal” />

<TextView android:id=“@+android:id/summary”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_below=“@android:id/title”

android:layout_alignLeft=“@android:id/title”

android:textAppearance=“?android:attr/textAppearanceSmall”

android:ellipsize=“end”

android:maxLines=“2” />

哈哈,到此就不在解释啥了,很直观了,就是这么任性,就是这么简单的实现了Header List的显示。

3-3 Preference源码浅析

说这个的原因是上面PreferenceFragemnt分析加载设置adapter的getView方法时留下的历史问题。我们先来看看这个文件的核心代码,后面总结串起来你就明白了,如下源码:

//可以看见,他不是一个View,但是组合管理了一个View和PreferenceManager

public class Preference implements Comparable {

//各种属性

private PreferenceManager mPreferenceManager;

//重点关注,和自定义及Preference显示原理息息相关,preference就是下面列出的xml资源

private int mLayoutResId = com.android.internal.R.layout.preference;

private int mWidgetLayoutResId;

//各种getXXX及setXXX方法

/**

  • Gets the View that will be shown in the {@link PreferenceActivity}.

  • 获取Preference的item显示view

*/

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) {

//mWidgetLayoutResId有专门的set方法可以设置或者重写

if (mWidgetLayoutResId != 0) {

//android:id/widget_frame为mWidgetLayoutResId所对应的布局预留空间插入

layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);

} else {

//默认实现是null的

widgetFrame.setVisibility(View.GONE);

}

}

return layout;

}

/**

  • Binds the created View to the data for this Preference.

  • This is a good place to grab references to custom Views in the layout and

  • set properties on them.

*/

protected void onBindView(View view) {

//设置子View相关属性

final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title);

if (titleView != null) {

final CharSequence title = getTitle();

if (!TextUtils.isEmpty(title)) {

titleView.setText(title);

titleView.setVisibility(View.VISIBLE);

} else {

titleView.setVisibility(View.GONE);

}

}

//类似的各种子View设置操作,不再列出

}

}

可以看见,这个getView其实就是上面PreferenceFragment分析中Adapter中getView调运的Preference的getView。怎么样,串起来吧。也就是说Preference不是View,但是他提供View给ListView的每一个Item显示,其提供的View的基类布局(上面Preference类中mLayoutResId属性的值)如下:

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:minHeight=“?android:attr/listPreferredItemHeight”

android:gravity=“center_vertical”

android:paddingEnd=“?android:attr/scrollbarSize”

android:background=“?android:attr/selectableItemBackground” >

<ImageView

android:id=“@+android:id/icon”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center”

/>

<RelativeLayout

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginStart=“15dip”

android:layout_marginEnd=“6dip”

android:layout_marginTop=“6dip”

android:layout_marginBottom=“6dip”

android:layout_weight=“1”>

<TextView android:id=“@+android:id/title”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:singleLine=“true”

android:textAppearance=“?android:attr/textAppearanceLarge”

android:ellipsize=“marquee”

android:fadingEdge=“horizontal” />

<TextView android:id=“@+android:id/summary”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_below=“@android:id/title”

android:layout_alignStart=“@android:id/title”

android:textAppearance=“?android:attr/textAppearanceSmall”

android:textColor=“?android:attr/textColorSecondary”

android:maxLines=“4” />

<LinearLayout android:id=“@+android:id/widget_frame”

android:layout_width=“wrap_content”

android:layout_height=“match_parent”

android:gravity=“center_vertical”

android:orientation=“vertical” />

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值