使用ViewDragHelper实现Activity侧滑退出

本文介绍了如何利用Android的ViewDragHelper实现Activity的侧滑退出功能。通过创建自定义View并设置ViewDragHelper Callback,监听滑动手势,并在回调中处理滑动逻辑。文中详细讲解了关键代码实现,包括触摸事件处理、拖拽模式设置、界面刷新以及在MainActivity和样式设置中的应用。最后提供完整项目代码供参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

日常使用app的过程中,可以发现很多app都具备侧滑退出当前界面的功能,就像微信、贴吧极速版之类的。

这种操作对于提升使用体验还是有一些效果的,下面就来看一下,怎么通过ViewDragHelper去实现这样的效果。

关于ViewDragHelper,这里不做太多介绍,简单来说它是由V4包提供的用来帮助你更轻松的处理复杂的手势。

好了,下面是具体的实现,首先,创建一个View继承一个布局:

public class SwipeOutView extends LinearLayout {

    private ViewDragHelper viewDragHelper;
    private Point currentPoint = new Point();   //用来表示当前位置

    public SwipeOutView(Context context) {
        this(context, null);
    }

    public SwipeOutView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwipeOutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        viewDragHelper = ViewDragHelper.create(this, 1.0f, viewDragCallBack);
    }
}

在ViewDragHelper.create()方法中三个参数分别如下:

  • ViewGroup forParent:使用ViewDragHelper所指定的父布局,有了父布局,才能对子布局或者控件进行操作。
  • float sensitivity:用于设置touchSlop的距离,表示系统判定的有效滑动距离,这个值越大,那么touchSlop就越小。
  • ViewDragHelper.Callback cb:这个就是最重要的参数了,这个回调就是我们处理手势的核心。
 helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));

接下来,我们实现回调方法:

    private ViewDragHelper.Callback viewDragCallBack = new ViewDragHelper.Callback() {
        //返回true表示捕获相关View,可以根据第一个参数child决定捕获哪个View。
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return false;
        }

        //获取水平方向的坐标,left表示child的x轴坐标(相对于ViewGroup)
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //getWidth():viewGroup的遍历每个子view,子view的layout()方法测量的结果。测量方式:getWidth=子布局右侧-子布局左侧;
            // getMeasuredWidth():viewGroup的遍历每个子view,子view的最近一次调用measure()方法测量后得到的,就是View的宽度。
            if (left < 0){
                left = 0;
            }
            currentPoint.x = left;
            return left;
        }

        //获取垂直方向的坐标,这里返回0用于阻止页面上下移动
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            currentPoint.y = top;
            return 0;
        }

        //当拖拽的View被释放后的回调
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (viewDragHelper.isEdgeTouched(ViewDragHelper.EDGE_LEFT)){
                //这里表示如果滑动距离超过屏幕二分之一,就滑到消失,否则当作没有滑动
                if (currentPoint.x > getWidth() / 2){
                    viewDragHelper.settleCapturedViewAt(getWidth(), 0);
                } else {
                    viewDragHelper.settleCapturedViewAt(0, 0);
                }
            }
            currentPoint.x = 0;
            currentPoint.y = 0;
            invalidate();   //进行刷新

        }

        //开始边缘拖拽的回调
        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            //这里去进行控件捕获,捕获对象是SwipeOutView的第一个子布局
            viewDragHelper.captureChildView( getChildAt(0), pointerId);
        }

        //当控件停止时的位置
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            //表示滑动完成
            if (left >= getWidth()){
                swipeCallBack .onFinish();
            }
        }
    };

上面的方法中,我们在最后的onViewPositionChanged调用了一个回调的方法,这个方法可以用来退出Activity,首先创建这个回调接口:

public interface SwipeCallBack {
    void onFinish();
}

在SwipeOutView中添加:

    private SwipeCallBack swipeCallBack;

    public SwipeOutView setSwipeCallBack(SwipeCallBack swipeCallBack) {
        this.swipeCallBack = swipeCallBack;
        return this;
    }

当我们想通过滑动退出Activity的时候去实现这个接口就可以了。

不过这个时候当然是不能滑动的,毕竟我们连拦截事件和触摸事件都没处理呢:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return viewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }

同时不要忘了设置拖拽模式:

viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);

以及重写computeScroll()方法:

    @Override
    public void computeScroll() {
        if (viewDragHelper.continueSettling(true)){
            invalidate();
        }
    }

通过在computeScroll()中调用invalidate()方法,可以在滑动的时候对界面事实刷新,如果没有重写这个方法,你会发现,滑到一半松手,界面是会停在你松手的地方的。

接下来是主界面的布局以及代码了:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.example.thefirstgamenotebook.testforviewdraghelper.SwipeOutView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipeOutView"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_red_light"
            android:padding="20dp"
            android:text="测试Activity"
            android:textColor="@android:color/white" />

    </LinearLayout>

</com.example.thefirstgamenotebook.testforviewdraghelper.SwipeOutView>

由于是设置拖拽事件的是SwipeOutView的第一个子布局,所以上面的布局中所有的控件只能放在一个布局中去,这里我放到了LinearLayout中,接着是Activity的代码了:

MainActivity

public class MainActivity extends AppCompatActivity implements SwipeCallBack{

    private SwipeOutView swipeOutView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        swipeOutView = findViewById(R.id.swipeOutView);
        swipeOutView.setSwipeCallBack(this);
    }

    @Override
    public void onFinish() {
        finish();
    }
}

看一下运行效果怎么样:

可以看到,效果并不如理想中的样子,怎么去解决它呢?首先给Activity设置一个透明背景的主题,然后将SwipeOutView的唯一子布局背景设置为白色试试看:

在style.xml里的主题中添加:

    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>

同时给SwipeOutView下的LinearLayout添加:

        android:background="@android:color/white"

再看看效果:

这里写图片描述

还是比较滑稽,不过这时候只要把theme设定为NoActionBar即可。

这样侧滑退出Activity就完成了!

不过这样的实现有一个小缺点,就是必须把SwipeOutView作为根布局,显然,这么做不够fasion,为了弥补这个短板,可以创建一个SwipeHelper类去完善相关逻辑:

public class SwipeHelper {

    private Activity activity;
    private SwipeCallBack swipeCallBack;
    private SecondSwipeOutView secondSwipeOutView;

    public SwipeHelper(Activity activity) {
        this.activity = activity;
    }

    public void onActivityCreate() {
        secondSwipeOutView = (SecondSwipeOutView) LayoutInflater.from(activity).inflate(R.layout.swipe_layout, null);
        secondSwipeOutView.setSwipeCallBack(swipeCallBack);
        secondSwipeOutView.attachToActivity(activity);
    }


    public SwipeHelper setSwipeCallBack(SwipeCallBack swipeCallBack) {
        this.swipeCallBack = swipeCallBack;
        return this;
    }
}

同时在SwipeOutView中添加:

 //核心代码,绑定到相应activity
    public void attachToActivity(Activity activity) {
        TypedArray typedArray = activity.getTheme().obtainStyledAttributes(new int[]{android.R.attr.windowBackground});
        int background = typedArray.getResourceId(0, 0);
        typedArray.recycle();
        ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);
        decorChild.setBackgroundResource(background);
        decorChild.setBackgroundColor(Color.parseColor("#ffffffff"));       //将子布局背景设置为白色
        decorView.removeView(decorChild);
        addView(decorChild);
        decorView.addView(this);
    }

这里默认把SwipeOutView的子布局背景设置为白色,所以每次想要完成这个效果只需要对主题设置一下就ok了,在看一下第二个Activity的代码:

public class SecondActivity extends AppCompatActivity implements SwipeCallBack{


    private SwipeHelper swipeHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        swipeHelper = new SwipeHelper(this);
        swipeHelper.setSwipeCallBack(this);
        swipeHelper.onActivityCreate();
    }


    @Override
    public void onFinish() {
        finish();
    }
}

看一下运行效果:

这里写图片描述

大功告成!

最后是完整代码地址:

项目地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值