Reference: https://www.jianshu.com/p/1e20f301272e
一、框架引入
- 先在項目的 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。
作者:猜火車_
鏈接:https://www.jianshu.com/p/1e20f301272e
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。