最近在做一个屏保功能,需要支持如图的上滑关闭功能。
因为屏保是可以左右滑动切换的,内部是一个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