引言
不知道大家有没有仔细观察过支付宝的账单页面,月份为组头,账单为组的内容。账单按月份进行分组,当连续往下滑动RecyclerView,当月月份仍显示,下滑至前一月账单时,由前一月的组头顶替当前月组头,如此循环往复。
这一功能,又成为“吸顶”效果,原因无它,皆因内容变化,导致组头交替覆盖,但最顶上的组头位置不会发生任何改变,故称吸顶。
效果预览
用法
步骤一、添加第三方依赖库
依赖:app下的build.gradle文件中添加:
implementation 'com.github.donkingliang:GroupedRecyclerViewAdapter:2.3.0'
步骤二、创建实体类
实体类1:GroupEntity
/**
* 组数据的实体类
*/
public class GroupEntity {
private String header;
private String footer;
private ArrayList<ChildEntity> children;
public GroupEntity(String header, String footer, ArrayList<ChildEntity> children) {
this.header = header;
this.footer = footer;
this.children = children;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getFooter() {
return footer;
}
public void setFooter(String footer) {
this.footer = footer;
}
public ArrayList<ChildEntity> getChildren() {
return children;
}
public void setChildren(ArrayList<ChildEntity> children) {
this.children = children;
}
}
实体类2:ChildEntity
/**
* 子项数据的实体类
*/
public class ChildEntity {
private String child;
public ChildEntity(String child) {
this.child = child;
}
public String getChild() {
return child;
}
public void setChild(String child) {
this.child = child;
}
}
实体类3:GroupModel
/**
* @data on 12/1/20 4:11 PM
* @auther armStrong
* @describe 用来处理和获取:头+体+尾数据
*/
public class GroupModel {
/**
* 获取组列表数据
*
* @param groupCount 组数量
* @param childrenCount 每个组里的子项数量
* @return
*/
public static ArrayList<GroupEntity> getGroups(int groupCount, int childrenCount) {
ArrayList<GroupEntity> groups = new ArrayList<>();
for (int i = 0; i < groupCount; i++) {
ArrayList<ChildEntity> children = new ArrayList<>();
for (int j = 0; j < childrenCount; j++) {
children.add(new ChildEntity("第" + (i + 1) + "组第" + (j + 1) + "项"));
}
groups.add(new GroupEntity("第" + (i + 1) + "组头部",
"第" + (i + 1) + "组尾部", children));
}
return groups;
}
}
步骤三、布局文件
布局文件1:主布局R.layout.activity_case66
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".blog2.Case66"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/blue"
android:gravity="center"
android:text="RecyclerView吸顶效果"
android:textColor="@color/white"
android:textSize="20sp" />
<com.donkingliang.groupedadapter.widget.StickyHeaderLayout
android:id="@+id/sticky_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.donkingliang.groupedadapter.widget.StickyHeaderLayout>
</LinearLayout>
布局文件2:头布局R.layout.adapter_header
<?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:background="@color/red"
android:orientation="vertical">
<TextView
android:id="@+id/tv_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
布局文件3:内容布局R.layout.adapter_child
<?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:background="@color/white"
android:layout_marginBottom="1px"
android:orientation="vertical">
<TextView
android:id="@+id/tv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:padding="8dp"
android:textSize="16sp" />
</LinearLayout>
布局文件4:尾布局R.layout.adapter_footer
<?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:background="@color/green"
android:orientation="vertical">
<TextView
android:id="@+id/tv_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
步骤四、为RecyclerView书写Adapter
Adapter1:GroupedListAdapter
/**
* @data on 11/25/20 2:13 PM
* @auther armStrong
* @describe 这是普通的分组Adapter 每一个组都有头部、尾部和子项。
*/
public class GroupedListAdapter extends GroupedRecyclerViewAdapter {
protected 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();
}
public void clear(){
mGroups.clear();
notifyDataChanged();
}
public void setGroups(ArrayList<GroupEntity> groups){
mGroups = groups;
notifyDataChanged();
}
@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());
}
}
Adaprer2:NoFooterAdapter
/**
* @data on 12/1/20 4:38 PM
* @auther armStrong
* @describe 没有尾部的Adapter(用来做吸顶效果)
*/
public class NoFooterAdapter extends GroupedListAdapter {
public NoFooterAdapter(Context context, ArrayList<GroupEntity> groups) {
super(context, groups);
}
/**
* 返回false表示没有组尾
*
* @param groupPosition
* @return
*/
@Override
public boolean hasFooter(int groupPosition) {
return false;
}
/**
* 当hasFooter返回false时,这个方法不会被调用。
*
* @return
*/
@Override
public int getFooterLayout(int viewType) {
return 0;
}
/**
* 当hasFooter返回false时,这个方法不会被调用。
*
* @param holder
* @param groupPosition
*/
@Override
public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) {
}
}
步骤五、在Activity中书写业务逻辑
public class Case66 extends AppCompatActivity {
private RecyclerView recycler;
private StickyHeaderLayout stickyLayout;
private LinearLayoutManager layoutManager;
private NoFooterAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_case66);
initView();
initAdatper();
//设置是否吸顶。
stickyLayout.setSticky(true);
}
private void initAdatper() {
layoutManager = new LinearLayoutManager(this);
recycler.setLayoutManager(layoutManager);
adapter = new NoFooterAdapter(this, GroupModel.getGroups(10, 5));
adapter.setOnHeaderClickListener(new GroupedRecyclerViewAdapter.OnHeaderClickListener() {
@Override
public void onHeaderClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder,
int groupPosition) {
Toast.makeText(Case66.this, "组头:groupPosition = " + groupPosition,
Toast.LENGTH_LONG).show();
Log.e("eee", adapter.toString() + " " + holder.toString());
}
});
adapter.setOnChildClickListener(new GroupedRecyclerViewAdapter.OnChildClickListener() {
@Override
public void onChildClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder,
int groupPosition, int childPosition) {
Toast.makeText(Case66.this, "子项:groupPosition = " + groupPosition
+ ", childPosition = " + childPosition,
Toast.LENGTH_LONG).show();
}
});
recycler.setAdapter(adapter);
}
private void initView(){
recycler = (RecyclerView) findViewById(R.id.rv_list);
stickyLayout = (StickyHeaderLayout) findViewById(R.id.sticky_layout);
}
}