基于前一篇 https://blog.csdn.net/jzlhll123/article/details/135222910的研究成果,进一步搞的更好看一点:
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.EdgeEffect;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
/**
* @author au
* @date :2023/12/26 14:42
* @description: 因为RecyclerView内部做了边缘(edge)效果处理,
* 在1.2.1和1.3.2对于边缘处理不同,进而导致parentView无法监听到Nest事件。
* 研究许久,如果想要下拉刷新的嵌套RecyclerView的Layout,则禁用内部的RecyclerView的edge效果即可。
*/
public class NoEdgeEffect extends EdgeEffect {
public NoEdgeEffect(Context context) {
super(context);
}
@RequiresApi(api = Build.VERSION_CODES.S)
public NoEdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setSize(int width, int height) {}
@Override
public boolean isFinished() {
return true;
}
@Override
public void onPull(float deltaDistance) {}
@Override
public void onPull(float deltaDistance, float displacement) {}
@Override
public float onPullDistance(float deltaDistance, float displacement) {
return 0f;
}
@Override
public void onRelease() {}
@Override
public void onAbsorb(int velocity) {}
@Override
public boolean draw(Canvas canvas) {
return false;
}
}
import android.content.Context;
import android.util.AttributeSet;
import android.widget.EdgeEffect;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
/**
* @author au
* @date 2024/8/7 11:54
* 研究许久,如果想要下拉刷新的嵌套RecyclerView的Layout,则禁用内部的RecyclerView的edge效果即可。
* rcv1.3.2的库,额外处理了edge consume掉了nest的距离。因此,我们这里搞一个假的空Effect进去,也符合我们
* 下拉刷新的本质.
*/
public class NoTopEffectRecyclerView extends RecyclerView {
public NoTopEffectRecyclerView(@NonNull Context context) {
super(context);
init(context);
}
public NoTopEffectRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public NoTopEffectRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setEdgeEffectFactory(new NoEdgeTopEffectFactory(context));
}
public static final class DefaultEdgeEffectFactory extends EdgeEffectFactory {
@NonNull
@Override
public EdgeEffect createEdgeEffect(@NonNull RecyclerView view, int direction) {
return super.createEdgeEffect(view, direction);
}
}
public static final class NoEdgeTopEffectFactory extends EdgeEffectFactory {
private final NoEdgeEffect effect;
private final DefaultEdgeEffectFactory defaultFactory = new DefaultEdgeEffectFactory();
public NoEdgeTopEffectFactory(Context context) {
this.effect = new NoEdgeEffect(context);
}
@NonNull
@Override
protected EdgeEffect createEdgeEffect(@NonNull RecyclerView view, int direction) {
if (direction == EdgeEffectFactory.DIRECTION_TOP) {
return effect;
}
return defaultFactory.createEdgeEffect(view, direction);
}
}
}
通过使用上述代码,我们只禁用了顶部的下滑动作,其他动作保留。如果你是横向的,可以自行参考。
这样recyclerView的效果比较好看。往下拉,没有微微的拉伸动作。而到了底部往上继续拉有微微的拉动效果。
同时,还可以套入到NestScrollingChild和Parent的框架中继续分发事件。从而制作出下拉刷新的逻辑。因为这个拉伸效果,其实就是代表了recyclerView消费(consumed)掉了y的距离。而通过上述代码禁掉了顶部的拉动效果,事件就能分发到父控件上。
另外,附赠如果RecyclerView是在viewPager的Fragment里面,可能还会遇到容易触发viewPager的左右滑动问题:
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import com.au.module_android.utils.dp
import kotlin.math.abs
/**
* @author au
* @date :2024/8/5 18:01
* @description: 放在Viewpager里面的rcv,需要处理下左右滑动的容易触发viewPager的切换问题
*/
class InViewPageRecyclerView : NoTopEffectRecyclerView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :super(context, attrs, defStyleAttr)
private var startX = 0 //手指碰到屏幕时的 X坐标
private var startY = 0
private var mTriggerViewPagerHorzDistance:Int = 0
/**
* 设置允许父控件左右滑动的距离
*/
fun setTriggerViewPagerHorzDistance(distance:Int) {
mTriggerViewPagerHorzDistance = distance
}
private fun getTriggerViewPagerHorzDistance() : Int{
if (mTriggerViewPagerHorzDistance == 0) {
mTriggerViewPagerHorzDistance = 33.dp
}
return mTriggerViewPagerHorzDistance
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ev ?: return super.dispatchTouchEvent(null)
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
startX = ev.x.toInt()
startY = ev.y.toInt()
//按下去我们就不允许父控件接受事件
parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
val endX = ev.x.toInt()
val disX = abs(endX - startX)
val endY = ev.y.toInt()
val disY = abs(endY - startY)
/**
* 只有当x滑动的距离超过1个单位。而且小于3个竖向单位的时候,认为是横向滑动,才允许父控件接受事件
*/
if (disX > getTriggerViewPagerHorzDistance() && disY < getTriggerViewPagerHorzDistance() * 2) {
parent.requestDisallowInterceptTouchEvent(false)
}
}
else ->
//抬起手后恢复
parent.requestDisallowInterceptTouchEvent(false)
}
return super.dispatchTouchEvent(ev)
}
}
通过上述代码,来阻止事件被上层view截获,解决误触严重的问题。数值可以自行调试。
如果你是支持顶部微微拉伸效果的RecyclerView,只需要正常继承RecyclerView就好了。