之前發過一篇有關于自定義preference 在ActivityGroup 的包容下出現UI不能更新的問題,當時還以為是Android 的一個BUG 現在想想真可笑 。其實是自己對機制的理解不夠深刻,看來以后要多看看源碼才行。
本篇講述內容大致為如何自定義preference 開始到與ActivityGroup 互用下UI更新的解決方法。
首先從擴展preference開始:
類文件必須繼承自Preference并實現構造函數,這里我一般實現兩個構造函數分別如下(類名為:test):
publictest(Context?context)?{this(context,null);//TODO?Auto-generated?constructor?stub}publictest(Context?context,?AttributeSet?attrs)?{
super(context,?attrs);//TODO?Auto-generated?constructor?stub}
這里第二個構造函數第二個參數為可以使用attrs 為我們自定義的preference 添加擴展的注冊屬性,比如我們如果希望為擴展的preference 添加一個數組引用,就可使用如下代碼:
intresouceId=attrs.getAttributeResourceValue(null,"Entries",0);if(resouceId>0)?{
mEntries=getContext().getResources().getTextArray(resouceId);
}
這里的mEntries 是頭部聲明的一個數組,我們可以在xml文件通過 Entries=數組索引得到一個數組。在這里不深入為大家示范了。
我們擴展preference 有時想讓其UI更豐富更好看,這里我們可以通過引用一個layout 文件為其指定UI,可以通過實現如下兩個回調函數:
@OverrideprotectedView?onCreateView(ViewGroup?parent)?{//TODO?Auto-generated?method?stubreturnLayoutInflater.from(getContext()).inflate(
R.layout.preference_screen,?parent,false);
}
此回調函數與onBindView 一一對應,并優先執行于onBindView ,當創建完后將得到的VIEW返回出去給onBindView處理,如下代碼:
@OverrideprotectedvoidonBindView(View?view)?{//TODO?Auto-generated?method?stubsuper.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 比較常用到的幾個方法:
Preference another)
與另外一個preference比較,如果相等則返回0,不相等則返回小于0的數字。
Object newValue)
如果你希望你擴展的Preference 可以支持當數值改變時候可以調用OnPreferenceChangeListener此監聽方法,則必須調用此方法,查看該方法源碼為:
protectedboolean?callChangeListener(Object?newValue)?{returnmOnChangeListener==null?true:?mOnChangeListener.onPreferenceChange(this,?newValue);
}
源碼簡單不做過多介紹,只是實現一個接口。
protectedboolean?getPersistedBoolean(boolean?defaultReturnValue)?{if(!shouldPersist())?{returndefaultReturnValue;
}returnmPreferenceManager.getSharedPreferences().getBoolean(mKey,?defaultReturnValue);
}如果你有接觸過sharePreference 相信一眼就能看出這里它為我們做了什么。
如上,獲取一個String 數值
protectedboolean?persistBoolean(boolean?value)?{if(shouldPersist())?{if(value==getPersistedBoolean(!value))?{//It's?already?there,?so?the?same?as?persistingreturntrue;
}
SharedPreferences.Editor?editor=mPreferenceManager.getEditor();
editor.putBoolean(mKey,?value);
tryCommit(editor);returntrue;
}returnfalse;
}
都是sharePreference 的知識,這里不做過多介紹。其他的跟上面的都 一樣,略過。
通過如上的一些設置,一個基本的擴展preference 就己經完成,下面來講講如果在ActivityGroup 里面讓擴展的preference可以更新UI。之前 農民伯伯 探討過,他建議我使用onContentChanged()方法,可以使UI更新 ,試了一下發現有些許問題,不過非常感謝農民伯伯。這個方法是全局刷新,則全部UI都刷新一次,但是這樣不是很合理,我想了一下,那既然此方法可以更新UI那么一定可以行得通,我查看一下源碼,下面把源碼貼出來:
@OverridepublicvoidonContentChanged()?{
super.onContentChanged();
postBindPreferences();
}/**
*?Posts?a?message?to?bind?the?preferences?to?the?list?view.
*?
*?Binding?late?is?preferred?as?any?custom?preference?types?created?in
*?{@link?#onCreate(Bundle)}?are?able?to?have?their?views?recycled.*/privatevoidpostBindPreferences()?{if(mHandler.hasMessages(MSG_BIND_PREFERENCES))return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}privatevoidbindPreferences()?{
final?PreferenceScreen?preferenceScreen=getPreferenceScreen();if(preferenceScreen!=null)?{
preferenceScreen.bind(getListView());
}
}
privatestaticfinalintMSG_BIND_PREFERENCES=0;privateHandler?mHandler=newHandler()?{
@OverridepublicvoidhandleMessage(Message?msg)?{switch(msg.what)?{caseMSG_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;publicclassPreferenceScreenExt?extends?PreferenceGroup?implements
OnItemClickListener,?DialogInterface.OnDismissListener?{privateDialog?dialog;privateTextView?title,?summary;privateRelativeLayout?area;privateListView?listView;
Listlist;privateList>listStr;privateCharSequence[]?mEntries;privateString?mValue;privateSimpleAdapter?simple;privatestaticfinalintMSG_BIND_PREFERENCES=0;privateHandler?mHandler=newHandler()?{
@OverridepublicvoidhandleMessage(Message?msg)?{switch(msg.what)?{caseMSG_BIND_PREFERENCES:
setValue(mValue);break;
}
}
};publicPreferenceScreenExt(Context?context,?AttributeSet?attrs)?{this(context,?attrs,?android.R.attr.preferenceScreenStyle);//TODO?Auto-generated?constructor?stub}publicPreferenceScreenExt(Context?context,?AttributeSet?attrs,intdefStyle)?{
super(context,?attrs,?android.R.attr.preferenceScreenStyle);//TODO?Auto-generated?constructor?stubintresouceId=attrs.getAttributeResourceValue(null,"Entries",0);if(resouceId>0)?{
mEntries=getContext().getResources().getTextArray(resouceId);
}
}
@OverrideprotectedvoidonBindView(View?view)?{//TODO?Auto-generated?method?stubarea=(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(newOnClickListener()?{
@OverridepublicvoidonClick(View?v)?{//TODO?Auto-generated?method?stubshowDialog();
}
});
}
@OverrideprotectedView?onCreateView(ViewGroup?parent)?{//TODO?Auto-generated?method?stuView?view=LayoutInflater.from(getContext()).inflate(
R.layout.preference_screen,?parent,false);returnview;
}publicvoidbindView(ListView?listview)?{intlength=mEntries.length;inti=0;
listStr=newArrayList>();for(i=0;?i
HashMapmap=newHashMap();
map.put("keyname",?mEntries[i].toString());
listStr.add(map);
}
simple=newSimpleAdapter(getContext(),?listStr,?R.layout.dialog_view,newString[]?{"keyname"},newint[]?{?R.id.text?});
listview.setAdapter(simple);
listview.setOnItemClickListener(this);
}publicvoidshowDialog()?{
listView=newListView(getContext());
bindView(listView);
dialog=newDialog(getContext(),?android.R.style.Theme_NoTitleBar);
dialog.setContentView(listView);
dialog.setOnDismissListener(this);
dialog.show();
}
@OverridepublicvoidonItemClick(AdapterView>parent,?View?view,intposition,longid)?{//TODO?Auto-generated?method?stubmValue=listStr.get(position).get("keyname").toString();
persistString(mValue);
callChangeListener(mValue);
dialog.dismiss();
}
@OverridepublicvoidonDismiss(DialogInterface?dialog)?{//TODO?Auto-generated?method?stub}privateOnPreferenceChangeListener?temp;publicinterfaceOnPreferenceChangeListener?{publicboolean?onPreferenceChange(Preference?preference,?Object?newValue);
}publicvoidsetOnPreferenceChangeListener(
OnPreferenceChangeListener?preference)?{this.temp=preference;
}publicvoidsetValue(String?value)?{
summary.setText(value);
}publicboolean?callChangeListener(Object?newValue)?{returntemp==null?true:?temp.onPreferenceChange(this,?newValue);
}publicvoidpostBindPreferences()?{if(mHandler.hasMessages(MSG_BIND_PREFERENCES))return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
}
然后在preferenceActivity 界面在回調函數:onPreferenceChange調用postBindPreferences即可更新。
Tip:這里的onPreferenceChange??調用postBindPreferences 不是必須的,你同樣可以在內部里面實現,通過執行某一操作發送消息也可。
好了,在這里我要感謝那幾位朋友對我的幫助,提出了很多寶貴的意見。謝謝。