Android 一起來封裝一個簡單易用的Adapter

neverwoods發表於2018-01-05

前言

還記得我初學 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

相關文章