安卓Activity上滑关闭效果实现

发布于:2024-03-29 ⋅ 阅读:(16) ⋅ 点赞:(0)

最近在做一个屏保功能,需要支持如图的上滑关闭功能。

因为屏保是可以左右滑动切换的,内部是一个viewpager

做这个效果的时候,关键就是要注意外层拦截触摸事件时,需要有条件的拦截,不能影响到内部viewpager的滑动处理。

以下是封装好的自定义view,继承自FrameLayout:



import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

public class SlideCloseFrameLayout extends FrameLayout {

    /**
     * 滑动监听器
     */
    public interface OnSlideCloseListener {
        /**
         * 滑动开始时调用
         */
        void onStartSlide();

        /**
         * 滑动结束&动画结束时调用,isClose为true表示滑动关闭,为false表示滑动恢复原位
         * @param isClose
         */
        void onStopSlide(boolean isClose);
    }

    private OnSlideCloseListener onSlideCloseListener;

    private static final String TAG = "SlideCloseFrameLayout";
    private float downY = 0; // 记录手指按下时的Y坐标
    private boolean isSlideAction = false; // 标记是否为滑动关闭动作
    private VelocityTracker velocityTracker = null; // 速度跟踪器
    private float lastTranslationY = 0; // 记录上一次的TranslationY值,用于滑动时的位置更新

    public SlideCloseFrameLayout(Context context) {
        super(context);
    }

    public SlideCloseFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SlideCloseFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        try {
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    downY = event.getRawY();
                    if (downY > getHeight() - getHeight() / 5f) {
                        initVelocityTracker();
                        velocityTracker.addMovement(event);
                        return false; // 拦截事件
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    velocityTracker.addMovement(event);
                    velocityTracker.computeCurrentVelocity(1000);
                    float xVelocity = velocityTracker.getXVelocity();
                    float yVelocity = velocityTracker.getYVelocity();
                    if (Math.abs(yVelocity) > ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity()
                            && Math.abs(yVelocity) > Math.abs(xVelocity)) {
                        // 如果超过最小判定距离,并且Y轴速度大于X轴速度,才视为纵向滑动
                        if (yVelocity < 0) {
                            // 向下滑动
                            if (onSlideCloseListener != null) {
                                onSlideCloseListener.onStartSlide();
                            }
                            isSlideAction = true;
                            return true;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    isSlideAction = false;
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            if (isSlideAction) {
                velocityTracker.addMovement(event);
                int action = event.getAction();
                switch (action) {
                    case MotionEvent.ACTION_MOVE:
                        float moveDistance = event.getRawY() - downY;
                        if (moveDistance < 0) { // 仅当向上滑动时处理
                            lastTranslationY = moveDistance;
                            this.setTranslationY(moveDistance);
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        velocityTracker.computeCurrentVelocity(1000);
                        float velocityY = velocityTracker.getYVelocity();
                        if (Math.abs(velocityY) > 1000 || Math.abs(lastTranslationY) > getHeight() / 5f) {
                            slideUpAndExit();
                        } else {
                            slideBack();
                        }
                        releaseVelocityTracker();
                        isSlideAction = false;
                        break;
                }
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.onTouchEvent(event);
    }

    public boolean isSlideAction() {
        return isSlideAction;
    }

    public OnSlideCloseListener getOnSlideCloseListener() {
        return onSlideCloseListener;
    }

    public void setOnSlideCloseListener(OnSlideCloseListener onSlideCloseListener) {
        this.onSlideCloseListener = onSlideCloseListener;
    }

    private void initVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        } else {
            velocityTracker.clear();
        }
    }

    private void releaseVelocityTracker() {
        if (velocityTracker != null) {
            velocityTracker.recycle();
            velocityTracker = null;
        }
    }

    private void slideUpAndExit() {
        // 执行上移退出动画
        TranslateAnimation exitAnimation = new TranslateAnimation(0, 0, getTranslationY(), -getHeight());
        exitAnimation.setDuration(300);
        exitAnimation.setFillAfter(false);
        exitAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                // 动画结束后的操作
                setVisibility(View.GONE); // 隐藏或其他逻辑
                if (onSlideCloseListener != null) {
                    onSlideCloseListener.onStopSlide(true);
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        startAnimation(exitAnimation);
        this.setTranslationY(0); // 重置TranslationY值
    }

    private void slideBack() {
        // 使用属性动画使视图回到原位
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationY", getTranslationY(), 0);
        animator.setDuration(300);
        animator.start();
        animator.addListener(new Animator.AnimatorListener(){
            @Override
            public void onAnimationStart(@NonNull Animator animation) {

            }

            @Override
            public void onAnimationEnd(@NonNull Animator animation) {
                if (onSlideCloseListener != null) {
                    onSlideCloseListener.onStopSlide(false);
                }
            }

            @Override
            public void onAnimationCancel(@NonNull Animator animation) {
                if (onSlideCloseListener != null) {
                    onSlideCloseListener.onStopSlide(false);
                }
            }

            @Override
            public void onAnimationRepeat(@NonNull Animator animation) {

            }
        });
    }
}

Activity使用时,只需要把根View设置为这个自定义view,然后透明主题,透明背景,同时关闭Activity的进入退出动画,便可以实现如图效果了。

嵌套使用时,不会影响到内部的Viewpager或其他可滑动view

本文含有隐藏内容,请 开通VIP 后查看