之前發過一篇有關於自定義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(nullEntries0);
        
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
     * {@link #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(nullEntries0);
        
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 不是必須的,你同樣可以在內部裡面實現,通過執行某一操作傳送訊息也可。

好了,在這裡我要感謝那幾位朋友對我的幫助,提出了很多寶貴的意見。謝謝。