對RecyclerView.Adapter做一個簡單的封裝

RecyclerView的適配器寫起來非常繁瑣,里面有很多地方都是重復(fù)的套路,以至于每寫一個適配器都要手動去編寫那些不必要的代碼。為了省力,我們可以將那些重復(fù)的部分封裝起來。

為了更好的講解封裝的思路,這里不先貼出封裝后的代碼,而是貼出封裝前的RecyclerView適配器代碼。如果不想看繁瑣的講解,請直接將頁面移到底部查看完整代碼以及用法(頁內(nèi)跳轉(zhuǎn)不知道為什么不起作用,所以只能靠手動移到底部)。

RecordAdapter

public class RecordAdapter extends RecyclerView.Adapter<RecordAdapter.RecordHolder> {

    //上下文環(huán)境
    private Context context;
    //數(shù)據(jù)集合
    private List<Record> data;

    public RecordAdapter(Context context, List<Record> data) {
        this.context = context;
        this.data = data;
    }

    /**
     * 刷新數(shù)據(jù)
     * @param data
     */
    public void refresh(List<Record> data){
        this.data = data;
        notifyDataSetChanged();
    }

    @Override
    public RecordHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_record, parent, false);
        return new RecordHolder(view);
    }

    @Override
    public void onBindViewHolder(RecordHolder holder, int position) {
        Record record = data.get(position);
        holder.bind(record);
    }


    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }

    class RecordHolder extends RecyclerView.ViewHolder {

        TextView tvId;
        TextView tvRemark;
        TextView tvCreatedAt;

        public RecordHolder(View itemView) {
            super(itemView);
            tvId = (TextView) itemView.findViewById(R.id.tvId);
            tvRemark = (TextView) itemView.findViewById(R.id.tvRemark);
            tvCreatedAt = (TextView) itemView.findViewById(R.id.tvCreatedAt);
        }

        //綁定數(shù)據(jù)
        public void bind(Record record) {
            tvId.setText(record.getId().toString());
            tvRemark.setText(record.getRemark());
            tvCreatedAt.setText(record.getCreatedAt().toString());
        }
    }
}

這是一個適配器所要完成的幾個最基本的工作,重寫onCreateViewHolder()、 onBindViewHolder()、getItemCount()方法以及自定義ViewHolder。當然還有itemView布局中各個控件的監(jiān)聽事件的綁定,只不過此處只是一個簡單的演示,所以沒寫。

接下來通過對比的方式講解BaseRecyclerAdapter的封裝思路

BaseRecyclerAdapter的成員變量以及構(gòu)造器
public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter {

    //上下文環(huán)境
    private Context context;
    //數(shù)據(jù)集合
    private List<T> data;

    public BaseRecyclerAdapter(Context context, List<T> data) {
        this.context = context;
        this.data = data;
    }
}
對比RecordAdapterde的成員變量以及構(gòu)造器
public class RecordAdapter extends RecyclerView.Adapter<RecordAdapter.RecordHolder> {

    //上下文環(huán)境
    private Context context;
    //數(shù)據(jù)集合
    private List<Record> data;

    public RecordAdapter(Context context, List<Record> data) {
        this.context = context;
        this.data = data;
    }

    ...
}

所有的適配器都有這兩個成員變量,所以對這兩個變量可以做一個封裝。List的類型是自定義的,這里用泛型T表示。

BaseRecyclerAdapter中的getItemCount()方法
    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }
對比RecordAdapterde中的getItemCount()方法
    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }

一模一樣!所有的getItemCount()都長這樣所以沒啥好說的。

在重寫onCreateViewHolder()和onBindViewHolder()方法之前先完成BaseViewHolder

BaseRecyclerAdapter內(nèi)部類BaseViewHolder
    /**
     * 封裝 ViewHolder
     */
    class BaseViewHolder extends RecyclerView.ViewHolder {

        // item 中的控件緩存在這里
        private SparseArray<View> views;

        public BaseViewHolder(View itemView) {
            super(itemView);
            views = new SparseArray<>();
        }

        /**
         * 獲取 itemView 中的控件
         *
         * @param id
         * @return
         */
        public View getView(int id) {
            View view = views.get(id);
            if (view == null) {
                view = itemView.findViewById(id);
                views.put(id, view);
            }
            return view;
        }
    }
對比RecordAdapterde內(nèi)部類RecordHolder
    class RecordHolder extends RecyclerView.ViewHolder {

        // item 中的控件
        TextView tvId;
        TextView tvRemark;
        TextView tvCreatedAt;

        public RecordHolder(View itemView) {
            super(itemView);
            tvId = (TextView) itemView.findViewById(R.id.tvId);
            tvRemark = (TextView) itemView.findViewById(R.id.tvRemark);
            tvCreatedAt = (TextView) itemView.findViewById(R.id.tvCreatedAt);
        }

        //綁定數(shù)據(jù)
        public void bind(Record record) {
            tvId.setText(record.getId().toString());
            tvRemark.setText(record.getRemark());
            tvCreatedAt.setText(record.getCreatedAt().toString());
        }
    }

可以看到RecordHolder中的成員變量是已經(jīng)寫死的,如果我們要做的封裝的話就不能寫死,所以只能用一個SparseArray<View>集合保存那些控件。而且RecordHolder在構(gòu)造方法中就已經(jīng)將那些控件綁定,在BaseViewHolder中顯然不能這么做,它提供了public View getView(int id)方法。這方法可以從SparseArray<View>中取出控件,并且在取出的過程中動態(tài)的綁定控件。

這里簡單介紹一下SparseArray集合:SparseArray和HashMap類似,都是以Key和Value的形式保存數(shù)據(jù)。(如果想要了解得更詳細的話可以去問度娘)

getView()方法中的代碼片段
if (view == null) {
    view = itemView.findViewById(id);
    views.put(id, view);
}

這部分代碼的作用就是當SparseArray<View>集合中沒有指定控件的時候會去itemView中找,這個過程就已經(jīng)完成了控件的綁定了,所以不用像RecordAdapterde那樣在構(gòu)造器中就已經(jīng)完成控件的綁定了。

我們還剩余兩個方法沒有搞定,一個是onCreateViewHolder()另一個是onBindViewHolder(),在這之前先做一件事情,將RecyclerView.Adapter改為RecyclerView.Adapter<BaseRecyclerAdapter.BaseViewHolder>

public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BaseRecyclerAdapter.BaseViewHolder> {

    //上下文環(huán)境
    private Context context;
    //數(shù)據(jù)集合
    private List<T> data;

    ...
}
BaseRecyclerAdapter中的onCreateViewHolder()方法
    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(getItemLayoutId(), parent, false);
        return new BaseViewHolder(view);
    }

    /**
     * 子類通過重寫此方法設(shè)置 RecyclerView 的 item 項的布局視圖
     *
     * @return
     */
    public abstract int getItemLayoutId();
對比RecordAdapter中的onCreateViewHolder()方法
    @Override
    public RecordHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_record, parent, false);
        return new RecordHolder(view);
    }

除了R.layout.item_record這個參數(shù)需要手動設(shè)置,其他的代碼都是固定不變的。這個參數(shù)可以通過抽象方法public abstract int getItemLayoutId()來設(shè)置,而我們通過在子類中重寫這個方法就可以靈活的設(shè)置itemView。

BaseRecyclerAdapter中的onBindViewHolder()方法
    @Override
    public void onBindViewHolder(BaseRecyclerAdapter.BaseViewHolder holder, int position) {
        T t = data.get(position);
        bindData(holder, t);
    }

    /**
     * 將數(shù)據(jù)綁定到 itemView 視圖上
     *
     * @param holder
     * @param t
     */
    public abstract void bindData(BaseViewHolder holder, T t);
對比RecordAdapter中的onBindViewHolder()方法
    @Override
    public void onBindViewHolder(RecordHolder holder, int position) {
        Record record = data.get(position);
        holder.bind(record);
    }

在BaseRecyclerAdapter中通過抽象方法public abstract void bindData(BaseViewHolder holder, T t)方法實現(xiàn)數(shù)據(jù)綁定,同樣的我們需要在子類中重寫該方法。

到這里BaseRecyclerAdapter就完成了,下面是完整代碼

完整代碼

BaseRecyclerAdapter
public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BaseRecyclerAdapter.BaseViewHolder> {

    //上下文環(huán)境
    private Context context;
    //數(shù)據(jù)集合
    private List<T> data;

    /**
     * 刷新數(shù)據(jù)
     *
     * @param data
     */
    public void refresh(List<T> data) {
        this.data = data;
        notifyDataSetChanged();
    }

    /**
     * 添加數(shù)據(jù)
     *
     * @param data
     */
    public void append(List<T> data) {
        this.data.addAll(data);
        notifyDataSetChanged();
    }

    public BaseRecyclerAdapter(Context context, List<T> data) {
        this.context = context;
        this.data = data;
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(getItemLayoutId(), parent, false);
        return new BaseViewHolder(view);
    }

    @Override
    public void onBindViewHolder(BaseRecyclerAdapter.BaseViewHolder holder, int position) {
        T t = data.get(position);
        bindData(holder, t);
    }

    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }

    /**
     * 將數(shù)據(jù)綁定到itemView視圖上
     *
     * @param holder
     * @param t
     */
    public abstract void bindData(BaseViewHolder holder, T t);

    /**
     * 子類通過重寫此方法設(shè)置itemView項的布局視圖
     *
     * @return
     */
    public abstract int getItemLayoutId();

    /**
     * 封裝 ViewHolder
     */
    class BaseViewHolder extends RecyclerView.ViewHolder {

        // item 中的控件緩存在這里
        private SparseArray<View> views;

        public BaseViewHolder(View itemView) {
            super(itemView);
            views = new SparseArray<>();
        }

        /**
         * 獲取 itemView 中的控件
         *
         * @param id
         * @return
         */
        public View getView(int id) {
            View view = views.get(id);
            if (view == null) {
                view = itemView.findViewById(id);
                views.put(id, view);
            }
            return view;
        }

    }
}

使用

RecordAdapter.java
public class RecordAdapter extends BaseRecyclerAdapter<Record> {

    public RecordAdapter(Context context, List<Record> data) {
        super(context, data);
    }

    @Override
    public void bindData(BaseViewHolder holder, Record record) {
        TextView tvId = (TextView) holder.getView(R.id.tvId);
        TextView tvRemark = (TextView) holder.getView(R.id.tvRemark);
        TextView tvCreatedAt = (TextView) holder.getView(R.id.tvCreatedAt);

        tvId.setText(record.getId().toString());
        tvRemark.setText(record.getRemark());
        tvCreatedAt.setText(record.getCreatedAt().toString());
    }

    @Override
    public int getItemLayoutId() {
        return R.layout.item_record;
    }
}

創(chuàng)建一個RecordAdapter類,它繼承自BaseRecyclerAdapter,并且在泛型T位置填Record。然后導(dǎo)入構(gòu)造方法和重寫兩個抽象方法。

Record.java
public class Record {

    //ID
    private Integer id;
    //備注
    private String remark;
    //創(chuàng)建日期
    private Date createdAt;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
}
item_record.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="10dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tvId"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="1"
            android:textSize="30sp"/>

        <android.support.v7.widget.ContentFrameLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="5dp"
            android:layout_weight="3">

            <TextView
                android:id="@+id/tvRemark"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:text="這里是備注"/>

            <TextView
                android:id="@+id/tvCreatedAt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|right"
                android:text="2018.6.6 11:00:00"/>
        </android.support.v7.widget.ContentFrameLayout>
    </LinearLayout>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private RecordAdapter adapter;
    private List<Record> records;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new RecordAdapter(this, records);
        recyclerView.setAdapter(adapter);

        records = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Record record = new Record();
            record.setId(i);
            record.setRemark("第" + i + "條備注");
            record.setCreatedAt(new Date());
            records.add(record);
        }

        adapter.refresh(records);
    }
}

運行效果如下

運行結(jié)果.png

最后

雖然這個類能夠簡化適配器的編寫,但是僅僅適用于簡單情況。如果要對item_record中的每一個控件綁定不同的監(jiān)聽器,這個類顯然是做不到的。所以對于復(fù)雜的情況還是要使用原始的辦法,也就是繼承RecyclerView.Adapter去一步一步的寫,沒法偷懶。封裝監(jiān)聽器的綁定在下一篇對RecyclerView.Adapter做一個簡單的封裝2中解決。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 【Android 控件 RecyclerView】 概述 RecyclerView是什么 從Android 5.0...
    Rtia閱讀 308,481評論 27 440
  • 前言 關(guān)于adapter的封裝,網(wǎng)上有很多開源庫,開發(fā)的時候可以直接拿來用,省了很多事。最近閑來無事,想著自己動手...
    誰幫我起個名字閱讀 1,361評論 3 16
  • 我雙十一的戰(zhàn)利品都基本到位,一些日常用品,像抽紙,濕巾;洗護用品,像洗發(fā)水,洗衣液,等等,還有更多,每年...
    垛垛閱讀 527評論 0 1
  • 搜索引擎優(yōu)化的概念 優(yōu)化工作不是一個一成不變的工作,他需要優(yōu)化人員時時刻刻去尋找和優(yōu)化用戶...
    T小兜閱讀 517評論 0 0
  • 竟然已經(jīng)是這一天的尾巴了,困得無法自拔但還是要寫點什么。 走了,匆匆忙忙。大大小小塞進車里的行李,宿舍門外堆著的凌...
    hellolibo閱讀 169評論 0 1

友情鏈接更多精彩內(nèi)容