You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否重写SimpleOnGestureListener检测释放?抛物线滑动回弹实现疑问

实现Tinder式抛物线滑动回弹效果

你已经搭好了抛物线滑动的核心逻辑,要实现未滑满时的回弹效果,核心就是捕获用户抬手动作,再根据当前滑动距离判断是否触发平滑回弹动画。下面给你两种基于现有代码的扩展方案,都是非常实用的实现方式:

方案一:在OnTouchListener中直接处理ACTION_UP事件

你的onTouch方法目前只是把事件转发给GestureDetector,我们可以先在这里拦截ACTION_UP事件,执行完回弹逻辑后再传递事件,这样逻辑更直观:

public class MoveViewTouchListener implements View.OnTouchListener { 
    public GestureDetector mGestureDetector; 
    public View mView; 
    public double a; 
    public double b; 
    public double c; 
    // 自定义回弹阈值:比如滑动距离未超过屏幕1/3就触发回弹
    private float mReboundThreshold;

    public MoveViewTouchListener(View view, double a, double b, double c) { 
        mGestureDetector = new GestureDetector(view.getContext(), mGestureListener); 
        mView = view; 
        this.a = a; 
        this.b = b; 
        this.c = c; 
        // 初始化阈值:取当前屏幕宽度的1/3
        mReboundThreshold = view.getContext().getResources().getDisplayMetrics().widthPixels / 3f;
    } 

    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
        // 优先处理抬手事件
        if (event.getAction() == MotionEvent.ACTION_UP) {
            checkAndDoRebound();
        }
        return mGestureDetector.onTouchEvent(event); 
    } 

    // 判断是否需要回弹并执行平滑动画
    private void checkAndDoRebound() {
        float currentX = mView.getTranslationX();
        // 滑动距离未达阈值时触发回弹
        if (Math.abs(currentX) < mReboundThreshold) {
            // 使用ValueAnimator实现自然的回弹过渡
            ValueAnimator animatorX = ValueAnimator.ofFloat(currentX, 0f);
            animatorX.setDuration(300); // 动画时长300ms,可根据需求调整
            animatorX.addUpdateListener(animation -> {
                float value = (float) animation.getAnimatedValue();
                mView.setTranslationX(value);
                // 同步更新Y轴的抛物线位置
                double y = a * value * value + b * value + c;
                mView.setTranslationY((float) y);
            });
            animatorX.start();
        }
        // 若超过阈值,可添加滑出屏幕的动画逻辑(可选)
    }

    private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { 
        private float mMotionDownX, mMotionDownY; 

        @Override 
        public boolean onDown(MotionEvent e) { 
            mMotionDownX = e.getRawX() - mView.getTranslationX(); 
            mMotionDownY = e.getRawY() - mView.getTranslationY(); 
            return true; 
        } 

        @Override 
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 
            float newX = e2.getRawX() - mMotionDownX;
            mView.setTranslationX(newX); 
            // 注意:这里直接用构造函数传入的a/b/c,避免覆盖参数
            double y = a * newX * newX + b * newX + c; 
            Log.d("Chex ","X = " + newX +", Y = " + y); 
            mView.setTranslationY((float)y); 
            return true; 
        } 
    }; 
}

方案二:重写SimpleOnGestureListener的onUp方法

GestureDetector.SimpleOnGestureListener本身已经实现了OnGestureListener的所有回调,你可以直接重写onUp方法来捕获抬手动作,让逻辑更内聚:

public class MoveViewTouchListener implements View.OnTouchListener { 
    public GestureDetector mGestureDetector; 
    public View mView; 
    public double a; 
    public double b; 
    public double c; 
    private float mReboundThreshold;

    public MoveViewTouchListener(View view, double a, double b, double c) { 
        mGestureDetector = new GestureDetector(view.getContext(), mGestureListener); 
        mView = view; 
        this.a = a; 
        this.b = b; 
        this.c = c; 
        mReboundThreshold = view.getContext().getResources().getDisplayMetrics().widthPixels / 3f;
    } 

    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
        return mGestureDetector.onTouchEvent(event); 
    } 

    private void startReboundAnimation() {
        float currentX = mView.getTranslationX();
        ValueAnimator animatorX = ValueAnimator.ofFloat(currentX, 0f);
        animatorX.setDuration(300);
        animatorX.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue();
            mView.setTranslationX(value);
            double y = a * value * value + b * value + c;
            mView.setTranslationY((float) y);
        });
        animatorX.start();
    }

    private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { 
        private float mMotionDownX, mMotionDownY; 

        @Override 
        public boolean onDown(MotionEvent e) { 
            mMotionDownX = e.getRawX() - mView.getTranslationX(); 
            mMotionDownY = e.getRawY() - mView.getTranslationY(); 
            return true; 
        } 

        @Override 
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 
            float newX = e2.getRawX() - mMotionDownX;
            mView.setTranslationX(newX); 
            double y = a * newX * newX + b * newX + c; 
            Log.d("Chex ","X = " + newX +", Y = " + y); 
            mView.setTranslationY((float)y); 
            return true; 
        } 

        // 重写onUp回调,捕获用户抬手动作
        @Override
        public boolean onUp(MotionEvent e) {
            float currentX = mView.getTranslationX();
            if (Math.abs(currentX) < mReboundThreshold) {
                startReboundAnimation();
            }
            return super.onUp(e);
        }
    }; 
}

关键注意事项

  1. 避免参数覆盖:你的原代码在onScroll里重新定义了a/b/c局部变量,这会覆盖构造函数传入的参数,已经帮你修正了。
  2. 阈值自定义:可以根据产品需求调整mReboundThreshold的取值,比如改成屏幕宽度的一半,或者固定像素值。
  3. 动画平滑性:用ValueAnimator实现回弹是最自然的方式,避免直接设置translationX/Y导致的生硬跳转。
  4. 扩展滑出逻辑:如果需要完全复刻Tinder的体验,当滑动距离超过阈值时,可以添加快速滑出屏幕的动画,而不是保持当前位置。

内容的提问来源于stack exchange,提问作者Nicholas Muir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:41:20