关于这方面的资料其实网上有不少,不过很多不懂原理的小白可能就一直会有个疑问,,到底粘带的动作是怎么完成的,本章是以Fragment+RecyclerView+置顶View完成的,不废话,直接代码解说
<?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="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants" />
<TextView
android:id="@+id/tv_float_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fafafa"
android:paddingBottom="12dp"
android:paddingLeft="11dp"
android:paddingTop="12dp"
android:text="--"
android:textColor="#303030"
android:textSize="14sp" />
</FrameLayout>
可以见到,布局很简单,其中tv_float_view
就是负责进行粘带悬浮的头部
要点1 RecyclerView.ItemDecoration
我们知道,要达到粘带的效果,那么我们就需要知道RecyclerView的滚动事件又或者item的移动事件,我们可以为RecyclerView添加一个ItemDecoration进行监听
为什么是它?
因为ItemDecoration加入到RecyclerView的时候
ItemDecoration.onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
会不断重绘你的布局,当然,你没有自定义的布局的话,也可以做其他事情,例如~~题目说的粘带头部.
要点2 ItemDecoration.onDraw的工作方式
当手指触碰RecyclerView的时候,这个onDraw会不断的读取手势(上下左右),从而一直调用onDraw,所以不建议onDraw中做些什么复杂的工作,这也就是为什么我们用悬浮层,而不是用画笔画头部.
那么,既然我们知道它是响应手势的事件,那就好说了,我们知道,控件的移动其实就是y轴的移动,而现在我要告诉你们,每当item移动的时候,参数parent里面的每一个子item的y轴坐标都被更新了,意思是,手指滑动的任何一个坐标系,每次都会被更新到item里.然后所谓的粘带就是让你的悬浮层的Y轴跟住某个item的y轴就可以完成粘带效果了,看到这里,我想大家应该知道怎么做了吧?
如果还不懂的话,童鞋就直接用我写的工具类代码吧
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by huangzongcheng
* 固定头部粘带控件
* 建议RecyclerView在FrameLayout布局内,将floatView置于FrameLayout布局最顶层,实现两个方法即可
* BUG1:当头部高度大于非头部高度,会出现提早粘带
* BUG2:当滑动太快的时候,因为粘带过程很快,会导致好像闪烁的效果
*/
public abstract class FloatHeadItemDecoraction extends RecyclerView.ItemDecoration {
View root;
/**
* @param floatView 浮动视图布局
*/
public FloatHeadItemDecoraction(View floatView) {
this.root = floatView;
}
/**
* 头部悬浮值发生改变后需要处理的事情
*
* @param postion 当前item索引
*/
public abstract void onHeadChange(int postion);
/**
* 当前索引是否头部类型
*
* @param postion 当前item索引
* @return
*/
public abstract boolean isHeadType(int postion);
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (this.root == null) {
return;
}
int beforY = 0;//上次移动的Y轴坐标
int headHeight = 0;//head的高度
int infoHeight = 0;//非head的高度
int diffHeight = 0;//head&非head之间的高差
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
int positon = parent.getChildAdapterPosition(child);
//实际移动高度
int currentY = child.getTop() + parent.getPaddingTop();
//获得头部高度
if (isHeadType(positon) && headHeight == 0) {
headHeight = child.getMeasuredHeight();
}
//获得主体信息高度
if (!isHeadType(positon) && infoHeight == 0) {
infoHeight = child.getMeasuredHeight();
}
//获得头部和主体的高度差
if (diffHeight == 0 && headHeight != 0 && infoHeight != 0) {
diffHeight = Math.abs((headHeight - infoHeight));
}
//当第二个item是头部item,开始粘带动作
if (i == 1 && isHeadType(positon)) {
//如果当前item=1的头部移动坐标属于头部的高度范围内.允许进行粘带
if (currentY >= 0 && currentY <= headHeight) {
/**
* 因为befortop的上次总移动距离=item的高度,而head和非hand的item之间是有高度差
* 所以悬浮窗的y轴要从两者的差距高度开始进行移动
*/
int y = beforY + diffHeight;
root.setY(y);
}
} else if (i == 0) {//保持顶层悬浮
root.setY(0);
}
//获得item=0的上次y轴坐标
if (i == 0) {
beforY = currentY;
onHeadChange(positon);
}
}
}
}