自己實現notifyDatasetChanged,notifydatasetchanged
今天這篇部落格,我們來實現一下adapter那個最常用的notifyDatasetChanged功能,我們利用一個繼承一個LinearLayout來實現一個可能在日常工作中很常用的功能。
大家在工作中可能經常遇到這樣的功能:
需要定義一個列表來展示菜單,但是這個菜單並不一定適合ListView,然後,我們可能就通過一個LinearLayout來實現。
如何讓我們的LinearLayout使用起來更像ListView呢? 那就是設定Adapter。那你的代碼可能是這樣的。
擴充LinearLayout:
public class MyLinearLayout extends LinearLayout { private Adapter mAdapter; public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(VERTICAL); } public void setAdapter(Adapter adapter) { removeAllViews(); mAdapter = adapter; int itemCount = mAdapter.getItemCount(); View child; for (int i = 0; i < itemCount; i++) { child = mAdapter.getView(this, i); addView(child); } }}
自己動手實現一個簡單的Adapter:
public abstract class Adapter { public abstract int getItemCount(); public abstract View getView(View parent, int position);}
在我們的代碼如何使用呢?
首先繼承咱們定義的Adapter
public class MyAdapter extends Adapter { private ArrayList<String> mData; public MyAdapter(ArrayList<String> data) { mData = data; } @Override public int getItemCount() { return mData.size(); } @Override public View getView(View parent, int position) { TextView textView = new TextView(parent.getContext()); textView.setText(mData.get(position)); return textView; }}
給我們的LinearLayout設定Adapter。
mLinearLayout = (MyLinearLayout) findViewById(R.id.linear);mAdapter = new MyAdapter(mData);mLinearLayout.setAdapter(mAdapter);
這些都是最基礎的代碼,可能很多朋友在工作中也會有這麼辦的時候,所以,我們只是展示一下代碼,至於效果,肯定就是一個普通的豎向的LinearLayout。這裡也不再多提。
現在,我們來假設這樣的一個情形,假如我們的資料是變化的,上面的代碼該如何去做呢?
1. 手動調用LinearLayout.getChildAt(index)去設定資料。
2. 重新設定adapter,可以看到在MyLinearLayout.setAdapter開始,我們調用了removeAllViews()。
對於第一種方法,我們感覺有悖於我們的封裝,每次資料變化都要手動去更新item;而第二種方式肯定會影響效能,畢竟每次都重新添加view,感覺這肯定是沒必要的。
那麼我們該怎麼辦呢? 看看android的adapter是如何?的,我們只需要調用notifyDatasetChanged就ok。
在開始實現notifyDatasetChanged之前,我們來引入一個概念,那就是觀察者模式,java本身提供了觀察者模式的實現,但是使用起來有點小麻煩,所以android SDK給我們提供了另一種方式:DataSetObserver。DataSetObserver其實就是一個簡單的抽象類別,在原理上也很簡單,可以這麼說,我們完全可以自己定義一個DataSetObserver。但是我們還是選擇使用原生的DataSetObserver,畢竟android已經給我們提供好了。
這裡需要說明一下,代碼不具備任何價值,只有參考意義,而且,我們代碼有很大的局限性,因為我們只考慮了TextView。
先來看看我們的Adapter吧,在上面的基礎上加了幾行代碼:
public abstract class Adapter { private DataSetObserver mDataSetObserver; public void registerObserver(DataSetObserver observer) { mDataSetObserver = observer; } public void unregisterObserver() { mDataSetObserver = null; } public void notifyDataSetChanged() { if(mDataSetObserver != null) mDataSetObserver.onChanged(); } public void notifyDataSetInvalidate() { if(mDataSetObserver != null) mDataSetObserver.onInvalidated(); } public abstract String getItem(int position); public abstract int getItemCount(); public abstract View getView(View parent, int position);}
增加了一個DataSetObserver變數,對應的,定義了兩個方法來向這個DataSetObserver賦值,而且,我們新增了一個abstract方法
public abstract String getItem(int position);
最主要的,我們來看看兩個最重要的方法:
public void notifyDataSetChanged() { if(mDataSetObserver != null) mDataSetObserver.onChanged(); } public void notifyDataSetInvalidate() { if(mDataSetObserver != null) mDataSetObserver.onInvalidated(); }
notifyDataSetChanged方法,我們直接調用了DataSetObserver.onChanged(),invalidate也是一樣,這裡我們就需要關心一下這個DataSetObserver是從哪註冊進來的。
下面公布經過修改過的MyLinearLayout:
public class MyLinearLayout extends LinearLayout { private Adapter mAdapter; public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(VERTICAL); } private DataSetObserver mObserver = new DataSetObserver() { public void onChanged() { onChange(); } public void onInvalidated() { removeAllViews(); } }; private void onChange() { int childCount = getChildCount(); int itemCount = mAdapter.getItemCount(); // 如果資料變少了,則移出多餘的view if(childCount > itemCount) removeViews(itemCount, childCount - itemCount); // 現在view的個數 <= 資料的個數 TextView child; for (int i = 0; i < itemCount; i++) { if(i < childCount) { child = (TextView) getChildAt(i); child.setText(mAdapter.getItem(i)); }else { child = (TextView) mAdapter.getView(this, i); addView(child); } } } public void setAdapter(Adapter adapter) { removeAllViews(); mAdapter = adapter; mAdapter.registerObserver(mObserver); int itemCount = mAdapter.getItemCount(); View child; for (int i = 0; i < itemCount; i++) { child = mAdapter.getView(this, i); addView(child); } }}
在MyLinearLayout中定義了一個DataSetObserver,並且在setAdapter中向Adapter註冊這個DataSetObserver。最主要的代碼是在DataSetObserver中那個onChanged方法中:
private void onChange() { int childCount = getChildCount(); int itemCount = mAdapter.getItemCount(); // 如果資料變少了,則移出多餘的view if(childCount > itemCount) removeViews(itemCount, childCount - itemCount); // 現在view的個數 <= 資料的個數 TextView child; for (int i = 0; i < itemCount; i++) { if(i < childCount) { child = (TextView) getChildAt(i); child.setText(mAdapter.getItem(i)); }else { child = (TextView) mAdapter.getView(this, i); addView(child); } } }
第5行代碼,如果發現新的資料集比以前少了,那麼我們去移除多餘的view,
接下來9行,還是去遍曆所有的資料集,接著一個if…else…是去判斷如果存在view,則只改變資料,如果資料量>子view個數,則添加新的view。
這樣,就做到了在最大程度上減少view的添加和移除操作。
當然,這段代碼也很有局限性,只能添加TextView,因為我們在這裡面強轉了一下。這段代碼只是去示範如何去實現一個資料觀察者。ok,最後再來看看我們的測試代碼和最後效果:
public class MainActivity extends Activity { private ArrayList<String> mData = new ArrayList<String>() { { for (int i = 0; i < 10; i++) { add("android " + i); } } }; private MyLinearLayout mLinearLayout; private MyAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLinearLayout = (MyLinearLayout) findViewById(R.id.linear); mAdapter = new MyAdapter(mData); mLinearLayout.setAdapter(mAdapter); mLinearLayout.postDelayed(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { mData.add("loader " + i); } mAdapter.notifyDataSetChanged(); } }, 2000); mLinearLayout.postDelayed(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { mData.remove(0); } mAdapter.notifyDataSetChanged(); } }, 4000); mLinearLayout.postDelayed(new Runnable() { @Override public void run() { mAdapter.notifyDataSetInvalidate(); } }, 6000); }}
效果如下:
最後是代碼下載:代碼下載