CheckPreference类似常见控件的CheckBox,一个item,右侧有一个CheckBox,用于通过SharePreferences存储操作的设置值,。
如下是CheckPreference的相关属性介绍:
| attr | description |
| — | — |
| android:disableDependentsState | 与android:dependency相反;B可用,则A不可用;B不可用,则A可用。 |
| android:summaryOff | 选项未选中时显示的摘要。 |
| android:summaryOn | 选项被选中时显示的摘要。 |
EditTextPreference类似常见控件的EditText,一个item,点击弹出一个EditText的对话框,用于通过SharePreferences存储操作的设置值,。
该控件无自有属性。具体不再介绍,相关方法查看官方API。
ListPreference类似常见控件的ListView,一个item,点击弹出一个ListView的Dialog,用于通过SharePreferences存储操作的设置值,。
如下是ListPreference的相关属性介绍:
| attr | description |
| — | — |
| android:entries | list要显示的item数组名字。 |
| android:entryValues | list要显示的item数组值。 |
MultiSelectListPreference类似常见控件的ListView,一个item,点击弹出一个多选的ListView的Dialog,用于通过SharePreferences存储操作的设置值,。
MultiSelectListPreference的相关属性同上ListPreference。
SwitchPreference类似常见控件的Switch,一个item,右侧有一个Switch控件,用于通过SharePreferences存储操作的设置值,。
如下是SwitchPreference的相关属性介绍:
| attr | description |
| — | — |
| android:disableDependentsState | 与android:dependency相反;B可用,则A不可用;B不可用,则A可用。 |
| android:summaryOff | 选项未选中时显示的摘要。 |
| android:summaryOn | 选项被选中时显示的摘要。 |
| android:switchTextOff | 关闭状态的文字提示。 |
| android:switchTextOn | 打开状态的文字提示。 |
RingtonePreference就是一个铃声选择item,点击弹出铃声选择list的dialog,用于通过SharePreferences存储操作的设置值,。
如下是RingtonePreference的相关属性介绍:
| attr | description |
| — | — |
| android:ringtoneType | 铃声类型。ringtone/notification/alarm/all
|
| android:showDefault | 选项中默认的铃声。 |
| android:showSilent | 是否显示静音项。 |
PreferenceScreen就Preference hierarchy的root节点,实例化他可以使用createPreferenceScreen(Context)方法;这个类可以依附于两个地方,当一个preferenceactivity指向他时用来作为根布局显示偏好,当他嵌套出现在另一个Preference hierarchy内部时他会启动一个新的界面来显示子项Preference或者设置的intent;综上也就是说它不仅可以作为设置界面显示,而且还能够启动activity,。
如下展示了作为根布局及子布局的两种情况:
<–! 作为根及子项展示设置界面>
<PreferenceScreen
xmlns:android=“http://schemas.android.com/apk/res/android”
android:key=“first_preferencescreen”>
<CheckBoxPreference
android:key=“wifi enabled”
android:title=“WiFi” />
<PreferenceScreen
android:key=“second_preferencescreen”
android:title=“WiFi settings”>
<CheckBoxPreference
android:key=“prefer wifi”
android:title=“Prefer WiFi” />
… other preferences here …
<–! 内嵌intent的模式>
<intent
android:targetPackage=“com.test.main”
android:targetClass=“com.test.main.activity”
PreferenceCategory类似于LinearLayout,用于组合一组可设置标题的Preference,使布局更具备层次感,。
这个类也没有啥特殊的东西介绍,详细参考API。
到此常用的Preference组件xml属性介绍完毕,对应的java方法就不再说明了,还有就是他们的protect方法也不再详细介绍,具体参见API。
上面我们简单介绍了PreferenceScreen相关xml的属性,这些其实是老版本的处理方式;自从Android 3.0引入Fragment之后,Preference相关的控件也有了变化。
由于PreferenceActivity在3.0开始也需要能够处理多屏幕碎片化问题,所以Android 3.0之前采用PreferenceScreen嵌套的方法来跳转分类细则,而Android 3.0及之后使用了Preference Headers的方法来适配多屏幕碎片化问题。
他的核心就是在主屏中通过headers的xml布局列出所有的主题设置项,每个主题设置的详细设置由各自指定的PreferenceFragment负责,而各自的PreferenceFragment可以如传统的PreferenceActivity 一样布局自身的PreferenceScreen。
preference-headers就是他们的root,既然这样,那我们就来看看Headers相关的组件及方法吧。
。Header继承自Object,实现了Parcelable,用来展示一个item的header。
相关属性如下:
| attr | description |
| — | — |
| android:icon | |
| android:breadCrumbShortTitle | 在fragment显示的短标题文字。 |
| android:breadCrumbTitle | 在fragment显示的标题文字。 |
| android:fragment | 当选择该头文件时,将显示该fragment的全名称。 |
| android:id | 唯一识别id。 |
| android:summary | item描述信息。 |
| android:title | item头名称。 |
如下是一个简单展示:
<?xml version="1.0" encoding="utf-8"?>android:summary=“test 1”
android:title=“Name1”>
…
android:summary=“test”
android:title=“Name” >
<extra
android:name=“type”
android:value=“first” />
…
关于Header的用法下面会详细演示,基本情况就介绍到这里。
有了上面Preference组件基本概念及属性介绍以后就相当于我们有了砖瓦,接下来就是咋盖房子了,也就是如何组合这些组件显示在屏幕上,我们现在就来看看这些常用的操作。
。PreferenceActivity继承自ListActivity,这个类是Preference相关控件展示的基类,在Android 3.0以前推荐直接使用,3.0以后推荐和preferencefragment一起使用,所以你可以看见PreferenceActivity中有些方法现在已经是过时的了。
public class DemoActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List
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。 |
。PreferenceFragment继承自Fragment,这个类是3.0以后推荐使用的,用来处理碎片化问题。
该类的常用方法和上面PreferenceActivity的介绍差不多,这里不再详细说明,只是PreferenceActivity的@deprecated方法在PreferenceFragment中不是@deprecated的而已。
。PreferenceManager继承自Object,这个类其实我们前一篇获取Preference实例就该说明的,这里才说而已。
Android中得到SharedPreference的方式有四种:
可以自己设置SharedPreference的名字与模式。
name是Activity名字,不能设置。
通过PreferenceManager维护一个SharedPreference,我们可以调用PreferenceManager的API来设置name和mode,并且最终也是调用到ContextWrapper的getSharedPreferences。
得到的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);
}
好了,控件使用就到这里了。
关于Preference控件家族的使用比较简单,自定义网上也一大把,所以不再给出例子。如果你想看例子可以参考如下:
Settings源码。
其他的相关用法参考API及网络例子。
【工匠若水 转载请注明出处。】
3 Preference组件源码设计简单分析
扯蛋了这么多,唉,叹个气继续吧,接下来就到了有意思的环节,源码结构简介。这里只是针对Preference控件特性介绍分析,不会过多追究View及Activity和Fragment细节,具体View及Activity和Fragment细节后面会写文章分析的。
首先还记得上面基础说了,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管理而已。
说到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
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
// 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” />
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
因篇幅问题不能全部显示,请点此查看更多更全内容