前言
還記得我初學 Android 沒多久又需要用到 ListView 的時候還不會寫 Adapter,結果我居然硬生生的用 TableLayout 和 LinearLayout 把 ListView 給替代了。現在回過神了想想,我當時還真是厲害啊,在另一種意義上。。
其實要會寫 Adapter,乃至於封裝一個能提高生產效率的 Adapter,是離不開對 ListView、GridView 到 RecyclerView 等一系列檢視的Item複用機制的理解和掌握的。不熟悉的朋友們請移駕 http://blog.csdn.net/lmj623565791/article/details/24333277
正文
我還依稀記得最早是這麼寫一個 Adapter 的(為什麼是依稀呢?)
BaseAdapter adapter = new BaseAdapter() {
@Override
public int getCount() {
return list.size();
}
@Override
public String getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = inflater.inflate(layoutId, parent, false);
TextView textView = convertView.findViewById(textViewId);
Button button = convertView.findViewById(buttonId);
String data = getItem(position);
textView.setText(data);
button.setOnClickListener(onClickListener);
return convertView;
}
};
複製程式碼
後來我才意識到,這樣子寫在滑動的時候會很頻繁的去 inflate,開銷很大,是不合格的寫法,於是改成了下面這樣:
BaseAdapter adapter = new BaseAdapter() {
@Override
public int getCount() {
return list.size();
}
@Override
public String getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = inflater.inflate(layoutId, parent, false);
viewHolder = new ViewHolder();
viewHolder.textView = convertView.findViewById(textViewId);
viewHolder.button = convertView.findViewById(buttonId);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
String data = getItem(position);
viewHolder.textView.setText(data);
viewHolder.button.setOnClickListener(onButtonClickeListener);
convertView.setTag(viewHolder);
convertView.setOnClickListener(onConvertViewClickedListener);
return convertView;
}
};
複製程式碼
class ViewHolder {
TextView textView;
Button button;
}
複製程式碼
這樣子寫基本上沒有遇到遇到太大問題,也就最初容易遇到因為 item 複用導致的各種混亂,在理解了複用機制之後其實很容易避免。我也曾經見到過將 convertView 傳入 ViewHolder 中,在 viewHolder 裡去實現大部分邏輯的寫法,這裡就不一一贅述了。
現在來想一下,寫一個 Adapter,我們要做些什麼?大致有這些吧: 1.實現 BaseAdapter 的 getCount、getItem、getItemId 這三個方法; 2.新建一個 ViewHolder 類,當中要包含一個 item 中需要操作的所有 view; 3.在 BaseAdapter 的 getView 方法中實現 item 複用,處理好 viewHolder 與 convertView 的關係; 4.處理 item,設定資料至 view ,新增監聽等。
看上去也不太多嘛,就4步。但是實際上多寫幾次就很難受了,要是專案中 ListView、GridView 特別多,估計能把一隻猿活活寫吐了!
因為1、2、3的程式碼基本上每次都差不多,不同之處大多都在於4。一個複雜的 ListView 寫下來感覺腰痠背痛手抽筋,簡單的則感覺不會再愛了,畢竟花在寫4的時間上好像遠遠小於123的重複工作。久而久之,我一聽到要做 ListView 就會這樣 --> T_T
直到在一個月黑風高的夜晚,我意外得到了一件神器
public class ViewHolder {
public static <T extends View> T get(View view, int id) {
SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
view.setTag(viewHolder);
}
View childView = viewHolder.get(id);
if (childView == null) {
childView = view.findViewById(id);
viewHolder.put(id, childView);
}
return (T) childView;
}
}
複製程式碼
從程式碼上看,這個 ViewHolder 類會往傳入的 view 中存一系列 id-view 的鍵值對作為 tag,程式碼並不複雜,相信大家都看得懂。 我們再看看這神器到底能幹什麼
BaseAdapter adapter = new BaseAdapter() {
@Override
public int getCount() {
return list.size();
}
@Override
public String getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(layoutId, parent, false);
}
TextView textView = ViewHolder.get(convertView, textViewId);
Button button = ViewHolder.get(convertView, buttonId);
String data = getItem(position);
textView.setText(data);
button.setOnClickListener(onButtonClickListener);
return convertView;
}
};
複製程式碼
完全不用專門寫 ViewHolder 類了有木有,對於我這樣的懶人簡直就是福音得意 以上就是神器 ViewHolder 的功效。
難道這是全部了嗎?作為一個懶惰的程式猿,相信大家肯定和我一樣不滿足於此。放眼上述的程式碼,不管是哪個 adapter 都寫了幾個重複的方法,那些方法要怎麼幹掉呢?
其實,一個簡單的基類加上對泛型的支援就可以了
public abstract class ListAdapter<T> extends BaseAdapter {
protected abstract void setItem(View convertView, T data, int position);
protected List<T> mData;
protected Context mContext;
protected LayoutInflater mInflater;
protected int mLayoutRes;
public ListAdapter(Context context, List<T> data, int layoutRes) {
this.mData = data;
this.mContext = context;
this.mLayoutRes = layoutRes;
this.mInflater = LayoutInflater.from(mContext);
}
/**
* 重新整理 adapter
* 考慮到可能會發生資料完全改變的情況,故提供此方法
* @param data
*/
public void refresh(List<T> data) {
try {
this.mData = data;
notifyDataSetChanged();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public int getCount() {
return mData.size();
}
@Override
public T getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
try {
if (convertView == null) {
convertView = mInflater.inflate(mLayoutRes, null);
}
setItem(convertView, getItem(position), position);
} catch (Exception e) {
e.printStackTrace();
}
return convertView;
}
public <V extends View> V getChildView(View view, int id) {
return ViewHolder.get(view, id);
}
}
複製程式碼
封裝到此結束,讓我們看看是怎麼使用的吧
BaseAdapter adapter = new ListAdapter<String>(MainActivity.this, list, layoutId) {
@Override
protected void setItem(View convertView, String data, int position) {
TextView textView = getChildView(convertView, textViewId);
textView.setText(data);
getChildView(convertView, buttonId)
.setOnClickListener(onButtonClickListener);
}
};
複製程式碼
沒仔細看前面程式碼的圍觀群眾:WTF?這就完了?
沒錯,傳入上下文、資料集合,還有佈局檔案,重寫一個 setItem 方法就完了。相比最初的 adapter,我們只需要專注於佈局、資料和檢視的繫結,已經事件的監聽,不需要重寫相同的程式碼,不需要寫累贅的 ViewHolder 類,不需要寫八股文一般的 Item 複用程式碼。
雖然並不完美,也還沒做到極簡,但一個簡單好用的 Adapter 已經初步成型了。謝謝各位看官賞臉看到現在。
下一篇文章打算在這次的 ListAdapter 的基礎上封裝 RecyclerView 的 Adapter,並且提供對 ItemType 的支援方式的參考思路。
專案原始碼:https://github.com/neverwoodsS/zy-open