PreferenceActivity给我们封装好了一个数据存储对象,我们只需要在xml文件中写上控件即可完成简单的设置界面。但是系统提供的设置界面十分的简陋,要想做的好看必须要自己来进行修改。本文就是一步一步教大家如何定义自己的PreferenceActivity界面。
一、创建模块一(选择模块组)
先在res/xml文件夹中定义一个customer_prefs.xml文件(名字自定),建立根节点
……
我们的每一个选项组都是一个PreferenceCategory,所以我们在节点中定义这个标签。
我们注意到在PreferenceCategory节点中有android:layout属性,这个属性就是定义这个模块组的头部视图的。在这个layout中的textview的id必须是安卓自己的id,也就是title,这样我们在xml中设置的title就会对应到这个布局的文字上,才能有效!
prefs_category_widget
对应到布局就是->
接下来我们在PreferenceCategory里面写上了我们的控件,这里我们放的是一个checkbox,一个单选list,一个多选List
title:控件上的主文字
defaultValue:默认的值
icon:左边的图标
key:这个控件的key(必须有),系统是通过这个key来存放数据的
layout:这个控件的布局文件
checkbox_prefs_widget.xml
summary:控件的摘要(说明)文字
entries:在单选/多选列表中,显示的选项信息。功能:给用户看
entryValues:在单选/多选列表中,选项信息对应的值。系统最后户把这个控件的key和这个值,以键值对的形式进行存放。
- 白
- 蓝
- 灰
- 胖丁
- 布丁
- 皮卡丘
- white
- blue
- gray
- pangding
- buding
- pikaqiu
第一个模块的全部布局
这里面用到了我自定的两个类,一个是单选菜单,一个是多选菜单。其实我就是继承了原本的类,然后让原本的summary中显示我们选中的值而已。下面是两个类的代码,很简单。
MyListPreference
package com.kale.preference;import android.content.Context;import android.preference.ListPreference;import android.util.AttributeSet;import android.view.View;public class MyListPreference extends ListPreference{ public MyListPreference(Context context, AttributeSet attrs) { super(context, attrs); } public MyListPreference(Context context) { this(context, null); } @Override protected void onBindView(View view) { super.onBindView(view); setSummary(getEntry() == null?getSummary():getEntry()); //setSummary(getEntry()); }}
MyMultiSelectListPreference
package com.kale.preference;import java.util.Set;import android.content.Context;import android.preference.MultiSelectListPreference;import android.util.AttributeSet;import android.view.View;public class MyMultiSelectListPreference extends MultiSelectListPreference { public MyMultiSelectListPreference(Context context) { super(context); } public MyMultiSelectListPreference(Context context, AttributeSet attrs) { super(context, attrs); } /** * @return 列表选中状态的数组 */ private boolean[] getSelectedItems() { final CharSequence[] entries = getEntryValues(); final int entryCount = entries.length; final Setvalues = getValues(); boolean[] result = new boolean[entryCount]; for (int i = 0; i < entryCount; i++) { result[i] = values.contains(entries[i].toString()); } return result; } private void updateSummary() { setSummary(" "); CharSequence[] c = getEntries();// 得到列表值数组 String str = (String) getSummary();// 得到列表选择状态数组 for (int i = 0; i < getSelectedItems().length; i++) { if (getSelectedItems()[i]) { str += (String) c[i] + ","; } } str = str.substring(0, str.length() - 1);// 去除最后一个逗号 // 设置摘要的内容 setSummary(str); } @Override protected void onBindView(View view) { super.onBindView(view); int i; for (i = 0; i < getSelectedItems().length; i++) { if (getSelectedItems()[i] == true) { updateSummary(); break; } } if (i == getSelectedItems().length) { setSummary(""); } } @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); int i; for (i = 0; i < getSelectedItems().length; i++) { if (getSelectedItems()[i] == true) { updateSummary(); break; } } if (i == getSelectedItems().length) { setSummary(""); } }}
于是我们第一个模块组就大功告成了!
--------------------------------------------------------------------------------------------
二、创建模块二(特殊模块组)
这个模块中有两个新的属性
negativeButtonText:设置对话框中取消按钮的文字
positiveButtonText:设置对话框中确定按钮的文字,点击确定后会将值保存下来
通过前面的铺垫,我们现在可以直接看懂以下的代码了
MyEditTextPreference,MySeekBarDialogPreference,MySeekBarPreference都是我自己定义的类,功能还是将所输入的/所选择的值显示到summary中。
MyEditTextPreference
package com.kale.preference;import android.content.Context;import android.preference.EditTextPreference;import android.util.AttributeSet;import android.view.View;public class MyEditTextPreference extends EditTextPreference { public MyEditTextPreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyEditTextPreference(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onBindView(View view) { super.onBindView(view); setSummary(getText() == null?getSummary():getText()); }}
MySeekBarDialogPreference
/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.kale.preference;import android.content.Context;import android.content.SharedPreferences;import android.graphics.drawable.Drawable;import android.preference.DialogPreference;import android.util.AttributeSet;import android.view.View;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.TextView;import android.widget.SeekBar.OnSeekBarChangeListener;import com.kale.shared.R;public class MySeekBarDialogPreference extends DialogPreference implements OnSeekBarChangeListener { //private static final String TAG = "SeekBarDialogPreference"; private Drawable mMyIcon; private TextView value; private SeekBar seekBar; // 如果要修改最大值和最小值的话,那么可以在这里修改。或者是调用函数setMax、setMin private int mMax = 100, mMin = 0; private int mProgress; public MySeekBarDialogPreference(Context context, AttributeSet attrs) { super(context, attrs); setDialogLayoutResource(R.layout.seekbar_dialog_prefs_widget); // Steal the XML dialogIcon attribute's value mMyIcon = getDialogIcon(); setDialogIcon(null); } public MySeekBarDialogPreference(Context context) { this(context, null); } public void setMax(int max) { if (max != mMax) { mMax = max; notifyChanged(); } } public void setMin(int min) { if (min != mMin) { mMin = min; notifyChanged(); } } /** * Saves the progress to the { @link SharedPreferences}. * * @param text * The progress to save */ public void setProgress(int progress) { final boolean wasBlocking = shouldDisableDependents(); mProgress = progress; persistInt(mProgress); final boolean isBlocking = shouldDisableDependents(); if (isBlocking != wasBlocking) { notifyDependencyChange(isBlocking); } } /** * Gets the text from the { @link SharedPreferences}. * * @return The current preference value. */ public int getProgress() { return mProgress; } @Override protected void onBindView(View view) { super.onBindView(view); mProgress = getPersistedInt(getProgress()); setSummary(String.valueOf(mProgress)); } @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon); value = (TextView)view.findViewById(R.id.value); if (mMyIcon != null) { iconView.setImageDrawable(mMyIcon); } else { iconView.setVisibility(View.GONE); } seekBar = getSeekBar(view); seekBar.setMax(mMax-mMin); seekBar.setProgress(mProgress); seekBar.setOnSeekBarChangeListener(this); value.setText(String.valueOf(mProgress)); } @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); // 如果没按下确定按钮就返回null if (!positiveResult) { return; } if (shouldPersist()) { persistInt(mProgress); setSummary(String.valueOf(mProgress)); } // 提交数据 notifyChanged(); } protected static SeekBar getSeekBar(View dialogView) { return (SeekBar) dialogView.findViewById(R.id.seekbar); } @Override public CharSequence getSummary() { String summary = super.getSummary().toString(); int value = getPersistedInt(mProgress); return String.format(summary, value); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { value.setText(String.valueOf(progress)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO 自动生成的方法存根 } @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO 自动生成的方法存根 mProgress = seekBar.getProgress() + mMin; }}
用到的布局文件:seekbar_dialog_prefs_widget.xml
MySeekBarPreference
/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.kale.preference;import android.content.Context;import android.os.Parcel;import android.os.Parcelable;import android.preference.Preference;import android.util.AttributeSet;import android.view.View;import android.widget.SeekBar;import android.widget.SeekBar.OnSeekBarChangeListener;import android.widget.TextView;import com.kale.shared.R;public class MySeekBarPreference extends Preference implements OnSeekBarChangeListener { private TextView value; private int mProgress; private int mMax = 100; private boolean mTrackingTouch; public MySeekBarPreference( Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setMax(mMax); setLayoutResource(R.layout.seekbar_prefs); } public MySeekBarPreference(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySeekBarPreference(Context context) { this(context, null); } @Override protected void onBindView(View view) { super.onBindView(view); SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar); seekBar.setMax(mMax); seekBar.setProgress(mProgress); seekBar.setEnabled(isEnabled()); seekBar.setOnSeekBarChangeListener(this); value = (TextView)view.findViewById(R.id.value); value.setText(String.valueOf(mProgress)); } @Override protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { setProgress(restoreValue ? getPersistedInt(mProgress): (Integer) defaultValue); } public void setMax(int max) { if (max != mMax) { mMax = max; notifyChanged(); } } public void setProgress(int progress) { setProgress(progress, true); } private void setProgress(int progress, boolean notifyChanged) { if (progress > mMax) { progress = mMax; } if (progress < 0) { progress = 0; } if (progress != mProgress) { mProgress = progress; persistInt(progress); if (notifyChanged) { notifyChanged(); } } } public int getProgress() { return mProgress; } /** * Persist the seekBar's progress value if callChangeListener * returns true, otherwise set the seekBar's progress to the stored value */ void syncProgress(SeekBar seekBar) { int progress = seekBar.getProgress(); if (progress != mProgress) { if (callChangeListener(progress)) { setProgress(progress, false); } else { seekBar.setProgress(mProgress); } } } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { System.err.println("now value = "+progress); } if (fromUser && !mTrackingTouch) { syncProgress(seekBar); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { mTrackingTouch = true; } @Override public void onStopTrackingTouch(SeekBar seekBar) { mTrackingTouch = false; if (seekBar.getProgress() != mProgress) { syncProgress(seekBar); value.setText(seekBar.getProgress()+""); } } @Override protected Parcelable onSaveInstanceState() { /* * Suppose a client uses this preference type without persisting. We * must save the instance state so it is able to, for example, survive * orientation changes. */ final Parcelable superState = super.onSaveInstanceState(); if (isPersistent()) { // No need to save instance state since it's persistent return superState; } // Save the instance state final SavedState myState = new SavedState(superState); myState.progress = mProgress; myState.max = mMax; return myState; } @Override protected void onRestoreInstanceState(Parcelable state) { if (!state.getClass().equals(SavedState.class)) { // Didn't save state for us in onSaveInstanceState super.onRestoreInstanceState(state); return; } // Restore the instance state SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); mProgress = myState.progress; mMax = myState.max; notifyChanged(); } /** * SavedState, a subclass of { @link BaseSavedState}, will store the state * of MyPreference, a subclass of Preference. ** It is important to always call through to super methods. */ private static class SavedState extends BaseSavedState { int progress; int max; public SavedState(Parcel source) { super(source); // Restore the click counter progress = source.readInt(); max = source.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); // Save the click counter dest.writeInt(progress); dest.writeInt(max); } public SavedState(Parcelable superState) { super(superState); } }}
所用到的布局文件:seekbar_prefs.xml
于是,我们就完成了第二个模块的设置。
--------------------------------------------------------------------------------------------
三、第三个模块组的(触发模块组)
这个组里面有一个点击触发动作和自定义preference
intent模块中我们设置一个action和data属性,这样点击它后,与之对应的Activity将被启动。这里实现的效果是启动浏览器来展示网页。
我们还自定义了一个preference,用来做一些特殊的操作。这个preference不用写什么自定义类,直接在这个的Activity中找到它既可。
package com.kale.shared;import android.content.Intent;import android.content.SharedPreferences;import android.os.Bundle;import android.preference.Preference;import android.preference.Preference.OnPreferenceClickListener;import android.preference.PreferenceActivity;import android.widget.Toast;public class MainActivity extends PreferenceActivity implements OnPreferenceClickListener { /** 自定义布局 **/ Preference myPrefers = null; @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置背景图,给activity设置后。所有fragment的背景都会改了,十分方便! // getWindow().setBackgroundDrawable(getResources().getDrawable(R.color.bgColor)); // setContentView(R.layout.activity_main); 这里就不能设置布局了 setContentView(R.layout.prefs_list_content); addPreferencesFromResource(R.xml.customer_prefs); // 初始化控件 myPrefers = findPreference("my_prefs_key"); myPrefers.setSummary("可以自定义布局和点击事件"); myPrefers.setOnPreferenceClickListener(this); } @Override public boolean onPreferenceClick(Preference preference) { if (preference == myPrefers) { Toast.makeText(MainActivity.this, "自定义Preference被按下", Toast.LENGTH_SHORT).show(); } return false; } }
现在,这个模块也定义好了。
--------------------------------------------------------------------------------------------
四、子父依赖模块组
这个模块存在一个依赖关系,子控件必须在父控件开启的时候才能进行操作,否则不可用。也就是说只有上面的父控件的开关开启,下面的子控件的开关才能用。
这里的关键属性是:android:dependency="parent_checkbox_preference",设置这个属性的控件表明自己是依托于parent_checkbox_preference这个控件的
--------------------------------------------------------------------------------------------
五、二级菜单模块组
点击任何一个栏目,就会跳到另一个Activity中进行操作,最后系统会将选择的值返回到这个Activity中。
下面这种嵌套产生二级菜单的方式是系统给出的,很简单,但是扩展性很差。↓
下面这个就是一个intent,点击后跳转到指定的Activity中去。是intent的隐式跳转。↓
我定义了这样一个Activity来相应这个intent。
需要注意的是:这里面category节点必须定义,否则会出现找不到action的情况!
下面这种其实就是自定义了preference,点击后跳转到一个Activity中去。↓
MainActivity
package com.kale.shared;import android.content.Intent;import android.content.SharedPreferences;import android.os.Bundle;import android.preference.Preference;import android.preference.Preference.OnPreferenceClickListener;import android.preference.PreferenceActivity;import android.widget.Toast;public class MainActivity extends PreferenceActivity implements OnPreferenceClickListener { /** 自定义布局 **/ Preference myPrefers = null; Preference getValueSingPrefs,getValueMultiPrefs; SharedPreferences sp; SharedPreferences.Editor editor; @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置背景图,给activity设置后。所有fragment的背景都会改了,十分方便! // getWindow().setBackgroundDrawable(getResources().getDrawable(R.color.bgColor)); // setContentView(R.layout.activity_main); 这里就不能设置布局了 sp = getSharedPreferences("kaleShared", MODE_PRIVATE); editor = sp.edit(); editor.putString("KEY", "value"); editor.commit(); if (sp.contains("KEY")) { System.out.println("have a key"); } setContentView(R.layout.prefs_list_content); addPreferencesFromResource(R.xml.customer_prefs); // 初始化控件 initPrefers(); myPrefers.setSummary("可以自定义布局和点击事件"); myPrefers.setOnPreferenceClickListener(this); getValueSingPrefs.setOnPreferenceClickListener(this); getValueMultiPrefs.setOnPreferenceClickListener(this); } @Override public boolean onPreferenceClick(Preference preference) { if (preference == myPrefers) { Toast.makeText(MainActivity.this, "自定义Preference被按下", Toast.LENGTH_SHORT).show(); } else if (preference == getValueSingPrefs) { Intent intent = new Intent(MainActivity.this, SingleActivity.class); startActivityForResult(intent, 100); } else if (preference == getValueMultiPrefs) { Intent intent = new Intent(MainActivity.this, MultiActivity.class); startActivityForResult(intent, 120); } return false; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { //如果返回码表示RESULT_OK,那么说明是从SecondActivity返回的intent if (resultCode == RESULT_OK) { //如果是100表示是返回给启动activity的intent(这里是我们的getValuePreference)的值 switch (requestCode) { case 100: getValueSingPrefs.setSummary(data.getExtras().getString("osNameKey")); break; case 120: getValueMultiPrefs.setSummary(data.getExtras().getString("languageKey")); break; default: break; } } } @SuppressWarnings("deprecation") private void initPrefers() { myPrefers = findPreference("my_prefs_key"); getValueSingPrefs = findPreference("getValue_single_prefers"); getValueMultiPrefs = findPreference("getValue_multi_prefers"); }}
跳转的界面如下:
上面是单选列表,下面是多选列表。我已经写好了一个类,大家只需要找到这个activity中的所有checkboxpreference,然后通过一个方法就能设置为单选组和多选组了。很简单吧~
//添加到单选列表中
addToSingleChoiceList(android,ios,wp); // 添加到多选列表中 addToMultiChoiceList("languageKey",java, c, js);package com.kale.shared;import android.os.Bundle;import android.preference.CheckBoxPreference;import com.kale.preference.ChoicePrefsActivity;/** * @author:Jack Tony * @tips :展现多选列表的界面 * 初始化后将preference添加到多选列表中即可 * addToSingleChoiceList(android,ios,wp); * @date :2014-8-6 */@SuppressWarnings("deprecation")public class MultiActivity extends ChoicePrefsActivity { CheckBoxPreference android,ios,wp; CheckBoxPreference java, c, js; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setBackgroundDrawableResource(R.color.bgColor); setContentView(R.layout.prefs_list_content); addPreferencesFromResource(R.xml.second_level_prefs); initPreference(); //添加到单选列表中 addToSingleChoiceList(android,ios,wp); // 添加到多选列表中 addToMultiChoiceList("languageKey",java, c, js); } private void initPreference() { android = (CheckBoxPreference) findPreference("android_prefs"); ios = (CheckBoxPreference) findPreference("ios_prefs"); wp = (CheckBoxPreference) findPreference("wp_prefs"); java = (CheckBoxPreference) findPreference("java_prefs"); c = (CheckBoxPreference) findPreference("c_prefs"); js = (CheckBoxPreference) findPreference("js_prefs"); }}
在MainActivity中我用onActivityResult()取得在二级菜单中选中的值,并且赋值给summary。
package com.kale.shared;import android.content.Intent;import android.content.SharedPreferences;import android.os.Bundle;import android.preference.Preference;import android.preference.Preference.OnPreferenceClickListener;import android.preference.PreferenceActivity;import android.widget.Toast;public class MainActivity extends PreferenceActivity implements OnPreferenceClickListener { /** 自定义布局 **/ Preference myPrefers = null; Preference getValueSingPrefs,getValueMultiPrefs; SharedPreferences sp; SharedPreferences.Editor editor; @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置背景图,给activity设置后。所有fragment的背景都会改了,十分方便! // getWindow().setBackgroundDrawable(getResources().getDrawable(R.color.bgColor)); // setContentView(R.layout.activity_main); 这里就不能设置布局了 sp = getSharedPreferences("kaleShared", MODE_PRIVATE); editor = sp.edit(); editor.putString("KEY", "value"); editor.commit(); if (sp.contains("KEY")) { System.out.println("have a key"); } setContentView(R.layout.prefs_list_content); addPreferencesFromResource(R.xml.customer_prefs); // 初始化控件 initPrefers(); myPrefers.setSummary("可以自定义布局和点击事件"); myPrefers.setOnPreferenceClickListener(this); getValueSingPrefs.setOnPreferenceClickListener(this); getValueMultiPrefs.setOnPreferenceClickListener(this); } @Override public boolean onPreferenceClick(Preference preference) { if (preference == myPrefers) { Toast.makeText(MainActivity.this, "自定义Preference被按下", Toast.LENGTH_SHORT).show(); } else if (preference == getValueSingPrefs) { Intent intent = new Intent(MainActivity.this, SingleActivity.class); startActivityForResult(intent, 100); } else if (preference == getValueMultiPrefs) { Intent intent = new Intent(MainActivity.this, MultiActivity.class); startActivityForResult(intent, 120); } return false; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { //如果返回码表示RESULT_OK,那么说明是从SecondActivity返回的intent if (resultCode == RESULT_OK) { //如果是100表示是返回给启动activity的intent(这里是我们的getValuePreference)的值 switch (requestCode) { case 100: getValueSingPrefs.setSummary(data.getExtras().getString("osNameKey")); break; case 120: getValueMultiPrefs.setSummary(data.getExtras().getString("languageKey")); break; default: break; } } } @SuppressWarnings("deprecation") private void initPrefers() { myPrefers = findPreference("my_prefs_key"); getValueSingPrefs = findPreference("getValue_single_prefers"); getValueMultiPrefs = findPreference("getValue_multi_prefers"); }}
好啦,主要的讲解就到这里,下面附上源码。
源码下载: