天天看點

通用Adapter的實作套路(ListView篇)

一般情況,我們ListView的Adapter,繼續自BaseAdapter,很多方法都是重複性的工作,如:getItem(),getCount()等,為了更高效,我們要自己建立ViewHolder,用于緩存View。随着開發經驗的積累,我們發現其中的一些套路,可以将這些不變的部分放到抽象類中,将變換的部分,寫成抽象方法,這樣,在使用時,直接繼續自我們的CommonAdapter,複寫抽象方法即可。

1、CommonAdapter

public abstract class CommonAdapter<T> extends BaseAdapter {

    protected Context mContext;
    private List<T> datas;
    private int mLayoutId;

    public CommonAdapter(Context context, List<T> datas, int layoutId) {
        this.mContext = context;
        this.datas = datas;
        this.mLayoutId = layoutId;
    }

    @Override
    public int getCount() {
        return datas.size();
    }

    @Override
    public T getItem(int position) {
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = ViewHolder.getViewHolder(mContext, convertView, mLayoutId);

        convert(viewHolder, datas.get(position), position);

        return viewHolder.getConvertView();
    }

    protected abstract void convert(ViewHolder viewHolder, T data, int position);
}
           

2、ViewHolder

public class ViewHolder {

    private View convertView;
    private SparseArray<WeakReference<View>> mViews;

    public ViewHolder(Context context, View convertView, int layoutId) {
        convertView = View.inflate(context, layoutId, null);
        convertView.setTag(this);

        this.convertView = convertView;

        mViews = new SparseArray<>();
    }

    public static ViewHolder getViewHolder(Context context, View convertView, int layoutId) {
        if (convertView == null) {
            ViewHolder viewHolder = new ViewHolder(context, convertView, layoutId);
            return viewHolder;
        } else {
            return (ViewHolder) convertView.getTag();
        }
    }

    public View getConvertView() {
        return convertView;
    }

    public ViewHolder setText(int viewId, CharSequence text) {
        TextView tv = getView(viewId);
        if (tv != null) {
            tv.setText(text);
        }

        return this;
    }

    public ViewHolder setImageRes(int viewId, int resId) {
        ImageView iv = getView(viewId);
        if (iv != null) {
            iv.setImageResource(resId);
        }

        return this;
    }

    public ViewHolder setImageUrl(int viewId, String url) {
        ImageView iv = getView(viewId);
        if (iv != null) {
            //這裡要配合網絡架構使用...
        }
        return this;
    }

    public <T extends View> T getView(int viewId) {

        WeakReference<View> viewWeakReference = mViews.get(viewId);
        View view = null;
        if (viewWeakReference != null) {
            view = viewWeakReference.get();
        }

        if (view == null) {
            view = convertView.findViewById(viewId);
            mViews.put(viewId, new WeakReference<View>(view));
        }

        return (T) view;
    }
}
           

将之前在getView()中判斷convertView的内容,移到了getViewHolder()方法中,然後提供了常用的setText()和setImage()方法,通路網絡圖檔的,這裡沒寫完。其他控件可以通過調用getView()方法進行内容設定。

還有兩點,需要注意:

  1. setText()這幾個方法,我們采用鍊式調用的方式,一行代碼,設定任意多個控件。
  2. getView()的寫法,我們不是簡單的省去findViewById()這種操作,更重要的是,使用SparseArray對View的緩存,我們并沒有直接将View放到集合中,而是用WeakReference對View進行了一層包裝,防止記憶體洩漏。

3、如何用?

public class MyAdapter extends CommonAdapter<Person> {

    public MyAdapter(Context context, List datas) {
        super(context, datas, R.layout.item_layout);
    }

    @Override
    protected void convert(ViewHolder viewHolder, Person data, int position) {
        viewHolder.setText(R.id.item_tv, "測試")
        .setText(R.id.activity_main, "hello");

    }
}
           

說明:

  1. 隻要指定泛型參數,就可以直接在convert()方法中設定相應的值了。
  2. 我們在封裝CommonAdapter時,構造方法中有一個layoutId參數,它是用來設定item布局的,隻要在建立的Adapter中,調用super()方法,并設定相應的布局即可,不用再抛給外面設定了。
  3. 在convert()方法中,隻接調用viewHolder,就可以進行setText()等指派操作。

如何擷取代碼?

git clone https://github.com/droid4j/anKataLite.git

本篇對應的标簽 v0.2

git checkout v0.2

繼續閱讀