開源框架BaseRecyclerViewAdapterHelper使用——RecyclerView萬能適配器

前言

  • 主要聲明三點

  • 第一:
    關於本文的BaseRecyclerViewAdapterHelper用法,自然是轉載的官方原文。

  • 第二:
    本文所有內容都是官網上所有的,所以我要特別聲明:

1、本文其實只是按照自己的排版習慣重新排版而已;
2、在重寫排版的基礎上,增加了一些簡單的Demo,以及我在寫Demo過程中碰上的少量的問題,當然這些問題僅僅是我個人使用的時候碰上的,正如第一條所說的,我本身也是小白,也是在學習階段,對很多東西也是理解的不夠,所以會導致或多或少的問題,所以我像我其他文章一樣,我還是強烈建議不幸看到本文的同行去看原文學習使用:BRVAH官方使用指南(持續更新)http://www.jianshu.com/p/b343fcff51b0

  • 第三:
    該適配器雖號稱萬能適配器,但並非萬能,有很多功能本身是實現不了的,還會和其他開源的衝突,比如不能和XRecyclerView一起使用(至少我用的時候是真的衝突)。

  • 第四:
    關於文章中所提到的 "bug",我相信都不存在,只是由於時間關係,無心再去研究,但是我也堅信你繼續看本文,還是能夠知道該適配器的使用方式的。

一、框架引入

  • 先在項目的 build.gradle(Project:XXXX) 的 repositories 添加:
    allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
    }
  • 然後在Module的 build.gradle(Module:app) 的 dependencies 添加:
    dependencies {
            ......
            compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.22'
    }

注意: 一旦出現加載失敗的情況,只有兩種情況:

  • 一是:配置沒配置好

配置沒配置好,有幾種情況:
1. 只配置了 dependencies
2. 配置 repositories,但是位置錯了,build.gradle(Project:XXXX) 文件下的repositories有兩個,一個是buildscript下面的,一個是allprojects下面的,要配置到allprojects下面纔是對的。
3. 版本號前面多一個v,這個是我的鍋,在2.1.2版本之前都是帶v的,之後(包含2.1.2)都不需要帶v。

  • 二是:網絡原因(這個就不解釋了)

二、Adapter的最基本使用方法

2.1 常用示例代碼

  • 和原生的adapter相比,減少70%的代碼量。

  • 首先看一段使用示例代碼:

public class QuickAdapter extends BaseQuickAdapter<Status, BaseViewHolder> {
    public QuickAdapter() {
        super(R.layout.tweet, DataServer.getSampleData());
    }

    @Override
    protected void convert(BaseViewHolder viewHolder, Status item) {
        viewHolder.setText(R.id.tweetName, item.getUserName())
                .setText(R.id.tweetText, item.getText())
                .setText(R.id.tweetDate, item.getCreatedAt())
                .setVisible(R.id.tweetRT, item.isRetweet())
                .linkify(R.id.tweetText);
                 Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) viewHolder.getView(R.id.iv));
    }
}
  • 從上文中的實例代碼我們可以看出以下幾點:

1、使用: 首先需要繼承BaseQuickAdapter,然後BaseQuickAdapter<Status, BaseViewHolder>第一個泛型Status是數據實體類型,第二個BaseViewHolder是ViewHolder其目的是爲了支持擴展ViewHolder。
2、賦值:可以直接使用viewHolder對象點相關方法通過傳入viewId和數據進行,方法支持鏈式調用。如果是加載網絡圖片或自定義view可以通過viewHolder.getView(viewId)獲取該控件。

  • 當然這裏有一個比較常用的方法:

viewHolder.getLayoutPosition() 獲取當前item的position

2.2 最基本使用示例Demo

  • 要是剛接觸Android的朋友要是還是不知道怎麼用,那我這裏提供一個很簡單的demo:

  • 比如說我想要實現的效果大致如下:

     

  • 第一步:在佈局文件中引入RecyclerView
    activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>
  • 第二步:編寫條目佈局文件
    item_rv.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">

    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="150dp"
        android:layout_height="80dp" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/iv_img"
        android:text="我是標題"
        android:textColor="#f00"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@id/iv_img"
        android:text="我是描述" />
</RelativeLayout>
  • 第三步:編寫數據實體類型
    Model.java
public class Model {
    private String title;
    private String content;
    private String imgUrl;

    //生成set、get方法
    ......
}
  • 第四步:編寫適配器
    MyAdapter.java
public class MyAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {

    public MyAdapter(@LayoutRes int layoutResId, @Nullable List<Model> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);

        //獲取當前條目position
        //int position = helper.getLayoutPosition();
    }
}
  • 最後一步:在Activity中使用該適配器
    MainActivity.java
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas;
    private MyAdapter adapter;

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

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的數據(實際開發中一般是從網絡獲取的)
        datas = new ArrayList<>();
        Model model;
        for (int i = 0; i < 15; i++) {
            model = new Model();
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");
            datas.add(model);
        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MyAdapter(R.layout.item_rv, datas);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }
}
  • OK,運行,得到的效果就是上述的效果。

三、點擊事件

  • 上文中描述的就是使用BaseRecyclerViewAdapterHelper最基本的用法,因爲怕剛接觸Android的兄弟們不明朗或不相信這麼簡單的用法,所以做了上節簡單的demo示例用法。

  • 那麼使用列表當然少不了點擊事件,不論是整個條目的點擊事件還是條目中子控件的點擊事件,該適配器對點擊事件也是做了及簡化的處理:

3.1 條目事件

Item的點擊事件

        //條目點擊事件
        adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "點擊了第" + (position + 1) + "條條目", Toast.LENGTH_SHORT).show();
            }
        });

Item的長按事件

        //條目長按事件
        adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "長按了第" + (position + 1) + "條條目", Toast.LENGTH_SHORT).show();
                return false;
            }
        });

注意事項

  • 在嵌套recycleView的情況下需要使用你使用 adapter. setOnItemClickListener 來設置點擊事件,如果使用recycleView.addOnItemTouchListener會累計添加的。

3.2 條目子控件事件

Item子控件的點擊事件

  • 首先:在adapter的convert方法裏面通過 helper.addOnClickListener 綁定一下子控件的控件id
    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .addOnClickListener(R.id.iv_img)    //給圖標添加點擊事件
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
        //獲取當前條目position
        //int position = helper.getLayoutPosition();
    }
  • 然後:我們設置
        //條目子控件點擊事件
        adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "點擊了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
            }
        });

Item子控件的長按事件

  • 這裏和子控件點擊事件是一樣的,只是將 點擊 變成 長按 就可以了

  • 首先:在adapter的convert方法裏面通過 helper.addOnLongClickListener 綁定一下子控件的控件id

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                //.addOnClickListener(R.id.iv_img)    //給圖標添加點擊事件
                .addOnLongClickListener(R.id.iv_img)//給圖片添加長按事件
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
        //獲取當前條目position
        //int position = helper.getLayoutPosition();
    }
  • 然後:我們設置
        //條目子控件長按事件
        adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
            @Override
            public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "長按了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
                return false;
            }
        });

注意事項

  • 設置子控件的事件,如果不在adapter中綁定,點擊事件無法生效,因爲無法找到你需要設置的控件。

多個Item子控件事件

  • 官方文檔上沒說,但是 其實這裏可以用很常規的方法處理,就是通過判斷ID來判定是否是我要的控件?,從而處理不同的事件

  • 比如我這裏給 圖片和標題 都加點擊事件處理不同的邏輯

  • 首先:當然是在適配器中給圖片和標題都添加點擊事件

 @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可鏈式調用賦值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .addOnClickListener(R.id.iv_img)    //給圖標添加 點擊事件
                .addOnClickListener(R.id.tv_title)  //給標題也添加 點擊事件
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
        //獲取當前條目position
        //int position = helper.getLayoutPosition();
    }
  • 其次:
        //條目子控件點擊事件
        adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

                //判斷id
                if (view.getId() == R.id.iv_img) {
                    Log.i("tag", "點擊了第" + position + "條條目的 圖片");
                } else if (view.getId() == R.id.tv_title) {
                    Log.i("tag", "點擊了第" + position + "條條目的 標題");
                }
            }
        });
  • 那麼如果是長按事件呢?當然也是相同的處理方法。

如果需要在子控件事件中獲取其他子控件可以使用:

getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId)

比如:

        //條目子控件點擊事件
        adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "點擊了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();

                TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
                Log.i("tag", "當前圖片對應的 title=" + tv_title.getText());
            }
        });

        //條目子控件長按事件
        adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
            @Override
            public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "長按了第" + position + "條條目的圖片", Toast.LENGTH_SHORT).show();
                TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
                Log.i("tag", "長按的圖片對應的title=" + tv_title.getText());

                return false;
            }
        });

注意:如果有header的話需要處理一下position加上 headerlayoutcount。

  • 最後,到這裏,點擊事件基本就完了

四、添加列表加載動畫

  • 給條目添加動畫也是比較常見的需求

  • 開啓動畫(默認爲漸顯效果)

//開啓動畫(默認爲漸顯效果)
adapter.openLoadAnimation();
  • 該適配器提供了5種動畫效果(漸顯、縮放、從下到上,從左到右、從右到左)
public static final int ALPHAIN = 0x00000001;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SCALEIN = 0x00000002;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SLIDEIN_BOTTOM = 0x00000003;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SLIDEIN_LEFT = 0x00000004;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SLIDEIN_RIGHT = 0x00000005;
  • 更換動畫效果
//使用縮放動畫
adapter.openLoadAnimation(BaseQuickAdapter.SCALEIN);
  • 如果想自定義動畫,該適配器也提供了接口
        //自定義動畫效果
        adapter.openLoadAnimation(new BaseAnimation() {
            @Override
            public Animator[] getAnimators(View view) {
                return new Animator[]{
                        ObjectAnimator.ofFloat(view, "scaleY", 1, 0.5f, 1),
                        ObjectAnimator.ofFloat(view, "scaleX", 1, 0.5f, 1)
                };
            }
        });
  • 注意:動畫默認只執行一次,如果想重複執行可設置
//設置重複執行動畫
adapter.isFirstOnly(false);
  • 設置不顯示動畫數量(老實講,我沒明白這個方法的效果是啥,因爲我並沒有看到效果 0.0)
adapter.setNotDoAnimationCount(count);
  • 首次到界面的item每次都依次執行加載動畫

由於進入界面的item都是很多的速度進來的所以不會出現滑動顯示的依次執行動畫效果,這個時候會一起執行動畫,如果覺得這樣的效果不好可以使用setNotDoAnimationCount設置第一屏item不執行動畫,但是如果需要依次執行動畫可以重寫startAnim讓第一個屏幕的item動畫延遲執行即可。

@Override
    protected void startAnim(Animator anim, int index) {
        super.startAnim(anim, index);
        if (index < count)
        anim.setStartDelay(index * 150);
    }

五、添加頭部、尾部

  • 添加
    (可以添加多個頭部或尾部)
mQuickAdapter.addHeaderView(headerView);
mQuickAdapter.addFooterView(footerView);
  • 刪除指定view
mQuickAdapter.removeHeaderView(headerView);
mQuickAdapter.removeFooterView(footerView);
  • 刪除所有
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
  • 默認出現了頭部就不會顯示Empty,和尾部,配置以下方法也支持同時顯示:
mQuickAdapter.setHeaderAndEmpty()
mQuickAdapter.setHeaderFooterEmpty();
  • 默認頭部尾部都是佔滿一行,如果需要不佔滿可以配置:
mQuickAdapter.setHeaderViewAsFlow();
mQuickAdapter.setFooterViewAsFlow();

六、上拉加載

  • 沒錯,該適配器居然還實現了加載更多的功能,真心佩服作者

  • 設置上拉加載

// 滑動最後一個Item的時候回調onLoadMoreRequested方法
setOnLoadMoreListener(RequestLoadMoreListener);
  • 默認第一次加載會進入回調,如果不需要可以配置:
mQuickAdapter.disableLoadMoreIfNotFullPage();
  • 回調處理代碼
//上拉加載(設置這個監聽就表示有上拉加載功能了)
mQuickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override public void onLoadMoreRequested() {
                mRecyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //數據全部加載完畢
                            mQuickAdapter.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功獲取更多數據(可以直接往適配器添加數據)
                                mQuickAdapter.addData(DataServer.getSampleData(PAGE_SIZE));
                                mCurrentCounter = mQuickAdapter.getData().size();
                                //主動調用加載完成,停止加載
                                mQuickAdapter.loadMoreComplete();
                            } else {
                                //獲取更多數據失敗
                                isErr = true;
                                Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show();
                                //同理,加載失敗也要主動調用加載失敗來停止加載(而且該方法會提示加載失敗)
                                mQuickAdapter.loadMoreFail();

                            }
                        }
                    }

                }, delayMillis);
            }
        }, mReyclerView);
  • 加載完成(注意不是加載結束,而是本次數據加載結束並且還有下頁數據)
mQuickAdapter.loadMoreComplete();
  • 加載失敗
mQuickAdapter.loadMoreFail();
  • 加載結束
mQuickAdapter.loadMoreEnd();

注意:如果上拉結束後,下拉刷新需要再次開啓上拉監聽,需要使用setNewData方法填充數據。

  • 打開或關閉加載(一般用於下拉的時候做處理,因爲上拉下拉不能同時操作)
mQuickAdapter.setEnableLoadMore(boolean);
  • 預加載(這個功能屌炸天)
// 當列表滑動到倒數第N個Item的時候(默認是1)回調onLoadMoreRequested方法
mQuickAdapter.setPreLoadNumber(int);

設置自定義加載佈局

mQuickAdapter.setLoadMoreView(new CustomLoadMoreView());
public final class CustomLoadMoreView extends LoadMoreView {

    @Override 
    public int getLayoutId() {
        return R.layout.view_load_more;
    }

    /**
     * 如果返回true,數據全部加載完畢後會隱藏加載更多
     * 如果返回false,數據全部加載完畢後會顯示getLoadEndViewId()佈局
     */
    @Override 
    public boolean isLoadEndGone() {
        return true;
    }

    @Override 
    protected int getLoadingViewId() {
        return R.id.load_more_loading_view;
    }

    @Override 
    protected int getLoadFailViewId() {
        return R.id.load_more_load_fail_view;
    }

    /**
     * isLoadEndGone()爲true,可以返回0
     * isLoadEndGone()爲false,不能返回0
     */
    @Override 
    protected int getLoadEndViewId() {
        return 0;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_40">

    <LinearLayout
        android:id="@+id/load_more_loading_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/loading_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_marginRight="@dimen/dp_4"
            android:indeterminateDrawable="@drawable/sample_footer_loading_progress"/>

        <TextView
            android:id="@+id/loading_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/dp_4"
            android:text="@string/loading"
            android:textColor="#0dddb8"
            android:textSize="@dimen/sp_14"/>
    </LinearLayout>

    <FrameLayout
        android:id="@+id/load_more_load_fail_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">


        <TextView
            android:id="@+id/tv_prompt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="#0dddb8"
            android:text="@string/load_failed"/>

    </FrameLayout>

</FrameLayout>

七、下拉加載(符合聊天軟件下拉歷史數據需求)

1、這裏的下拉加載我個人覺得不好用,並不像我們常用的那種下拉加載(當然有可能是我沒有合理使用)
2、所以如果是需要下拉刷新的功能,我個人會使用SwipeRefreshLayout

  • 設置開啓開關
 mAdapter.setUpFetchEnable(true);
  • 設置監聽
mAdapter.setUpFetchListener(new BaseQuickAdapter.UpFetchListener() {
            @Override
            public void onUpFetch() {
                startUpFetch();
            }
        });
private void startUpFetch() {
        count++;
        /**
         * set fetching on when start network request.
         */
        mAdapter.setUpFetching(true);
        /**
         * get data from internet.
         */
        mRecyclerView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mAdapter.addData(0, genData());
                /**
                 * set fetching off when network request ends.
                 */
                mAdapter.setUpFetching(false);
                /**
                 * set fetch enable false when you don't need anymore.
                 */
                if (count > 5) {
                    mAdapter.setUpFetchEnable(false);
                }
            }
        }, 300);
    }
  • 開始加載的位置
mAdapter.setStartUpFetchPosition(2);

八、分組佈局

  • 由於我還沒碰到過這樣的需求,所以對 分組佈局 這個概念還不是很瞭解;

  • 當然,我對概念都不知道,所以也不知道是什麼樣的效果,當然也就沒有寫demo(主要是時間問題)。

  • 以下是正文

  • 實體類必須繼承SectionEntity:

public class MySection extends SectionEntity<Video> {
    private boolean isMore;
    public MySection(boolean isHeader, String header) {
        super(isHeader, header);
    }

    public MySection(Video t) {
        super(t);
    }
}
  • adapter構造需要傳入兩個佈局id,第一個是item的,第二個是head的,在convert方法裏面加載item數據,在convertHead方法裏面加載head數據:
public class SectionAdapter extends BaseSectionQuickAdapter<MySection> {
     public SectionAdapter(int layoutResId, int sectionHeadResId, List data) {
        super(layoutResId, sectionHeadResId, data);
    }
    @Override
    protected void convert(BaseViewHolder helper, MySection item) {
        helper.setImageUrl(R.id.iv, (String) item.t);
    }
    @Override
    protected void convertHead(BaseViewHolder helper,final MySection item) {
        helper.setText(R.id.header, item.header);
       
        helper.setOnClickListener(R.id.more, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context,item.header+"more..",Toast.LENGTH_LONG).show();
            }
        });
    }

九、多佈局

  • 多種類型條目的列表是我們日常開發中非常常見的功能,當然該適配器也給我們提供了相應的方法。

  • 使用該適配器設置不同類型條目有兩種方式,一種 耦合了實體類,一種是設置代理,這裏兩種方式我都會演示一遍。

9.1 實現MultiItemEntity的方式

  • 實體類必須實現MultiItemEntity,在設置數據的時候,需要給每一個數據設置itemType
public class MultipleItem implements MultiItemEntity {
    public static final int TEXT = 1;
    public static final int IMG = 2;
    private int itemType;

    public MultipleItem(int itemType) {
        this.itemType = itemType;
    }

    @Override
    public int getItemType() {
        return itemType;
    }
}
  • 在適配器構造函數裏面addItemType綁定type和layout的關係
public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem, BaseViewHolder> {

    public MultipleItemQuickAdapter(List data) {
        super(data);
        addItemType(MultipleItem.TEXT, R.layout.text_view);
        addItemType(MultipleItem.IMG, R.layout.image_view);
    }

    @Override
    protected void convert(BaseViewHolder helper, MultipleItem item) {
        switch (helper.getItemViewType()) {
            case MultipleItem.TEXT:
                helper.setImageUrl(R.id.tv, item.getContent());
                break;
            case MultipleItem.IMG:
                helper.setImageUrl(R.id.iv, item.getContent());
                break;
        }
    }

}
  • 如果考慮到在GridLayoutManager複用item問題可以配置
multipleItemAdapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() {
            @Override
            public int getSpanSize(GridLayoutManager gridLayoutManager, int position) {
                return data.get(position).getSpanSize();
            }
        });
  • 如果使用多佈局出現這個NotFoundException異常,有可能是addItemType()兩個參數寫反了。

  • 以上就是官方文檔給出的多佈局設置方式的介紹,可能對於像我這樣Android小白會看的一臉懵逼,所以我寫了一個很簡單的Demo方便理解。

Demo實例演示

  • 從上述文檔中我們是可以看出來兩點

1.我們的數據不直接傳給適配器,而是通過MultiItemEntity來傳遞
2.適配器構造裏必須綁定type和layout的關係

  • 廢話不多說,演示一個小demo(這裏Item佈局我就不貼出來了)

第一步:創建MultiItemEntity類

public class MyMultipleItem implements MultiItemEntity {
    public static final int FIRST_TYPE = 1;
    public static final int SECOND_TYPE = 2;
    public static final int NORMAL_TYPE = 3;

    private int itemType;
    private Model data;

    public MyMultipleItem(int itemType, Model data) {
        this.itemType = itemType;
        this.data = data;
    }

    @Override
    public int getItemType() {
        return itemType;
    }

    public Model getData(){
        return data;
    }
}

第二步:創建適配器

public class MultipleItemAdapter extends BaseMultiItemQuickAdapter<MyMultipleItem, BaseViewHolder> {
    
    public MultipleItemAdapter(List data) {
        super(data);
        //必須綁定type和layout的關係
        addItemType(MyMultipleItem.FIRST_TYPE, R.layout.first_type_layout);
        addItemType(MyMultipleItem.SECOND_TYPE, R.layout.second_type_layout);
        addItemType(MyMultipleItem.NORMAL_TYPE, R.layout.item_rv);

    }

    @Override
    protected void convert(BaseViewHolder helper, MyMultipleItem item) {
        switch (helper.getItemViewType()) {
            case MyMultipleItem.FIRST_TYPE:
                Log.i("tag","FIRST_TYPE==============="+helper.getLayoutPosition());
                break;
            case MyMultipleItem.SECOND_TYPE:
                Log.i("tag","SECOND_TYPE==============="+helper.getLayoutPosition());
                break;
            case MyMultipleItem.NORMAL_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getData().getTitle())
                        .setText(R.id.tv_content, item.getData().getContent());
                break;
        }
    }
}

第三步:最後當然是在我們的Activity裏編寫代碼

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas01;
    private List<MyMultipleItem> datas02;
    private MultipleItemAdapter adapter;

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

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的假數據(實際開發中當然是從網絡獲取數據)
        datas01 = new ArrayList<>();
        Model model;
        for (int i = 0; i < 30; i++) {
            model = new Model();
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");
            datas01.add(model);
        }

        datas02 = new ArrayList<>();
        //這裏我是隨機給某一條目加載不同的佈局
        for (int i = 0; i < 30; i++) {
            if (i % 3 == 0) {
                datas02.add(new MyMultipleItem(MyMultipleItem.FIRST_TYPE, null));
            } else if (i % 7 == 0) {
                datas02.add(new MyMultipleItem(MyMultipleItem.SECOND_TYPE, null));
            } else {
                datas02.add(new MyMultipleItem(MyMultipleItem.NORMAL_TYPE, datas01.get(i)));
            }

        }

        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MultipleItemAdapter(datas02);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }
}

最後:已近完事了,可以運行了,

9.2 爲 BaseQuickAdapter 設置代理的方式

  • 從上文中,我們可以知道,其實多佈局的本質還是要用某一個變量來區分,上述的方法是使用專門提供得MultiItemEntity接口,讓我們實現,從而進行區分;

  • 既然是某一個變量來區分,那我們能不能不實現MultiItemEntity接口,直接在適配器裏進行區分呢?

  • 官方還給出了一種方式,給BaseQuickAdapter 設置代理

  • 首先我們先看一下官方給出的文檔(分三步)

public class MultiDelegateAdapter extends BaseQuickAdapter<Entity, BaseViewHolder> {

        public MultiDelegateAdapter() {
            super(null);
            //Step.1
            setMultiTypeDelegate(new MultiTypeDelegate<Entity>() {
                @Override
                protected int getItemType(Entity entity) {
                    //根據你的實體類來判斷佈局類型
                    return entity.type;
                }
            });
            //Step.2
            getMultiTypeDelegate()
                    .registerItemType(Entity.TEXT, R.layout.item_text_view)
                    .registerItemType(Entity.IMG, R.layout.item_image_view);
        }

        @Override
        protected void convert(BaseViewHolder helper, Entity entity) {
            //Step.3
            switch (helper.getItemViewType()) {
                case Entity.TEXT:
                    // do something
                    break;
                case Entity.IMG:
                    // do something
                    break;
            }
        }
    }
  • 可能對於剛接觸Android的朋友會看的很懵逼,完全不知道該怎麼用,那是因爲我們對面向對象的思想理解的還不是很深刻,沒關係,這裏我也寫一個簡單的Demo,希望能夠幫助理解;

Demo實例演示

首先:我給Model類添加一個變量來區分類型

public class Model {
    public static final int FIRST_TYPE = 1;
    public static final int SECOND_TYPE = 2;
    public static final int NORMAL_TYPE = 3;

    //添加類型變量
    public int type;

    private String title;
    private String content;
    private String imgUrl;

    //set get方法
    ......
}

其次:編寫適配器

public class MultiDelegateAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {

    public MultiDelegateAdapter(@Nullable List<Model> data) {
        super(data);
        setMultiTypeDelegate(new MultiTypeDelegate<Model>() {
            @Override
            protected int getItemType(Model entity) {
                //根據你的實體類來判斷佈局類型
                return entity.type;
            }
        });

        getMultiTypeDelegate()
                .registerItemType(Model.FIRST_TYPE, R.layout.first_type_layout)
                .registerItemType(Model.SECOND_TYPE, R.layout.second_type_layout)
                .registerItemType(Model.NORMAL_TYPE, R.layout.item_rv);
    }

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        switch (helper.getItemViewType()) {
            case Model.FIRST_TYPE:
                Log.i("tag", "FIRST_TYPE===============" + helper.getLayoutPosition());
                break;
            case Model.SECOND_TYPE:
                Log.i("tag", "SECOND_TYPE===============" + helper.getLayoutPosition());
                break;
            case Model.NORMAL_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getTitle())
                        .setText(R.id.tv_content, item.getContent());
                break;
        }
    }
}

最後:在Activity中使用該適配器

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas;
    private MultiDelegateAdapter adapter;

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

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模擬的假數據(實際開發中當然是從網絡獲取數據)
        datas = new ArrayList<>();
        Model model;
        for (int i = 0; i < 30; i++) {
            model = new Model();
            model.setTitle("我是第" + i + "條標題");
            model.setContent("第" + i + "條內容");

            //這裏隨機給某一條目設置不同佈局類型
            if (i == 3 || i == 10 || i == 13) {
                model.setType(Model.FIRST_TYPE);
            } else if (i == 5 || i == 8 || i == 20) {
                model.setType(Model.SECOND_TYPE);
            } else {
                model.setType(Model.NORMAL_TYPE);
            }
            
            datas.add(model);
        }


        //創建佈局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //創建適配器
        adapter = new MultiDelegateAdapter(datas);

        //給RecyclerView設置適配器
        recyclerView.setAdapter(adapter);
    }
}
  • OK,運行結果應該是:第4、11、14條目顯示的是同一種類型佈局,第6、9、21條目顯示的是另一種類型佈局,其它條目顯示的又是另一種類型佈局。

十、設置空佈局

  • 就一個方法,沒有數據時就默認顯示該佈局:
// 沒有數據的時候默認顯示該佈局
mQuickAdapter.setEmptyView(getView());
  • 設置空佈局就一個方法,但是我還是單獨放在一個章節裏來講也是有原因的:

1、在我寫demo的時候,設置空佈局時就碰上一個bug,即使我所有的佈局中的相關控件高度都設置成填充父窗體,但是顯示時並沒有填充整個界面,而僅僅是包裹內容;
2、但是當我在我現在開發的項目中設置空佈局時又確實是填充整個父窗體,這就讓我很迷惑了,狂躁了好久,看了好多次demo代碼也沒找到原因,最後還是沒有找到原因.......
3、當然,因爲我也是一個Android新人,有很多地方我都需要繼續努力學習,或許只是一個很簡單、很低級的錯誤導致沒有填充父窗體,如果看到這篇文章的朋友你也遇到過相似的問題,而且又正好知道原因,還希望你能多多指教。

十一、添加拖拽、滑動刪除

  • 本人測試了一下,拖拽交換位置效果不錯,但是滑動刪除效果我測試的是有問題的,條目顯示會錯亂,被刪除的條目還會出現空白條目現象.......(疑惑)不知道是不是我打開方式不對???

  • 拖拽和滑動刪除的回調方法:

OnItemDragListener onItemDragListener = new OnItemDragListener() {
    @Override
    public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){}
    @Override
    public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
    @Override
    public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
}

OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {
    @Override
    public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {}
    @Override
    public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {}
    @Override
    public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {}
};
  • adapter需要繼承BaseItemDraggableAdapter:
public class ItemDragAdapter extends BaseItemDraggableAdapter<String, BaseViewHolder> {
    public ItemDragAdapter(List data) {
        super(R.layout.item_draggable_view, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, String item) {
        helper.setText(R.id.tv, item);
    }
}
  • Activity使用代碼:
mAdapter = new ItemDragAdapter(mData);

ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);

// 開啓拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);

// 開啓滑動刪除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);
  • 默認不支持多個不同的 ViewType 之間進行拖拽,如果開發者有所需求:

重寫 ItemDragAndSwipeCallback 裏的onMove()方法,return true即可

十二、樹形列表

  • 本人表示不想貼出demo了,因爲我沒嘗試着寫demo試試,看文檔說明感覺和多佈局貌似是一個套路....(感覺是...)

  • 例子:三級菜單

// if you don't want to extent a class, you can also use the interface IExpandable.
// AbstractExpandableItem is just a helper class.
public class Level0Item extends AbstractExpandableItem<Level1Item> {...}
public class Level1Item extends AbstractExpandableItem<Person> {...}
public class Person {...}
  • adapter需要繼承BaseMultiItemQuickAdapter
public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter<MultiItemEntity, BaseViewHolder> { 
    public ExpandableItemAdapter(List<MultiItemEntity> data) {    
        super(data);
        addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0);   
        addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1);    
        addItemType(TYPE_PERSON, R.layout.item_text_view);
    }
    @Override
    protected void convert(final BaseViewHolder holder, final MultiItemEntity item) {
        switch (holder.getItemViewType()) {
        case TYPE_LEVEL_0:
            ....
            //set view content
           holder.itemView.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   int pos = holder.getAdapterPosition();
                   if (lv0.isExpanded()) { 
                       collapse(pos);
                   } else {
                       expand(pos);
                   }
           }});
           break;
        case TYPE_LEVEL_1:
           // similar with level 0
           break;
        case TYPE_PERSON:
           //just set the content
           break;
    }
}
  • 開啓所有菜單:
adapter.expandAll();
  • 刪除某一個item(添加和修改的思路是一樣的)
// 獲取當前父級位置
 int cp = getParentPosition(person);
// 通過父級位置找到當前list,刪除指定下級
 ((Level1Item)getData().get(cp)).removeSubItem(person);
// 列表層刪除相關位置的數據
 getData().remove(holder.getLayoutPosition());
// 更新視圖
 notifyDataSetChanged();

十三、自定義ViewHolder

  • 需要繼承BaseViewHolder
public class MovieViewHolder extends BaseViewHolder 
  • 然後修改adapter的第二個泛型爲自定義的ViewHolder
public class DataBindingUseAdapter extends BaseQuickAdapter<Movie, DataBindingUseAdapter.MovieViewHolder>
  • 注意:需要單獨建一個外部類繼承BaseViewHolder,否則部分機型會出現ClassCastException,如果是內部類的構造方法要是public,定義的那個類也最好是public。

十三、擴展框架

結束語

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章