之前發過一篇有關於自定義preference 在ActivityGroup 的包容下出現UI不能更新的問題,當時還以為是Android 的一個BUG 現在想想真可笑 。其實是自己對機制的理解不夠深刻,看來以後要多看看源碼才行。
本篇講述內容大致為如何自定義preference 開始到與ActivityGroup 互用下UI更新的解決方法。
首先從擴展preference開始:
類文件必須繼承自Preference並實現構造函數,這裡我一般實現兩個構造函數分別如下(類名為:test):
public test(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public test(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
這裡第二個構造函數第二個參數為可以使用attrs 為我們自定義的preference 添加擴展的注冊屬性,比如我們如果希望為擴展的preference 添加一個數組引用,就可使用如下代碼:
int resouceId = attrs.getAttributeResourceValue(null, "Entries", 0);
if (resouceId > 0) {
mEntries = getContext().getResources().getTextArray(resouceId);
}
這裡的mEntries 是頭部聲明的一個數組,我們可以在xml文件通過 Entries=數組索引得到一個數組。在這裡不深入為大家示范了。
我們擴展preference 有時想讓其UI更豐富更好看,這裡我們可以通過引用一個layout 文件為其指定UI,可以通過實現如下兩個回調函數:
@Override
protected View onCreateView(ViewGroup parent) {
// TODO Auto-generated method stub
return LayoutInflater.from(getContext()).inflate(
R.layout.preference_screen, parent, false);
}
此回調函數與onBindView 一一對應,並優先執行於onBindView ,當創建完後將得到的VIEW返回出去給onBindView處理,如下代碼:
@Override
protected void onBindView(View view) {
// TODO Auto-generated method stub
super.onBindView(view);
canlendar = Calendar.getInstance();
layout = (RelativeLayout) view.findViewById(R.id.area);
title = (TextView) view.findViewById(R.id.title);
summary = (TextView) view.findViewById(R.id.summary);
layout.setOnClickListener(this);
title.setText(getTitle());
summary.setText(getPersistedString(canlendar.get(Calendar.YEAR) + "/"
+ (canlendar.get(Calendar.MONTH) + 1) + "/"
+ canlendar.get(Calendar.DAY_OF_MONTH)));
}
Tip:onBindView 不是必須的,可以將onBindView 裡的處理代碼在onCreateView 回調函數一並完成然後返回給onBindView ,具體怎麼寫看自己的代碼風格吧。我個人比較喜歡這種寫法,比較明了。
下面我們來了解一下我擴展preference 比較常用到的幾個方法:
- compareTo(Preference another)
與另外一個preference比較,如果相等則返回0,不相等則返回小於0的數字。
- getContext()
獲取上下文
- getEditor()
得到一個SharePrefence 的Editor 對象
- getIntent()
獲取Intetn
- getKey()
獲取當前我們在XML為其注冊的KEY
- getLayoutResource()
得到當前layout 的來源
- getOnPreferenceChangeListener()
值改變的監聽事件
- getPreferenceManager()
獲得一個preference管理
- getSharedPreferences()
通過獲得管理獲取當前的sharePreferences
- getSummary()
獲得當前我們在XML為其注冊的備注為summary 的值。Tip:在OnBindView 或者onCreateView 找到VIEW的時候如果存在summary 的View 對象必須為其設置summary
- getTitle()
如上,不過這裡是獲取標題
- callChangeListener(Object newValue)
如果你希望你擴展的Preference 可以支持當數值改變時候可以調用OnPreferenceChangeListener此監聽方法,則必須調用此方法,查看該方法源碼為:
protected boolean callChangeListener(Object newValue) {
return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
}
源碼簡單不做過多介紹,只是實現一個接口。
- getPersistedBoolean(boolean defaultReturnValue)
獲得一個保存後的布爾值,查看一下源碼:
protected boolean getPersistedBoolean(boolean defaultReturnValue) {
if (!shouldPersist()) {
return defaultReturnValue;
}
return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
}
如果你有接觸過sharePreference 相信一眼就能看出這裡它為我們做了什麼。
- getPersistedFloat(float defaultReturnValue)
如上,這裡獲取一個Float 的值
- getPersistedInt(int defaultReturnValue)
如上,獲取一個int 的值
- getPersistedLong(long defaultReturnValue)
如上,獲取一個Long 型數值
- getPersistedString(String defaultReturnValue)
如上,獲取一個String 數值
- persistBoolean(boolean value)
將一個布爾值保存在sharepreference中,查看一下源碼:
protected boolean persistBoolean(boolean value) {
if (shouldPersist()) {
if (value == getPersistedBoolean(!value)) {
// It's already there, so the same as persisting
return true;
}
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putBoolean(mKey, value);
tryCommit(editor);
return true;
}
return false;
}
都是sharePreference 的知識,這裡不做過多介紹。其他的跟上面的都 一樣,略過。
通過如上的一些設置,一個基本的擴展preference 就己經完成,下面來講講如果在ActivityGroup 裡面讓擴展的preference可以更新UI。之前 農民伯伯 探討過,他建議我使用onContentChanged()方法,可以使UI更新 ,試了一下發現有些許問題,不過非常感謝農民伯伯。這個方法是全局刷新,則全部UI都刷新一次,但是這樣不是很合理,我想了一下,那既然此方法可以更新UI那麼一定可以行得通,我查看一下源碼,下面把源碼貼出來:
@Override
public void onContentChanged() {
super.onContentChanged();
postBindPreferences();
}
/**
* Posts a message to bind the preferences to the list view.
* <p>
* Binding late is preferred as any custom preference types created in
* [email protected] #onCreate(Bundle)} are able to have their views recycled.
*/
private void postBindPreferences() {
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
private void bindPreferences() {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.bind(getListView());
}
}
private static final int MSG_BIND_PREFERENCES = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
}
}
};
原來,這裡它是另開一條線程來更新UI,然後當值發生變化時為其發送消息,在消息隊列裡面處理UI,只不過它這裡繼承了listActivity 更新了一整個listView ,那麼我們就將它提取出來,只更新我們想要的UI則可。OK,思路出來了,下面將我擴展的一個preference 的源碼提供出來:
package com.yaomei.preference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import com.yaomei.set.R;
public class PreferenceScreenExt extends PreferenceGroup implements
OnItemClickListener, DialogInterface.OnDismissListener {
private Dialog dialog;
private TextView title, summary;
private RelativeLayout area;
private ListView listView;
List<Preference> list;
private List<HashMap<String, String>> listStr;
private CharSequence[] mEntries;
private String mValue;
private SimpleAdapter simple;
private static final int MSG_BIND_PREFERENCES = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES:
setValue(mValue);
break;
}
}
};
public PreferenceScreenExt(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.preferenceScreenStyle);
// TODO Auto-generated constructor stub
}
public PreferenceScreenExt(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, android.R.attr.preferenceScreenStyle);
// TODO Auto-generated constructor stub
int resouceId = attrs.getAttributeResourceValue(null, "Entries", 0);
if (resouceId > 0) {
mEntries = getContext().getResources().getTextArray(resouceId);
}
}
@Override
protected void onBindView(View view) {
// TODO Auto-generated method stub
area = (RelativeLayout) view.findViewById(R.id.area);
title = (TextView) view.findViewById(R.id.title);
summary = (TextView) view.findViewById(R.id.summary);
title.setText(getTitle());
summary.setText(getPersistedString(getSummary().toString()));
area.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
showDialog();
}
});
}
@Override
protected View onCreateView(ViewGroup parent) {
// TODO Auto-generated method stu
View view = LayoutInflater.from(getContext()).inflate(
R.layout.preference_screen, parent, false);
return view;
}
public void bindView(ListView listview) {
int length = mEntries.length;
int i = 0;
listStr = new ArrayList<HashMap<String, String>>();
for (i = 0; i < length; i++) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("keyname", mEntries[i].toString());
listStr.add(map);
}
simple = new SimpleAdapter(getContext(), listStr, R.layout.dialog_view,
new String[] { "keyname" }, new int[] { R.id.text });
listview.setAdapter(simple);
listview.setOnItemClickListener(this);
}
public void showDialog() {
listView = new ListView(getContext());
bindView(listView);
dialog = new Dialog(getContext(), android.R.style.Theme_NoTitleBar);
dialog.setContentView(listView);
dialog.setOnDismissListener(this);
dialog.show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// TODO Auto-generated method stub
mValue = listStr.get(position).get("keyname").toString();
persistString(mValue);
callChangeListener(mValue);
dialog.dismiss();
}
@Override
public void onDismiss(DialogInterface dialog) {
// TODO Auto-generated method stub
}
private OnPreferenceChangeListener temp;
public interface OnPreferenceChangeListener {
public boolean onPreferenceChange(Preference preference, Object newValue);
}
public void setOnPreferenceChangeListener(
OnPreferenceChangeListener preference) {
this.temp = preference;
}
public void setValue(String value) {
summary.setText(value);
}
public boolean callChangeListener(Object newValue) {
return temp == null ? true : temp.onPreferenceChange(this, newValue);
}
public void postBindPreferences() {
if (mHandler.hasMessages(MSG_BIND_PREFERENCES))
return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
}
然後在preferenceActivity 界面在回調函數:onPreferenceChange調用postBindPreferences即可更新。
Tip:這裡的onPreferenceChange 調用postBindPreferences 不是必須的,你同樣可以在內部裡面實現,通過執行某一操作發送消息也可。
好了,在這裡我要感謝那幾位朋友對我的幫助,提出了很多寶貴的意見。謝謝。
轉自:http://www.cnblogs.com/TerryBlog/archive/2010/10/07/1845024.html