今天給大家介紹的是一個可以實現資料分組顯示的RecyclerViewAdapter:GroupedRecyclerViewAdapter。它可以很方便的實現RecyclerView的分組顯示,並且每個組都可以包含組頭、組尾和子項;可以方便實現多種Type型別的列表,可以實現如QQ聯絡人的列表一樣的列表展開收起功能等。下面先讓我們看一下它所能夠實現的一些效果:
還可以很容易的實時列表的展開收起效果:
以上展示的只是GroupedRecyclerViewAdapter能實現的一些常用效果,其實使用GroupedRecyclerViewAdapter還可以很容易的實現一些更加複雜的列表效果。在我的GroupedRecyclerViewAdapter專案的Demo中給出了上面幾種效果的實現例子,並且有詳細的註釋說明,有興趣的同學可以到我的GitHub下載原始碼。下面直接講解GroupedRecyclerViewAdapter的使用。
**1、引入依賴 ** 在Project的build.gradle在新增以下程式碼
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
複製程式碼
在Module的build.gradle在新增以下程式碼
compile 'com.github.donkingliang:GroupedRecyclerViewAdapter:1.3.0'
複製程式碼
2、繼承GroupedRecyclerViewAdapter
public class GroupedListAdapter extends GroupedRecyclerViewAdapter {
}
複製程式碼
3、實現GroupedRecyclerViewAdapter裡的方法 GroupedRecyclerViewAdapter是一個抽象類,它提供了一系列需要子類去實現的方法。
//返回組的數量
public abstract int getGroupCount();
//返回當前組的子項數量
public abstract int getChildrenCount(int groupPosition);
//當前組是否有頭部
public abstract boolean hasHeader(int groupPosition);
//當前組是否有尾部
public abstract boolean hasFooter(int groupPosition);
//返回頭部的佈局id。(如果hasHeader返回false,這個方法不會執行)
public abstract int getHeaderLayout(int viewType);
//返回尾部的佈局id。(如果hasFooter返回false,這個方法不會執行)
public abstract int getFooterLayout(int viewType);
//返回子項的佈局id。
public abstract int getChildLayout(int viewType);
//繫結頭部佈局資料。(如果hasHeader返回false,這個方法不會執行)
public abstract void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition);
//繫結尾部佈局資料。(如果hasFooter返回false,這個方法不會執行)
public abstract void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition);
//繫結子項佈局資料。
public abstract void onBindChildViewHolder(BaseViewHolder holder,
int groupPosition, int childPosition);
複製程式碼
還可是重寫GroupedRecyclerViewAdapter方法實現頭、尾和子項的多種型別item。效果就像上面的第6張圖一樣。
//返回頭部的viewType。
public int getHeaderViewType(int groupPosition);
//返回尾部的viewType。
public int getFooterViewType(int groupPosition) ;
//返回子項的viewType。
public int getChildViewType(int groupPosition, int childPosition) ;
複製程式碼
4、設定點選事件的監聽 GroupedRecyclerViewAdapter提供了對列表的點選事件的監聽方法。
//設定組頭點選事件
public void setOnHeaderClickListener(OnHeaderClickListener listener) {
mOnHeaderClickListener = listener;
}
//設定組尾點選事件
public void setOnFooterClickListener(OnFooterClickListener listener) {
mOnFooterClickListener = listener;
}
// 設定子項點選事件
public void setOnChildClickListener(OnChildClickListener listener) {
mOnChildClickListener = listener;
}
複製程式碼
注意事項:
1、對方法重寫的注意。 如果我們直接繼承RecyclerView.Adapter去實現自己的Adapter時,一般會重寫Adapter中的以下幾個方法:
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position);
public int getItemCount();
public int getItemViewType(int position);
複製程式碼
但如果是使用GroupedRecyclerViewAdapter,就一定不能去重寫這幾個方法,因為在GroupedRecyclerViewAdapter中已經對這幾個方法做了實現,而且是對實現列表分組至關重要的,如果子類重寫了這幾個方法,可能會破壞GroupedRecyclerViewAdapter的功能。 從前面給出的GroupedRecyclerViewAdapter的方法我們可以看到,這些方法其實就是對應RecyclerView.Adapter的這4個方法的,所以我們直接使用GroupedRecyclerViewAdapter提供的方法即可。 RecyclerView.Adapter中的
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
複製程式碼
對應GroupedRecyclerViewAdapter中的
//返回頭部的佈局id。(如果hasHeader返回false,這個方法不會執行)
public abstract int getHeaderLayout(int viewType);
//返回尾部的佈局id。(如果hasFooter返回false,這個方法不會執行)
public abstract int getFooterLayout(int viewType);
//返回子項的佈局id。
public abstract int getChildLayout(int viewType);
複製程式碼
這裡之所以返回的是佈局id而不是ViewHolder ,是因為在GroupedRecyclerViewAdapter專案中已經提供了一個通用的ViewHolder:BaseViewHolder。所以使用者只需要提供佈局的id即可,不需要自己去實現ViewHolder。
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(getLayoutId(mTempPosition, viewType), parent, false);
return new BaseViewHolder(view);
}
private int getLayoutId(int position, int viewType) {
int type = judgeType(position);
if (type == TYPE_HEADER) {
return getHeaderLayout(viewType);
} else if (type == TYPE_FOOTER) {
return getFooterLayout(viewType);
} else if (type == TYPE_CHILD) {
return getChildLayout(viewType);
}
return 0;
}
複製程式碼
RecyclerView.Adapter中的
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position);
複製程式碼
對應GroupedRecyclerViewAdapter中的
//繫結頭部佈局資料。(如果hasHeader返回false,這個方法不會執行)
public abstract void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition);
//繫結尾部佈局資料。(如果hasFooter返回false,這個方法不會執行)
public abstract void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition);
//繫結子項佈局資料。
public abstract void onBindChildViewHolder(BaseViewHolder holder,
int groupPosition, int childPosition);
複製程式碼
RecyclerView.Adapter中的
public int getItemCount();
複製程式碼
對應GroupedRecyclerViewAdapter中的
//返回組的數量
public abstract int getGroupCount();
//返回當前組的子項數量
public abstract int getChildrenCount(int groupPosition);
複製程式碼
RecyclerView.Adapter中的
public int getItemViewType(int position);
複製程式碼
對應GroupedRecyclerViewAdapter中的
//返回頭部的viewType。
public int getHeaderViewType(int groupPosition);
//返回尾部的viewType。
public int getFooterViewType(int groupPosition) ;
//返回子項的viewType。
public int getChildViewType(int groupPosition, int childPosition) ;
複製程式碼
2、對列表操作的注意 RecyclerView.Adapter提供了一系列對列表進行操作的方法。如:
//更新操作
public final void notifyDataSetChanged();
public final void notifyItemChanged(int position);
public final void notifyItemChanged(int position, Object payload);
public final void notifyItemRangeChanged(int positionStart, int itemCount);
public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload);
//插入操作
public final void notifyItemInserted(int position);
public final void notifyItemRangeInserted(int positionStart, int itemCount);
//刪除操作
public final void notifyItemRemoved(int position)
public final void notifyItemRangeRemoved(int positionStart, int itemCount);
複製程式碼
在GroupedRecyclerViewAdapter不建議使用RecyclerView.Adapter的任何對列表的操作方法,因為這些方法都是基於列表的操作,它的position是相對於整個列表而言的,而GroupedRecyclerViewAdapter是分組的列表,它對列表的操作應該是基於組的。同時GroupedRecyclerViewAdapter使用了組結構來維護整個列表的結構,使我們可以對列表進行組的操作,在列表發生變化時GroupedRecyclerViewAdapter需要及時對組結構進行調整,如果使用了RecyclerView.Adapter中的方法對列表進行更新,GroupedRecyclerViewAdapter可能因為無法及時調整組結構而發生異常。所以在使用中應該避免使用這些方法。GroupedRecyclerViewAdapter同樣提供了一系列對列表進行操作的方法,我們應該使用GroupedRecyclerViewAdapter所提供的方法。
//****** 重新整理操作 *****//
//通知資料列表重新整理。對應 notifyDataSetChanged();
public void notifyDataChanged();
//通知一組資料重新整理,包括組頭,組尾和子項
public void notifyGroupChanged(int groupPosition);
//通知多組資料重新整理,包括組頭,組尾和子項
public void notifyGroupRangeChanged(int groupPosition, int count);
// 通知組頭重新整理
public void notifyHeaderChanged(int groupPosition);
// 通知組尾重新整理
public void notifyFooterChanged(int groupPosition);
// 通知一組裡的某個子項重新整理
public void notifyChildChanged(int groupPosition, int childPosition);
// 通知一組裡的多個子項重新整理
public void notifyChildRangeChanged(int groupPosition, int childPosition, int count);
// 通知一組裡的所有子項重新整理
public void notifyChildrenChanged(int groupPosition);
//****** 刪除操作 *****//
// 通知所有資料刪除
public void notifyDataRemoved();
// 通知一組資料刪除,包括組頭,組尾和子項
public void notifyGroupRemoved(int groupPosition);
// 通知多組資料刪除,包括組頭,組尾和子項
public void notifyGroupRangeRemoved(int groupPosition, int count);
// 通知組頭刪除
public void notifyHeaderRemoved(int groupPosition);
// 通知組尾刪除
public void notifyFooterRemoved(int groupPosition);
// 通知一組裡的某個子項刪除
public void notifyChildRemoved(int groupPosition, int childPosition);
// 通知一組裡的多個子項刪除
public void notifyChildRangeRemoved(int groupPosition, int childPosition, int count);
// 通知一組裡的所有子項刪除
public void notifyChildrenRemoved(int groupPosition);
//****** 插入操作 *****//
// 通知一組資料插入
public void notifyGroupInserted(int groupPosition);
// 通知多組資料插入
public void notifyGroupRangeInserted(int groupPosition, int count);
// 通知組頭插入
public void notifyHeaderInserted(int groupPosition);
// 通知組尾插入
public void notifyFooterInserted(int groupPosition);
// 通知一個子項到組裡插入
public void notifyChildInserted(int groupPosition, int childPosition);
// 通知一組裡的多個子項插入
public void notifyChildRangeInserted(int groupPosition, int childPosition, int count);
// 通知一組裡的所有子項插入
public void notifyChildrenInserted(int groupPosition);
複製程式碼
3、使用GridLayoutManager的注意 如果要使用GridLayoutManager,一定要使用專案中所提供的GroupedGridLayoutManager。因為分組列表如果要使用GridLayoutManager實現網格佈局,就要保證組的頭部和尾部是要單獨佔用一行的。否則組的頭、尾可能會跟子項混著一起,造成佈局混亂。同時GroupedGridLayoutManager提供了對子項的SpanSize的修改方法,使用GroupedGridLayoutManager可以實現更多的複雜列表佈局。
//直接使用GroupedGridLayoutManager實現子項的Grid效果
GroupedGridLayoutManager gridLayoutManager = new GroupedGridLayoutManager(this, 2, adapter);
rvList.setLayoutManager(gridLayoutManager);
GroupedGridLayoutManager gridLayoutManager = new GroupedGridLayoutManager(this, 4, adapter){
//重寫這個方法 改變子項的SpanSize。
//這個跟重寫SpanSizeLookup的getSpanSize方法的使用是一樣的。
@Override
public int getChildSpanSize(int groupPosition, int childPosition) {
if(groupPosition % 2 == 1){
return 2;
}
return super.getChildSpanSize(groupPosition, childPosition);
}
};
rvList.setLayoutManager(gridLayoutManager);
複製程式碼
4、BaseViewHolder的使用 專案中提供了一個通用的ViewHolder:BaseViewHolder。提供了根據viewId獲取View的方法和對View、TextView、ImageView的常用設定方法。
//根據id獲取View
TextView textView = holder.get(R.id.tv_header);
//View、TextView、ImageView的常用設定方法。並且支援方法連綴呼叫
holder.setText(R.id.tv_header, "內容")
.setImageResource(R.id.iv_image, 資源id)
.setBackgroundRes(R.id.view,資源id);
複製程式碼
BaseViewHolder是可以通用的,在普通的Adapter中也可以使用,可以省去每次都要建立ViewHolder的麻煩。
下面看一個簡單的使用列子:
public class GroupedListAdapter extends GroupedRecyclerViewAdapter {
private ArrayList<GroupEntity> mGroups;
public GroupedListAdapter(Context context, ArrayList<GroupEntity> groups) {
super(context);
mGroups = groups;
}
@Override
public int getGroupCount() {
return mGroups == null ? 0 : mGroups.size();
}
@Override
public int getChildrenCount(int groupPosition) {
ArrayList<ChildEntity> children = mGroups.get(groupPosition).getChildren();
return children == null ? 0 : children.size();
}
@Override
public boolean hasHeader(int groupPosition) {
return true;
}
@Override
public boolean hasFooter(int groupPosition) {
return true;
}
@Override
public int getHeaderLayout(int viewType) {
return R.layout.adapter_header;
}
@Override
public int getFooterLayout(int viewType) {
return R.layout.adapter_footer;
}
@Override
public int getChildLayout(int viewType) {
return R.layout.adapter_child;
}
@Override
public void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition) {
GroupEntity entity = mGroups.get(groupPosition);
holder.setText(R.id.tv_header, entity.getHeader());
}
@Override
public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) {
GroupEntity entity = mGroups.get(groupPosition);
holder.setText(R.id.tv_footer, entity.getFooter());
}
@Override
public void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition) {
ChildEntity entity = mGroups.get(groupPosition).getChildren().get(childPosition);
holder.setText(R.id.tv_child, entity.getChild());
}
}
複製程式碼
public class GroupedListActivity extends AppCompatActivity {
private TextView tvTitle;
private RecyclerView rvList;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_group_list);
tvTitle = (TextView) findViewById(R.id.tv_title);
rvList = (RecyclerView) findViewById(R.id.rv_list);
tvTitle.setText(R.string.group_list);
rvList.setLayoutManager(new LinearLayoutManager(this));
GroupedListAdapter adapter = new GroupedListAdapter(this, GroupModel.getGroups(10, 5));
adapter.setOnHeaderClickListener(new GroupedRecyclerViewAdapter.OnHeaderClickListener() {
@Override
public void onHeaderClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder,
int groupPosition) {
Toast.makeText(GroupedListActivity.this, "組頭:groupPosition = " + groupPosition,
Toast.LENGTH_LONG).show();
}
});
adapter.setOnFooterClickListener(new GroupedRecyclerViewAdapter.OnFooterClickListener() {
@Override
public void onFooterClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder,
int groupPosition) {
Toast.makeText(GroupedListActivity.this, "組尾:groupPosition = " + groupPosition,
Toast.LENGTH_LONG).show();
}
});
adapter.setOnChildClickListener(new GroupedRecyclerViewAdapter.OnChildClickListener() {
@Override
public void onChildClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder,
int groupPosition, int childPosition) {
Toast.makeText(GroupedListActivity.this, "子項:groupPosition = " + groupPosition
+ ", childPosition = " + childPosition,
Toast.LENGTH_LONG).show();
}
});
rvList.setAdapter(adapter);
}
}
複製程式碼
這是我在專案Demo中的實現的其中一個列子。效果是上面的圖1。想看原始碼和更多的使用列子的歡迎訪問我的GitHub。
頭部懸浮吸頂功能 應一些朋友的反饋,我在1.2.0版本中新加了列表的頭部懸浮吸頂功能。使用起來非常的簡單,只需要用框架裡提供的StickyHeaderLayout包裹一下你的RecyclerView就可以了。當然,你需要使用GroupedRecyclerViewAdapter才能看到效果。
<!-- 用StickyHeaderLayout包裹RecyclerView -->
<com.donkingliang.groupedadapter.widget.StickyHeaderLayout
android:id="@+id/sticky_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.donkingliang.groupedadapter.widget.StickyHeaderLayout>
複製程式碼
StickyHeaderLayout提供了一個設定是否顯示懸浮吸頂的方法。
//是否吸頂,預設為true。
stickyLayout.setSticky(true);
複製程式碼
對懸浮吸頂功能的更多介紹,請看我的另一篇文章:《Android RecyclerView實現頭部懸浮吸頂效果》 效果圖:
使用DataBinding
GroupedRecyclerViewAdapter在1.3.0版本加入了對DataBinding的支援。要想在Adapter中使用DataBinding,只需要在GroupedRecyclerViewAdapter的建構函式的useBinding引數傳true即可。
public class BindingAdapter extends GroupedRecyclerViewAdapter {
public BindingAdapter(Context context) {
//只要在這裡傳true,Adapter就會用DataBinding的方式載入列表的item佈局。預設為false。
super(context, true);
}
}
複製程式碼
然後同過BaseViewHolder的getBinding()就可以獲取到item對應的ViewDataBinding物件。
@Override
public void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition) {
//獲取ViewDataBinding物件。
AdapterBindingHeaderBinding binding = holder.getBinding();
}
@Override
public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) {
//獲取ViewDataBinding物件。
AdapterBindingFooterBinding binding = holder.getBinding();
}
@Override
public void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition) {
//獲取ViewDataBinding物件。
AdapterBindingChildBinding binding = holder.getBinding();
}
複製程式碼