右滑返回功能 几种实现方式总结

Posted 白乾涛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了右滑返回功能 几种实现方式总结相关的知识,希望对你有一定的参考价值。


方式一:利用onTouchListener简单实现
基本思想:
让Activity中的根布局实现onTouchListener
当手指在屏幕上向右滑动时,我们记下ACTION_DOWN的X轴的位置
在手指滑动时(即ACTION_MOVE),获取滑动时的X轴的位置
当滑动的位置大于某个临界值且在这个方向上的速度大于某个临界值时,我们就认为用户滑动手指的意图是返回上一个页面。
重大缺点:页面完全没有跟着手指滑动,只是当手指滑动到一定条件时将Activity干掉,所以会感觉退出的非常生硬。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button btn = new Button(this);
        btn.setText("点击进入第二个页面");
        setContentView(btn);
        btn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SecondActivity.class));
                overridePendingTransition(R.anim.in_from_right, R.anim.out_to_left);//设置切换动画,从右边进入,左边退出
            }
        });
    }
}

public class SecondActivity extends Activity implements OnTouchListener {
    /**记录手指按下时的横坐标*/
    private float xDown;
    /**记录手指移动时的横坐标*/
    private float xMove;
    /**用于计算手指滑动的速度*/
    private VelocityTracker mVelocityTracker;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv_info;
        tv_info = new TextView(this);
        tv_info.setTextColor(Color.RED);
        tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
        tv_info.setGravity(Gravity.CENTER);
        tv_info.setText("从左侧边缘向右滑动返回");
        setContentView(tv_info);
        tv_info.setOnTouchListener(this);
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //创建VelocityTracker对象,并将触摸content界面的滑动事件加入到VelocityTracker当中。
        if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
        mVelocityTracker.addMovement(event);
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            xDown = event.getRawX();
            break;
        case MotionEvent.ACTION_MOVE:
            if (xDown < MyApplication.XDISTANCE_MAXFROM) {//当滑动的起始位置小于我们设定的值
                xMove = event.getRawX();
                int distanceX = (int) (xMove - xDown);//活动的距离
                int xSpeed = getScrollVelocity();//获取顺时速度
                //当滑动的距离大于我们设定的最小距离且滑动的瞬间速度大于我们设定的速度
                if (distanceX > MyApplication.XDISTANCE_MIN && xSpeed > MyApplication.XSPEED_MIN) {
                    finish();
                    overridePendingTransition(R.anim.in_from_left, R.anim.out_to_right);//设置切换动画,从右边进入,左边退出
                }
            }
            break;
        case MotionEvent.ACTION_UP:
            mVelocityTracker.recycle();
            mVelocityTracker = null;
            break;
        default:
            break;
        }
        return true;
    }
    /**
     * 获取手指在界面滑动的速度。
     * @return 滑动速度,以每秒钟移动了多少像素值为单位。
     */
    private int getScrollVelocity() {
        mVelocityTracker.computeCurrentVelocity(1000);
        int velocity = (int) mVelocityTracker.getXVelocity();
        return Math.abs(velocity);
    }
}

public class MyApplication extends Application {
    private static MyApplication mApplication = null;
    /**手指向右滑动时的最大起始距离(只有从左边缘滑动才有效)*/
    public static int XDISTANCE_MAXFROM;
    /**手指向右滑动时的最小距离(防止误操作)*/
    public static int XDISTANCE_MIN;
    /**手指向右滑动时的最小速度(防止误操作)*/
    public static int XSPEED_MIN;
    @Override
    
    public void onCreate() {
        super.onCreate();
        mApplication = this;
        XDISTANCE_MAXFROM = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
        XDISTANCE_MIN = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
        XSPEED_MIN = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
    }
    public static MyApplication getApplication() {
        return mApplication;
    }
}

方式二:利用View的滚动原理,自定义根ViewGroup
基本思想:
我们的滑动逻辑主要是利用View的scrollBy() 方法, scrollTo()方法和Scroller类来实现的 当手指拖动视图的时候,我们监听手指在屏幕上滑动的距离
利用View的scrollBy() 方法使得View随着手指的滑动而滑动 而当手指离开屏幕,我们在根据逻辑使用Scroller类startScroll()方法设置滑动的参数,然后再根据View的scrollTo进行滚动。 对于View的滑动,存在一些Touch事件消费的处理等问题,最主要的就是Activity里面有一些ListView、 GridView、ScrollView等控件 假如我们Activity里面存在ListView、GridView等控件的话,我们对Activity的最外层布局进行滚动根本就无效果,因为Touch事件被ListView、GridView等控件消费了,所以Activity的最外层布局根本得不到Touch事件,也就实现不了Touch逻辑了 为了解决此Touch事件问题,我们将OnTouchListener直接设置到ListView、GridView上面,这样子就避免了Activity的最外层接受不到Touch事件的问题了

public class SwipeBackLayout extends FrameLayout {
    public static final int XDISTANCE_MAXFROM = 30;
    /**手指向右滑动时的最大【起始】距离(防止误操作,只有从左边缘滑动才有效)*/
    private int xdistance_maxfrom;
    public static final int XDISTANCE_MIN = 90;
    /**手指向右滑动时的最小【滑动】距离(防止误操作,只有滑动超过1/4才关闭)*/
    private int xdistance_min;
    /**手指向右滑动时的最小【滑动】距离(只有滑动超过此距离才滚动view)*/
    private int xdistance_mintouch;
    //******************************************************************************************
    /**是否要finish掉Activity*/
    private boolean isFinish;
    /**Activity最外层的布局,系统会对我们的布局文件的最外层套一个FrameLayout,所以我们其实就是对FrameLayout进行滚动就行了*/
    private View mContentView;
    private int viewWidth;
    /**记录按下点的X坐标*/
    private int downX;
    /**记录手指在屏幕上的X坐标*/
    private int touchX;
    /**Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是辅助UI滑动,反而是单纯地为滑动提供计算*/
    private Scroller mScroller;
    private Activity mActivity;
    private List<ViewPager> mViewPagers = new LinkedList<ViewPager>();
    //构造方法******************************************************************************************
    public SwipeBackLayout(Context context) {
        this(context, null);
    }
    public SwipeBackLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        xdistance_min = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIPXDISTANCE_MIN, getResources().getDisplayMetrics());
        xdistance_maxfrom = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIPXDISTANCE_MAXFROM, getResources().getDisplayMetrics());
        //getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如ViewPage就是用这个距离来判断用户是否翻页的
        xdistance_mintouch = ViewConfiguration.get(context).getScaledTouchSlop();//注意单位是px
        mScroller = new Scroller(context);
        mShadowDrawable = getResources().getDrawable(R.drawable.shadow_left);
    }
    //必须(只需)调用的方法******************************************************************************************
    public void attachToActivity(Activity activity) {
        mActivity = activity;
        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] { android.R.attr.windowBackground });
        int background = a.getResourceId(0, 0);
        a.recycle();
        ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
        ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
        decorChild.setBackgroundResource(background);
        decor.removeView(decorChild);
        addView(decorChild);
        mContentView = (View) decorChild.getParent();
        decor.addView(this);
    }
    //事件拦截与处理******************************************************************************************
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ViewPager mViewPager = getMyTouchViewPager(mViewPagers, ev);
        //如果存在ViewPager并且ViewPager不是处在第一个Item,我们才拦截Touch事件,否则不拦截(Touch事件由ViewPager处理)
        if (mViewPager != null && mViewPager.getCurrentItem() != 0) return super.onInterceptTouchEvent(ev);
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = (int) ev.getRawX();//getX() 获得相对view的位置坐标(不会超过view的长度和宽度),getRawX获得相对屏幕的位置坐标
            touchX = (int) ev.getRawX();
            break;
        case MotionEvent.ACTION_MOVE:
            int moveX = (int) ev.getRawX();
            if (downX <= xdistance_maxfrom && moveX - downX > xdistance_mintouch) return true;//当滑动的起始位置小于我们设定的值,
            break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            int moveX = (int) event.getRawX();
            if (moveX - downX > xdistance_mintouch) mContentView.scrollBy(touchX - moveX, 0);//核心代码:将View中的内容滚动指定距离,注意:不是滚动到指定位置
            //Move the scrolled position of your view. This will cause a call to onScrollChanged(int, int, int, int) and the view will be invalidated.
            //要知道:我们调用View的scrollBy()方法,不是对该View进行滚动,而是对该View里面的内容(例如Button上面的文字)进行滚动
            touchX = moveX;
            break;
        case MotionEvent.ACTION_UP:
            if (Math.abs(mContentView.getScrollX()) >= xdistance_min) {//当滑动的距离大于我们设定的最小距离时
                isFinish = true;//关闭
                scrollRight();//滑到右侧
            } else {//当滑动的距离小于我们设定的最小距离时
                isFinish = false;//不关闭
                scrollOrigin();//回到起始位置
            }
            break;
        }
        return true;
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {//获取该布局的父布局和获取其控件的宽度
            viewWidth = this.getWidth();
            getAlLViewPager(mViewPagersthis);
        }
    }
    @Override
    public void computeScroll() {//Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary. 
        //computeScroll方法不是来让ViewGroup滑动的,真正让ViewGroup滑动的是scrollTo,scrollBy方法。computeScroll的作用是计算ViewGroup如何滑动
        if (mScroller.computeScrollOffset()) {//Call this when you want to know the new location.调用startScroll的时候scroller.computeScrollOffset()返回true
            mContentView.scrollTo(mScroller.getCurrX()mScroller.getCurrY());
            postInvalidate();
            if (mScroller.isFinished() && isFinish) mActivity.finish();//如果Scroller已经停止了滑动
        }
    }
    //阴影******************************************************************************************
    /**滑动时左边缘添加阴影*/
    private Drawable mShadowDrawable;
    /**滑动时左边缘是否显示阴影*/
    private boolean isShowShadow = true;
    /**设置滑动时左边缘是否显示阴影*/
    public void setShowShadow(boolean isShowShadow) {
        this.isShowShadow = isShowShadow;
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {//绘制VIew本身,通过调用View.onDraw实现;绘制自己的孩子,通过调用dispatchDraw实现(ViewGroup往往重写的就是dispatchDraw方法)
        //Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn). 
        super.dispatchDraw(canvas);
        if (isShowShadow && mShadowDrawable != null && mContentView != null) {
            int left = mContentView.getLeft() - mShadowDrawable.getIntrinsicWidth();
            int right = left + mShadowDrawable.getIntrinsicWidth();
            int top = mContentView.getTop();
            int bottom = mContentView.getBottom();
            mShadowDrawable.setBounds(left, top, right, bottom);
            mShadowDrawable.draw(canvas);
        }
    }
    //******************************************************************************************
    /**
     * 获取SwipeBackLayout里面的ViewPager的集合
     */
    private void getAlLViewPager(List<ViewPager> mViewPagers, ViewGroup parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            if (child instanceof ViewPager) mViewPagers.add((ViewPager) child);
            else if (child instanceof ViewGroup) getAlLViewPager(mViewPagers, (ViewGroup) child);
        }
    }
    /**
     * 返回我们touch的ViewPager
     */
    private ViewPager getMyTouchViewPager(List<ViewPager> mViewPagers, MotionEvent ev) {
        if (mViewPagers == null || mViewPagers.size() == 0) return null;
        Rect mRect = new Rect();
        for (ViewPager v : mViewPagers) {
            v.getHitRect(mRect);
            if (mRect.contains((int) ev.getX()(int) ev.getY())) return v;
        }
        return null;
    }
    //******************************************************************************************
    /**
     * 滚动出界面
     */
    private void scrollRight() {
        int delta = viewWidth + mContentView.getScrollX();
        //调用startScroll()是不会有滚动效果的,只有在computeScroll()获取滚动情况,做出滚动的响应,computeScroll在父控件执行drawChild时,会调用这个方法
        mScroller.startScroll(mContentView.getScrollX(), 0, -delta, 0, Math.abs(delta));//设置一些滚动的参数:int startX, int startY, int dx, int dy, int duration
        postInvalidate();//刷新界面
    }
    /**
     * 滚动到起始位置
     */
    private void scrollOrigin() {
        int delta = mContentView.getScrollX();
        mScroller.startScroll(mContentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
        postInvalidate();
    }
}

/**
 * 想要实现向右滑动删除Activity效果只需要继承SwipeBackActivity即可
 * 如果当前页面含有ViewPager,只需要调用SwipeBackLayout的setViewPager()方法即可
 */
public class SwipeBackActivity extends Activity {
    protected SwipeBackLayout layout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        layout = new SwipeBackLayout(this);
        layout.attachToActivity(this);
    }
    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
        overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);
    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(0, R.anim.base_slide_right_out);
    }
}

public class NormalActivity extends SwipeBackActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//这里不但要放在setContentView前,还必须放在super.onCreate之前,否则报错!
        super.onCreate(savedInstanceState);
        TextView tv_info = new TextView(this);
        tv_info.setTextColor(Color.RED);
        tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
        tv_info.setGravity(Gravity.CENTER);
        tv_info.setText("向右滑动返回,点击也能退出");
        //tv_info.setBackgroundColor(Color.GREEN);//因为本Activity的样式为【@android:style/Theme.Translucent】,如果根布局不设置背景色,则整个界面是透明的
        tv_info.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        layout.setShowShadow(false);//不显示阴影
        setContentView(tv_info);
    }
}

        <activity
            android:name=".NormalActivity"
            android:theme="@android:style/Theme.Translucent" />

方式三:利用开源框架实现
但是用起来有点问题
其Activity的样式不能自定义,只能是透明的
所以当前一个Activity滑动时看到的是桌面,而当其结束时却回到了之前的界面,这不是扯淡吗!





附件列表

     

    以上是关于右滑返回功能 几种实现方式总结的主要内容,如果未能解决你的问题,请参考以下文章

    iOS右滑返回的实现(interactivePopGestureRecognizer)

    想要隐藏navigationBar,同时又想支持右滑返回功能

    网页上的“返回上一页”的几种实现代码

    解决右滑返回手势和UIScrollView中的手势冲突

    手机百度右滑返回怎么关闭?

    一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式