RecyclerView中Adapter和ViewHolder的封裝

前情提要

最近項目我在項目中使用了RecyclerView代替了ListView.因爲項目中有多出列表項使用RecyclerView,這就致使須要寫多個AdapterViewHolder.java

其實,怎麼說呢?就是懶,想少寫代碼,因此想研究一下可否簡化一下.git

具體實現

封裝分爲AdapterViewHolder兩部分,以下所示.github

ViewHolder

抽象類BaseHolder繼承RecyclerView.ViewHolder,並依賴注入的數據類型M,即和ViewHolder綁定的數據類型爲M.微信

該抽象類包含一個構造方法,用於獲取item對應的佈局.一個抽象函數用於將數據設置到item上面.app

/**
 * 基礎的ViewHolder
 * Created by zyz on 2016/5/17.
 */
public abstract class BaseHolder<M> extends RecyclerView.ViewHolder {

    public BaseHolder(ViewGroup parent, @LayoutRes int resId) {
        super(LayoutInflater.from(parent.getContext()).inflate(resId, parent, false));
    }

    /**
     * 獲取佈局中的View
     * @param viewId view的Id
     * @param <T> View的類型
     * @return view
     */
    protected <T extends View>T getView(@IdRes int viewId){
        return (T) (itemView.findViewById(viewId));
    }

    /**
     * 獲取Context實例
     * @return context
     */
    protected Context getContext() {
        return itemView.getContext();
    }

    /**
     * 設置數據
     * @param data 要顯示的數據對象
     */
    public abstract void setData(M data);
}

Adapter

Adapter類也爲抽象類,繼承於RecyclerView.Adapter,並綁定了兩個泛型:ide

  1. M : 用於該 Adapter 的列表的數據類型,即List<M>.函數

  2. H : 即和 Adapter 綁定的 Holder 的類型.佈局

而且,該 Adapter 自帶 List 數據集合,聲明時能夠不用傳遞數據集合.也包含了 List 的相關操做.同時還給該 Adapter 綁定了一個 item 的點擊事件,且爲可選操做,不須要點擊操做,直接傳null便可.this

/**
 * 基礎的Adapter
 * Created by zyz on 2016/5/17.
 */
public abstract class BaseAdapter<M, H extends BaseHolder<M>> extends RecyclerView.Adapter<H> {

    protected List<M> dataList;
    protected OnItemClickListener<H> listener;

    /**
     * 設置數據,並設置點擊回調接口
     *
     * @param list 數據集合
     * @param listener 回調接口
     */
    public BaseAdapter(@Nullable List<M> list, @Nullable OnItemClickListener<H> listener) {
        this.dataList = list;
        if (this.dataList == null) {
            this.dataList = new ArrayList<>();
        }

        this.listener = listener;
    }

    @Override
    public void onBindViewHolder(final H holder, int position) {
        holder.setData(dataList.get(position));
        if (listener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    listener.onItemClick(holder);
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    /**
     * 填充數據,此方法會清空之前的數據
     *
     * @param list 須要顯示的數據
     */
    public void fillList(List<M> list) {
        dataList.clear();
        dataList.addAll(list);
    }

    /**
     * 更新數據
     *
     * @param holder item對應的holder
     * @param data   item的數據
     */
    public void updateItem(H holder, M data) {
        dataList.set(holder.getLayoutPosition(), data);
    }

    /**
     * 獲取一條數據
     *
     * @param holder item對應的holder
     * @return 該item對應的數據
     */
    public M getItem(H holder) {
        return dataList.get(holder.getLayoutPosition());
    }

    /**
     * 獲取一條數據
     *
     * @param position item的位置
     * @return item對應的數據
     */
    public M getItem(int position) {
        return dataList.get(position);
    }

    /**
     * 追加一條數據
     *
     * @param data 追加的數據
     */
    public void appendItem(M data) {
        dataList.add(data);
    }

    /**
     * 追加一個集合數據
     *
     * @param list 要追加的數據集合
     */
    public void appendList(List<M> list) {
        dataList.addAll(list);
    }

    /**
     * 在最頂部前置數據
     *
     * @param data 要前置的數據
     */
    public void preposeItem(M data) {
        dataList.add(0, data);
    }

    /**
     * 在頂部前置數據集合
     *
     * @param list 要前置的數據集合
     */
    public void preposeList(List<M> list) {
        dataList.addAll(0, list);
    }
}

使用範例

使用範例爲一種Item和多種Item這兩種類型.spa

一種Item

運行結果以下圖所示:

單個Item類型的ViewHolder以下:

/**
 * 一種View的Holder
 * Created by zyz on 2016/5/17.
 */
public class SingleHolder extends BaseHolder<Person> {

    TextView nameView;
    TextView ageView;

    public SingleHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);

        nameView = getView(R.id.name_tv);
        ageView = getView(R.id.age_tv);
    }

    @Override
    public void setData(Person data) {
        nameView.setText(data.getName());
        ageView.setText(String.valueOf(data.getAge()));
    }
}

與之對應的Adapter以下:

/**
 * 一種item的Adapter
 * Created by zyz on 2016/5/17.
 */
public class SingleAdapter extends BaseAdapter<Person, SingleHolder> {

    public SingleAdapter(SingleItemClickListener listener) {
        super(null, listener);
    }

    @Override
    public SingleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new SingleHolder(parent, R.layout.item_single);
    }

    @Override
    public void onBindViewHolder(final SingleHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        holder.nameView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ((SingleItemClickListener) listener).onNameClick(getItem(holder).getName());
            }
        });

        holder.ageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ((SingleItemClickListener) listener).onAgeClick(getItem(holder).getAge());
            }
        });
    }

    public interface SingleItemClickListener extends OnItemClickListener<SingleHolder> {

        void onNameClick(String name);

        void onAgeClick(int age);
    }
}

多種Item

運行結果以下圖所示:

多個Item的ViewHolder的寫法,能夠根據Item的View重合度來寫:

  1. 若是多個item徹底沒有相同的部分,則單獨繼承ViewHolder

  2. 若是Item之間有相同的部分,能夠抽出來一個父類來繼承ViewHolder

這裏的範例Item是具備重合部分的.模型來自聊天界面.

Holder部分以下:
|-ChatHolder //聊天View的Holder,包含公共部分
    |-TextHolder //文字消息的Holder,包含文字特有的部分
    |-ImageHolder //圖片消息的Holder,包含圖片特有的部分.
    
數據部分以下:
|-ChatMsg //表明一條聊天消息
    |-TextMsg //表明一條文字消息
    |-ImageMsg //表明一條圖片消息

ChatHolder代碼以下,包含發送者的名稱和時間:

/**
 * 聊天界面的ViewHolder
 * Created by zyz on 2016/5/18.
 */
public class ChatHolder extends BaseHolder<ChatMsg> {

    TextView senderNameTv;
    TextView createTimeTv;

    public ChatHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);

        senderNameTv = getView(R.id.name_tv);
        createTimeTv = getView(R.id.create_time_tv);
    }

    @Override
    public void setData(ChatMsg data) {
        senderNameTv.setText(data.getSenderName());
        createTimeTv.setText(data.getCreateTime());
    }
}

TextHolder的代碼以下,包含文本顯示的View

/**
 * 文本消息的Holder
 * Created by zyz on 2016/5/18.
 */
public class TextHolder extends ChatHolder {

    TextView contentTv;

    public TextHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);

        contentTv = getView(R.id.content_tv);
    }

    @Override
    public void setData(ChatMsg data) {
        super.setData(data);
        contentTv.setText(((TextMsg)data).getText());
    }
}

其中的setData()方法默認調用父類的方法,能夠直接設置發送者的名稱和時間.

ImageHolder的代碼以下,包含顯示圖片的View

/**
 * 表情消息的Holder
 * Created by zyz on 2016/5/18.
 */
public class ImageHolder extends ChatHolder {

    ImageView contentIv;

    public ImageHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);
        contentIv = getView(R.id.content_iv);
    }

    @Override
    public void setData(ChatMsg data) {
        super.setData(data);
        contentIv.setImageResource(((ImageMsg)data).getResId());
    }
}

最後是咱們的Adapter,代碼很少.

/**
 * 聊天界面的Adapter
 * Created by zyz on 2016/5/18.
 */
public class ChatAdapter extends BaseAdapter<ChatMsg, ChatHolder> {

    private static final int VIEW_TEXT = 0;
    private static final int VIEW_IMAGE = 1;

    public ChatAdapter(OnItemClickListener<ChatHolder> listener) {
        super(null, listener);
    }

    @Override
    public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ChatHolder holder;
        if (viewType == VIEW_IMAGE) {
            holder = new ImageHolder(parent, R.layout.item_msg_img_left);
        } else {
            holder = new TextHolder(parent, R.layout.item_msg_text_left);
        }
        return holder;
    }

    @Override
    public int getItemViewType(int position) {
        if (getItem(position).getMsgType() == ChatMsg.TYPE_TEXT) {
            return VIEW_TEXT;
        } else {
            return VIEW_IMAGE;
        }
    }
}

上述設置的有時間監聽,則對應的事件處理在Activity中完成

chatAdapter = new ChatAdapter(new OnItemClickListener<ChatHolder>() {
    @Override
    public void onItemClick(ChatHolder holder) {
        //處理事件
    }
});

以上就是對ViewHolder和Adapter的簡易封裝,之後會根據須要繼續封裝簡化.

代碼地址以下:https://github.com/Dev-Wiki/RecyclerView

更多文章請移步個人博客:DevWiki Blog

重要說明

想隨時獲取最新博客文章更新,請關注公共帳號DevWiki,或掃描下面的二維碼:

微信公共號

相關文章
相關標籤/搜索