本demo的使用限制:悬浮view的高度需要小于等于item的高,否则需要修改滑动逻辑。
目前Recyclerview实现item悬浮效果主流的有两种:
1、使用ItemDecoration来实现。
2、设置Recyclerview的addOnScrollListener来实现。
这里我们使用的是第二种方法来实现,个人感觉更简单点。
设置Activity的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".ScrollActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/my_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/suspend_tv"
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingRight="20dp"
android:paddingLeft="20dp"
android:gravity="center_vertical"
android:background="@color/colorAccent"
android:textSize="14sp"
android:text=""/>
</RelativeLayout>
设置item的布局:
<?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:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/item_suspend_tv"
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:gravity="center_vertical"
android:background="@color/colorAccent"
android:textSize="14sp"
android:text=""/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/item_time_tv"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text=""/>
<TextView
android:id="@+id/item_count_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
</LinearLayout>
</LinearLayout>
此处悬浮view(也就是suspend_tv)的高需要和item顶部分组view(也就是item_suspend_tv)的高设置一致。
因为只有每组数据中的第一个数据需要在item中显示分组信息,所以在adapter的onBindViewHolder中判断该item是否需要显示分组信息。
@Override
public void onBindViewHolder(@NonNull ScrollHolder holder, int position) {
MyDataBean myDataBean = dataBeans.get(position);
holder.itemSuspendTv.setText(myDataBean.time);
holder.itemTimeTv.setText(myDataBean.time);
holder.itemCountTv.setText(myDataBean.getCount() + "");
holder.itemSuspendTv.setVisibility(View.GONE);
if (position == 0) {
holder.itemSuspendTv.setVisibility(View.VISIBLE);
} else if (position > 0 && position < getItemCount() - 1) {
MyDataBean tWalletBean = dataBeans.get(position - 1);
if (tWalletBean.getTime().equals(myDataBean.getTime())) {
holder.itemSuspendTv.setVisibility(View.GONE);
} else {
holder.itemSuspendTv.setVisibility(View.VISIBLE);
}
}
}
在activity中正常初始化adapter,
给RecyclerView设置addOnScrollListener(核心代码):
myRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// 获取悬浮View的高
suspendBarHeight = suspendTv.getHeight();
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.i(TAG, "dy:" + dy);
if (datas.size() <= 1) {
return;
}
int firstItemPosition = llm.findFirstVisibleItemPosition();
//获取最顶部的item
View firstView = llm.findViewByPosition(firstItemPosition);
MyDataBean firstBean = datas.get(firstItemPosition);
int secondItemPosition = llm.findFirstVisibleItemPosition() + 1;
// 获取第二个显示的item
View secondView = llm.findViewByPosition(secondItemPosition);
MyDataBean secondBean = datas.get(secondItemPosition);
if (dy > 0) { // 上滑
// 设置dy < 200是因为当向上滑动过快时,悬浮的view会被挤上去,
if (dy < 200) {
if (secondBean.isSuspend()) {
/*
* 如果当屏幕上显示的第二个item为要悬浮的内容,并且secondViewY < suspendBarHeight,
* 也就是第二个item的顶部到达悬浮view的底部时,此时第二个item开始向上推动悬浮的view()
*/
float secondViewY = secondView.getY();
if (secondViewY < suspendBarHeight) {
float setY = secondViewY - suspendBarHeight;
suspendTv.setY(setY);
}
}
} else {
// dy >= 200时,悬浮view在顶端全部显示,
suspendTv.setY(0);
}
/*
* 当显示的第一个item的数据是要悬浮的类型,并且getY <= 0,也就是有一部分被划出屏幕,
* 此时直接显示悬浮的view,遮盖住第一个item的顶部,视觉上替代item的顶部分组部分,
* 其实此时item顶部分组部分的view已被划出屏幕
*/
if (firstBean.isSuspend()) {
if (null != firstView && firstView.getY() <= 0) {
suspendTv.setY(0);
}
}
} else { // 下拉
/*
* 如果第二个可见item是需要悬浮的数据,为什么是第二个item?因为第一个item从屏幕上面刚划出来时,
* 会被悬浮的view先遮挡住,
*/
if (secondBean.isSuspend()) {
float secondViewY = secondView.getY();
/*
* 当第二个可见item的getY小于悬浮view的高suspendBarHeight时,动态设置悬浮view的setY值,
* 此时屏幕顶部悬浮view的位置会被瞬间移到屏幕外,一点点被拉到屏幕内,
* 呈现出悬浮view从屏幕顶部被拉出来的视觉,此时我们看到的悬浮view其实是item顶部分组的view,
* */
if (secondViewY < suspendBarHeight) {
suspendTv.setY(secondViewY - suspendBarHeight);
} else {
suspendTv.setY(0);
}
} else {
suspendTv.setY(0);
}
}
suspendTv.setText(firstBean.time);
}
});
通过addOnScrollListener设置悬浮效果会有一个问题,那就是当向上滑动过快的时候,悬浮的view有时会被顶到屏幕外面,所以在这里设置了dy < 200来避免。