基于RecyclerView下拉刷新框架拉伸效果微调

基于前一篇 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就好了。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值