RecycleView多種布局顯示
1.前言
- 我們知道ListView多種布局顯示用到兩個方法一個getItemViewType和getViewTypeCount方法。
- getitemViewType方法告訴ListView我在第幾個position展示哪種布局
- getViewTypeCount方法告訴ListView我有多少種布局
- 那么RecycleView如何實現多布局顯示呢?其實這個和ListView原理一樣,我們待會說,先來了解下實現RecycleView.Adapter會有哪幾個方法要重寫
- getItemCount()告訴RecycleView我有多少個item數量
- onCreateViewHolder()創建自己的holder并返回
- onBindViewHolder()綁定holder
- 創建自己的holder只需繼承成RecycleView.ViewHolder即可(這一句不是重寫adapter的方法,而是在繼承RecycleView.Adapter的時候需要指定泛型,而這個泛型就是你的ViewHolder)。
2.多布局顯示
RecycleView.Adapter也提供了getItemViewType方法,此方法和ListView加載多布局一樣。我們只要在該方法中判斷某個位置下返回某一種類型的布局即可。比如這樣給RecycleView加頭布局和腳布局(以下代碼是偽代碼):
-
你得告訴RecycleView你要加載的item數量
public int getItemCount() { return mDatas.size()+2; }
在getItemViewType中根據不同位置的position返回不同布局類型
if(position==0){
return 頭布局類型;
}else if(position==getitemCount()-1){
return 腳布局類型;
}else {
return 默認布局類型;
}
-
在onCreateViewHolder(ViewGroup parent, intviewType)根據不同類型的type來返回不同的view給自己的ViewHolder。FooterView和SwitchView如何來的,其實是自己的adapter提供兩個方法setHeaderView()和setFooterView通過外界傳遞進來的。
public ListHolder onCreateViewHolder(ViewGroup parent, intviewType) { View root =null; if(viewType ==頭布局類型) { root =mHeaderView; }else if(viewType==腳布局類型){ root=mFooterView; }else{ root = View.inflate(context,R.layout.list_item,null); } return newListHolder(root,viewType); }
-
在自己的ViewHolder中進行處理如果是頭布局或者腳布局直接返回
public static class ListHolderextends RecyclerView.ViewHolder{ TextView tv; publicListHolder(View root, intviewType) { super(root); if(viewType==頭布局類型){ return ; } if(viewType==腳布局類型){ return ; } tv= (TextView)root.findViewById(R.id.item); }
-
在onBindViewHolder(ListHolder holder, intposition)方法中綁定數據
- 綁定View,這里是根據返回的這個position的類型,從而進行綁定的,HeaderView和FooterView就不綁定了
public void onBindViewHolder(ListHolder holder, intposition) {
int itemViewType = getItemViewType(position);
if(itemViewType ==頭布局類型) {
return;
}else if(itemViewType ==腳布局類型) {
return;
}else{
//這里注意因為加了一個頭布局position-1才是正確的數據
holder.tv.setTag(position-1);
holder.tv.setText(mDatas.get(position -1));
}
}
原理講完了,那么接下來就應該講講實際的東西了。
3.需求
- 一般情況下,RecycleView加載多布局就是頭部一個輪播圖腳部一個加載更多,那么今天帶給大家是RecycleView四種布局加載,為什么是四種布局呢,其實和以上三種布局原理是一樣的,只是為了多說一下,recycleView切換視圖列數。
請自動忽略圖丑
請自動忽略圖丑
-
我們分析下上面給出的兩張圖
- 藍色背景是headerView
- 紅色背景用來切換列表我們這里就叫做swichView
- 綠色部分就是我們的數據布局了
- 藍色部分為FooterView
要達到這種布局,那么首先你得在你的adapter中getitemCount中返回數據長度+3
在getItemViewType方法中根據position返回不同類型布局 ......和上面的三種布局一致這里就不多說了。
主要講解如何監聽RecycleView滑動到底部自定加載數據呢?如何切換item的列數呢?
4.滑動監聽
- 如何監聽RecycleView滑動到底部自定加載數據?
- RecycleView有個addOnScrollListener方法,此方法接受一個OnScrollListener的子類并重寫onScrolled,當RecycleView滑動的時 候會回調onScrolled方法
- onScrolled(RecyclerView recyclerView, int dx, int dy) 參數二:水平滾動距離,參數三:豎直滾動距離
- 那我們如何實現呢?
- 自定義一個類EndLessOnScrollListener 繼承RecyclerView.OnScrollListener重寫onScrolled方法。完整代碼如下:
public abstract class EndLessOnScrollListener extends RecyclerView.OnScrollListener{
public static final String TAG =EndLessOnScrollListener.class.getName();
private GridLayoutManager gridLayoutManager;
//已經加載出來的Item的數量
private int totalItemCount;
//主要用來存儲上一個totalItemCount
private int previousTotal = 0;
//在屏幕上可見的item數量
private int visibleItemCount;
//在屏幕可見的Item中的第一個
private int firstVisibleItem;
//是否正在上拉數據
private boolean loading = true;
//當前頁,從1開始
private int currentPage =1;
public EndLessOnScrollListener(GridLayoutManager gridLayoutManager) {
this.gridLayoutManager = gridLayoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = gridLayoutManager.getItemCount();
firstVisibleItem = gridLayoutManager.findFirstVisibleItemPosition();
if(loading){
if(totalItemCount > previousTotal){
//說明數據已經加載結束
loading = false;
previousTotal = totalItemCount;
}
}
/**
* 當不是正在加載時并且
* totalItemCount-visibleItemCount得到的值表示已經滾出和未滑入的總數,
* firstVisibleItem 也可以表示已經滾出屏幕的item個數
* 其實也可以寫成這樣
* int i = totalItemCount - visibleItemCount; i就是滾出和未滾入的和
* i-firstVisibleItem得到的是未滾入的 如果未滾入=0或者小于0說明已經滾動到底部了
* if(!loading&&i-firstVisibleItem<=0){
* currentPage ++;
* onLoadMore(currentPage);
* loading = true;
* }
*/
if (!loading && totalItemCount-visibleItemCount <= firstVisibleItem){
currentPage ++;
onLoadMore(currentPage);
loading = true;
}
}
/**
* 提供一個抽象方法,在Activity中監聽到這個EndLessOnScrollListener
* 并且實現這個方法
*/
public abstract void onLoadMore(int currentPage);
}
}
-
如何使用呢?
rec.addOnScrollListener(new EndLessOnScrollListener(gridLayoutManager) { @Override public void onLoadMore(int currentPage) { footerViewInTextView.setText("正在加載請稍后..."); getData(false); } });
因為要模擬網絡請求,這里用的是rxjava timer操作符來模擬耗時,count用來模擬數據加載完畢
private void getData(final boolean falg) {
if(refreshSubscribe!=null&&!refreshSubscribe.isUnsubscribed()){
refreshSubscribe.unsubscribe();
}
if(count==3){
footerViewInTextView.setText("沒有更多數據了");
return ;
}
if(falg){
swRefresh.setRefreshing(true);
}
refreshSubscribe = Observable.timer(3, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Long>() {
@Override
public void onCompleted() {
for (int x = 30*count; x < 30*(1+count); x++) {
list.add("item" + x);
}
if(falg){
swRefresh.setRefreshing(false);
}
count++;
notifyData();
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long aLong) {
}
});
}
5.更改布局列數
寫之前先來看一下動圖:
實現原理很簡單:
-
adapter定義兩種類型
- 一列類型
- 三列類型
默認加載三列類型那么可以提供一個變量來默認加載三列類型,提供外界一個可以設置類型的方法。用來更改列數
-
在getItemViewType中在返回默認布局的時候再次判斷下是三列還是一列
public int getItemViewType(int position) { if (position == 0){ //第一個item應該加載Header return TYPE_HEADER; //第二個選擇切換布局 }else if(position==1){ return TYPE_SWITCHER; }if (position == getItemCount()-1){ //最后一個,應該加載Footer return TYPE_FOOTER; }else { //如果是三列 if(showOneOrThree==TYPE_SHOW_THREE){ return TYPE_SHOW_THREE; }else { //一列 return TYPE_SHOW_ONE; } } }
-
同樣在onBindViewHolder中判斷類型,測試點擊的是哪一種列數
public void onBindViewHolder(ListHolder holder, int position) { int itemViewType = getItemViewType(position); if (itemViewType == TYPE_HEADER) { return; } else if (itemViewType == TYPE_SWITCHER) { return; } else if (itemViewType == TYPE_FOOTER) { return; } else { holder.tv.setTag(position-2); holder.tv.setText(mDatas.get(position - 2)); if(itemViewType==TYPE_SHOW_ONE){ holder.tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Integer tag = (Integer) v.getTag(); Toast.makeText(context,"一行單列:"+mDatas.get(tag),Toast.LENGTH_SHORT).show(); } }); }else { holder.tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Integer tag = (Integer) v.getTag(); Toast.makeText(context,"一行三列:"+mDatas.get(tag),Toast.LENGTH_SHORT).show(); } }); } } }
外界更改并刷新
//三列
to_gridview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (myAdapter != null) {
int spanSize = myAdapter.getShowOneOrThree();
//一列變三列,三列就不管
if (spanSize == RECYCLEVIEW_SHOW_ONE) {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0) {
return 3;
} else if (position == 1) {
return 3;
} else if (myAdapter.getItemCount() - 1 == position) {
return 3;
} else {
return 1;
}
}
});
//更改狀態
myAdapter.setShowOneOrThree(RECYCLEVIEW_SHOW_THREE);
//刷新視圖
myAdapter.notifyItemRangeChanged(3, myAdapter.getItemCount());
}
}
}
});
//一列
to_listview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (myAdapter != null) {
int spanSize = myAdapter.getShowOneOrThree();
//三列變一列,一列就不管
if (spanSize == RECYCLEVIEW_SHOW_THREE) {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0) {
return 3;
} else if (position == 1) {
return 3;
} else if (myAdapter.getItemCount() - 1 == position) {
return 3;
} else {
return 3;
}
}
});
//更改狀態
myAdapter.setShowOneOrThree(RECYCLEVIEW_SHOW_ONE);
//刷新視圖
myAdapter.notifyItemRangeChanged(3, myAdapter.getItemCount());
}
}
}
});
這里要說下gridLayoutManager.setSpanSizeLookup方法,此方法的作用是用來確定一個item占用幾列。因為我們默認用的是gridViewManager
來加載三列的,所以原始視圖是三列。
- 當position==0時 是頭布局,頭布局寬度是和屏幕一樣寬的,占用三列
- position==1時,是切換列數布局。同樣占三列
- position==myAdapter.getItemCount() - 1 腳布局,占三列
- 其余根據所要切換的列數來返回。