仿微信、知乎实现上下左右滑动关闭Acticity

用过IOS的手机都知道,IOS是可以右滑可以返回前一个界面的。在Android机上目前使用的还不多,到现在为止,我看到的只有微信、知乎实现了这个功能。

目录

[TOC]
- 效果展示
- 逻辑剖析
- 代码注释
- 代码库


效果展示

这里写图片描述
特色:
1、支持上下左右边界或者全屏幕的滑动
2、实现了前一个Activity的界面滑动

逻辑剖析

实现这个效果,我们需要处理两部分动画。一个是当前Activity的view的滑动,一个是前一个Activity的View的滑动。我们可以通过当前Activity的滑动的回调来处理前一个Activity的滑动。

我们知道每个Activity都会有个window用来绘制Activity的所有view,具体实现为PhoneWindow类。里面包含一个decorView,decorView是我们Activity的所有View的最顶层view。这些我们可以根据sdk中tools文件夹下的hierarchyviewer工具可以看出来。
这里写图片描述
这里可以看出,我们通过setContentView()方法添加的view,就是decorView的第一个子View。
通过自定义一个可以左右滑动的slideFrameView来包装contentView,再讲customView添加进decorView。这样就实现了我们滑动。

package wzk.com.library;

import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;

import wzk.com.library.model.SlideConfig;
import wzk.com.library.widget.SlideFrame;

/**
 * @author WangZhengkui on 2016-02-20 10:30
 */
public class SlideClose {

    public static void initSlideClose(Activity currentActivity,SlideConfig config) {
        View decorView = currentActivity.getWindow().getDecorView();
        View contentView = ((ViewGroup)decorView).getChildAt(0);
        ((ViewGroup) decorView).removeView(contentView);

        SlideFrame slideFrame = new SlideFrame(currentActivity,config,contentView);
        slideFrame.setId(R.id.slide_frame);
        contentView.setId(R.id.slide_content);
        ((ViewGroup) decorView).addView(slideFrame, 0);
    }
}

效果如下
这里写图片描述

通过上图还可以看到我们在SlideFrame里面还有一个view,这个view是用来实现灰色背景的。
其实说到这里,原理都已经通了,就可以自己去实现,不需要往下看了。下面只是我的一种实现。

代码注释

首先来实现当前Activity的滑动,也就是SlideFrame的滑动。
SlideFrame的滑动是通过ViewDragHelper类实现的。这个类是在官方的v4包中提供的。它可以代替我们实现View的拖动动画,就不用我们为view拖动时的触摸事件、加速度检测、scroller、fling等来费神了。如果对ViewDragHelper不熟的话,没关系下面会详解,用好viewDragHelper关键是用好ViewDragHelper.CallBack这个类。
在讲解SlideFrame类之前,先看一下提供的配置的类

package wzk.com.library.model;

/**
 * 触摸的边界
 * @author WangZhengkui on 2016-02-21 12:34
 */
public class SlideEdgeDirect {
    /**
     * Edge flag indicating that the left edge should be affected.
     */
    public static final int EDGE_LEFT = 1 << 0;

    /**
     * Edge flag indicating that the right edge should be affected.
     */
    public static final int EDGE_RIGHT = 1 << 1;

    /**
     * Edge flag indicating that the top edge should be affected.
     */
    public static final int EDGE_TOP = 1 << 2;

    /**
     * Edge flag indicating that the bottom edge should be affected.
     */
    public static final int EDGE_BOTTOM = 1 << 3;
}

这个类的定义来源于ViewDragHelper中的EDGE_LEFT、EDGE_RIGHT、EDGE_TOP、EDGE_BOTTOM这些成员变量,分别表示改边界是否支持拖动。

package wzk.com.library.model;

import android.support.annotation.FloatRange;

import wzk.com.library.widget.SlideFrame;

/**
 * @author WangZhengkui on 2016-02-20 10:55
 */
public class SlideConfig {

    /**
     * 能否滑开的最小滑动速率
     */
    private int mMinVelocity = 2000;

    /**
     *ViewDragHelper的sensitivity
     */
    private float mSensitivity = 1.0f;

    /**
     * 背景view的默认透明度
     */
    private float mDimColor = 0.6f;
    /**
     * 是从哪个边界滑动关闭
     */
    private int mEdgeDirect = SlideEdgeDirect.EDGE_LEFT;

    /**
     * 是否只能从边界滑动,false则表示全屏滑动
     */
    private boolean mEdgeOnly = true;

    /**
     * 边界滑动的距离(从0~1,基数为view的宽度)
     */
    private float mEdgeSize = 0.18f;

    /**
     * 松开手指时的最小距离(从0~1,基数为view的宽度)
     */
    private float mDistanceForRelease = 0.4f;

    /**
     * 监听器
     */
    private SlideFrame.OnFrameSlideListener onFrameSlideListener;


    public int getMinVelocity() {
        return mMinVelocity;
    }

    public SlideConfig setMinVelocity(int mMinVelocity) {
        this.mMinVelocity = mMinVelocity;
        return this;
    }

    public float getSensitivity() {
        return mSensitivity;
    }

    public SlideConfig setSensitivity(float mSensitivity) {
        this.mSensitivity = mSensitivity;
        return this;
    }

    public float getDimColor() {
        return mDimColor;
    }

    public SlideConfig setDimColor(float mDimColor) {
        this.mDimColor = mDimColor;
        return this;
    }

    public int getEdgeDirect() {
        return mEdgeDirect;
    }

    public SlideConfig setEdgeDirect(int mEdgeDirect) {
        this.mEdgeDirect = mEdgeDirect;
        return this;
    }

    public boolean isEdgeOnly() {
        return mEdgeOnly;
    }

    public SlideConfig setEdgeOnly(boolean mEdgeOnly) {
        this.mEdgeOnly = mEdgeOnly;
        return this;
    }

    public float getEdgeSize(int screenWidth) {
        return mEdgeSize*screenWidth;
    }

    public SlideConfig setEdgeSize(float mEdgeSize) {
        this.mEdgeSize = mEdgeSize;
        return this;
    }


    public float getDistanceForRelease() {
        return mDistanceForRelease;
    }

    public SlideConfig setDistanceForRelease(float mDistanceForRelease) {
        this.mDistanceForRelease = mDistanceForRelease;
        return this;
    }

    public SlideFrame.OnFrameSlideListener getOnFrameSlideListener() {
        return onFrameSlideListener;
    }

    public SlideConfig setOnFrameSlideListener(SlideFrame.OnFrameSlideListener onFrameSlideListener) {
        this.onFrameSlideListener = onFrameSlideListener;
        return this;
    }

    public static class Builder {
        private SlideConfig config;
        public Builder() {
            config = new SlideConfig();
        }
        public Builder setMinVelocity(int mMinVelocity) {
            config.mMinVelocity = mMinVelocity;
            return this;
        }

        public Builder setSensitivity(float sensitivity) {
            config.mSensitivity = sensitivity;
            return this;
        }
        public Builder setEdgeDirect(int mEdgeFlags) {
            config.mEdgeDirect = mEdgeFlags;
            return this;
        }
        public Builder setEdgeOnly(boolean mEdgeOnly) {
            config.mEdgeOnly = mEdgeOnly;
            return this;
        }
        public Builder setEdgeSize(@FloatRange(from = 0f, to = 1f) float edgeSize){
            config.mEdgeSize = edgeSize;
            return this;
        }
        public Builder setDistanceForRelease(float mDistanceForRelease) {
            config.mDistanceForRelease = mDistanceForRelease;
            return this;
        }
        public Builder setDimColor(float mDimColor) {
            config.mDimColor = mDimColor;
            return this;
        }
        public Builder setOnFrameSlideListener(SlideFrame.OnFrameSlideListener onFrameSlideListener) {
            config.onFrameSlideListener = onFrameSlideListener;
            return this;
        }
        public SlideConfig build() {
            return config;
        }
    }
}

这些配置表明了我们想要的一些效果,比如想从哪个边界拖动,边界检测的有效距离,拖动到什么值时触发Activity关闭,是否支持全屏滑动、最小速度、监听回调等等。
参考用法:

SlideConfig config = new SlideConfig.Builder()
.setSensitivity(1)
.setEdgeDirect(SlideEdgeDirect.EDGE_TOP|SlideEdgeDirect.EDGE_LEFT|SlideEdgeDirect.EDGE_RIGHT)
.setEdgeOnly(true)
.setDistanceForRelease(0.4f)
.setMinVelocity(2000).build();

这个配置表示我们可以在上,左,右三个边界可以滑动。注意:如果setEdgeOnly(false),则表示为全屏滑动
最终通过在当前Activity中调用SlideClose.initSlideClose(this, config);来实现初始化。

请看猪脚SlideFrame类

package wzk.com.library.widget;

import android.content.Context;
import android.graphics.Color;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewGroupCompat;
import android.support.v4.widget.ViewDragHelper;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import wzk.com.library.model.SlideConfig;
import wzk.com.library.model.SlideEdgeDirect;

/**
 * @author WangZhengkui on 2016-02-20 10:35
 */
public class SlideFrame extends FrameLayout {
    private static final int MIN_FLING_VELOCITY = 400; // dips per second
    View contentView;
    ViewDragHelper viewDragHelper;
    SlideConfig mConfig;
    OnFrameSlideListener mListener;
    /**
     * 当前手指的按下点是否是在边界
     */
    private boolean isTouchEdge;
    private int mScreenWidth;
    private int mScreenHeight;
    private View mDimView;

    /**
     * 因为ViewDragHelper的getEdgesTouched()方法确定的触摸边界范围(mEdgeSize)太小,而mEdgeSize的值又不能改变,所以我们自己在onTouchEvent的down事件来判断触摸边界。
     */
    private int mEdgeTouched;

    /**边界值,这个值=*/
    private double mEdgeSize;
    /**
     * 判断是否将要关闭还是打开,true表示为打开,false表示为关闭
     */
    private boolean stateWillTo;

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

    public SlideFrame(Context context, SlideConfig config, View decorView) {
        super(context);
        this.contentView = decorView;
        this.mConfig = (config == null ? new SlideConfig.Builder().build() : config);
        this.mListener = mConfig.getOnFrameSlideListener();
        //添加背景
        // Setup the dimmer view
        mDimView = new View(getContext());
        mDimView.setBackgroundColor(Color.BLACK);
        mDimView.getBackground().setAlpha((int) (mConfig.getDimColor() * 255));
        // Add the dimmer view to the layout
        addView(mDimView);
        //将decorView添加到该View
        addView(decorView);

        viewDragHelper = ViewDragHelper.create(this, mConfig.getSensitivity(), mCallBack);
        //设置最小速度
        final float density = getResources().getDisplayMetrics().density;
        float minVelocity = mConfig.getMinVelocity() * density;
        viewDragHelper.setMinVelocity(minVelocity);
        viewDragHelper.setEdgeTrackingEnabled(mConfig.getEdgeDirect());

        ViewGroupCompat.setMotionEventSplittingEnabled(this, false);


        mScreenWidth = getResources().getDisplayMetrics().widthPixels;
        post(new Runnable() {
            @Override
            public void run() {
                mScreenHeight = getHeight();
            }
        });

    }

    /**
     * Clamp Integer values to a given range
     *
     * @param value the value to clamp
     * @param min   the minimum value
     * @param max   the maximum value
     * @return the clamped value
     */
    public static int clamp(int value, int min, int max) {
        return Math.max(min, Math.min(max, value));
    }

    private void applyDimColor(float percent) {
        mDimView.getBackground().setAlpha((int) (percent * mConfig.getDimColor() * 255));
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //当前是否是触摸到边界
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mEdgeTouched = 0;
            mEdgeTouched = getEdgesTouched(ev.getX(),ev.getY());
            if (mConfig.isEdgeOnly()) {
                //如果没有触摸到边界,则isTouchEdge为false;
                isTouchEdge = mEdgeTouched != 0;
            } else {
                //表示全屏滑动
                isTouchEdge = true;
            }
        }

        boolean interceptForDrag;
        interceptForDrag = viewDragHelper.shouldInterceptTouchEvent(ev);
        return interceptForDrag;
    }

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

    @Override
    public void computeScroll() {
        super.computeScroll();
        //根据viewDragHelper,继续完成动画效果
        if (viewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {
        /**=0为水平方向 ,=1为竖直方向*,=-1则表示方向未确定*/
        int direct = -1;
        int left,top;
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            //根据ID和触摸边界来判断是否可以捕获该child.
            if (child.getId() == contentView.getId() && isTouchEdge) {
                return true;
            }
            return false;
        }
        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            super.onEdgeTouched(edgeFlags, pointerId);
        }
        //该函数的返回值意味着CaptureView在X方向的位置
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            this.left = left;
            //如果触摸的边界不是在左边或者右边则返回0
            //全屏滑动则略过判断
            if (mConfig.isEdgeOnly()&&(((mEdgeTouched & SlideEdgeDirect.EDGE_LEFT) == 0) && (mEdgeTouched & SlideEdgeDirect.EDGE_RIGHT) == 0)) {
                return 0;
            }
            //一旦方向确定,则direct的值不再判断
            if (direct == -1) {
                direct = checkDirect(left,top);
            }
            if (direct == 0) {
                return left;
            }
            //如果是水平方向,则屏蔽竖直方向的滑动
            return 0;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return mScreenWidth;
        }
        //该函数的返回值意味着CaptureView在Y方向的位置
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            this.top  = top;
            //如果触摸的边界不是在上边或者下边则返回0
            //如果支持全屏滑动则略过判断
            if (mConfig.isEdgeOnly()&&(((mEdgeTouched & SlideEdgeDirect.EDGE_TOP) == 0) && (mEdgeTouched & SlideEdgeDirect.EDGE_BOTTOM) == 0)) {
                return 0;
            }
            //一旦方向确定,则direct的值不再判断
            if (direct == -1) {
                direct = checkDirect(left,top);
            }
            if (direct == 1) {
                return top;
            }
            //如果是竖直方向,则屏蔽水平方向的滑动
            return 0;
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return mScreenHeight;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            int mMaxLength = 0;
            int distance = 0;
            int mEdgeDrag = 0;
            if (direct == 0) {
                mMaxLength = mScreenWidth;
                distance = Math.abs(left);
                //判断边界拖动
                if (left > 0) {
                    mEdgeDrag = SlideEdgeDirect.EDGE_LEFT;
                } else {
                    mEdgeDrag = SlideEdgeDirect.EDGE_RIGHT;
                }
            } else {
                mMaxLength = mScreenHeight;
                distance = Math.abs(top);
                //判断边界拖动
                if (top > 0) {
                    mEdgeDrag = SlideEdgeDirect.EDGE_TOP;
                } else {
                    mEdgeDrag = SlideEdgeDirect.EDGE_BOTTOM;
                }
            }
            float percent = distance * 1.0f / mMaxLength;
            if (mListener != null) mListener.onSlideChange(mEdgeDrag,percent);
            //控制dimView的透明度
            applyDimColor(1 - percent);
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            //根据滑动的结果来做边界检测
            if (releasedChild.getLeft() > 0) {
                releaseLeft(releasedChild, xvel);
            } else if (releasedChild.getLeft() < 0) {
                releaseRight(releasedChild, xvel);
            } else if (releasedChild.getTop() > 0) releaseTop(releasedChild, yvel);
              else releaseBottom(releasedChild, yvel);
        }

        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
            if (mListener != null) mListener.onStateChanged(state);
            switch (state) {
                //终止时的状态
                case ViewDragHelper.STATE_IDLE:
                    if (mListener != null) {
                        //根据滑动结果回调
                        if (!stateWillTo) mListener.onClosed();
                        else mListener.onOpened();
                    }
                    //将direct设置为默认值
                    direct = -1;
                    break;
            }

        }
        /**
         * 判断滑动方向
         * @param left
         * @param top
         * @return
         */
        public int checkDirect(int left,int top) {
            //在50的范围内检测
            if (Math.abs(left) <= 50 && Math.abs(top) <= 50) {
                if (Math.abs(left) >= Math.abs(top)) {
                    return 0;
                } else {
                    return 1;
                }
            } else {
                return direct;
            }
        }
    };

    /**
     * 当触摸边界为左边时的手指松开后的判断
     * @param releasedChild
     * @param vel
     * @return
     */
    public boolean releaseLeft(View releasedChild, float vel) {
        //判断在松开手指时的view滑动的位置
        int settleLeft = 0;
        int left = releasedChild.getLeft();
        int distanceForRelease = (int) (mConfig.getDistanceForRelease() * getWidth());
        if (vel > 0) {
            if (left > distanceForRelease) {
                settleLeft = mScreenWidth;
            } else if (vel > mConfig.getMinVelocity()) {
                settleLeft = mScreenWidth;
            }
        } else if (vel == 0) {
            if (left > distanceForRelease) {
                settleLeft = mScreenWidth;
            }
        }
        if (left != 0) {
            //启动动画,将view设置到判定的值
            //这里一旦启动,则意味着边界检测结束
            viewDragHelper.settleCapturedViewAt(settleLeft, releasedChild.getTop());
            invalidate();
        }

        return stateWillTo = settleLeft != 0;
    }

    /**
     * 当触摸边界为右边时的手指松开后的判断
     * @param releasedChild
     * @param vel
     * @return
     */
    public boolean releaseRight(View releasedChild, float vel) {
        //判断在松开手指时的view滑动的位置
        int settleRight = 0;
        //当触摸边界在右边时,getleft的值为负,则这里前面加负号。这样可以保证如果getLeft()>0时,将view复位而不会触发动画。
        //如果用Math.abs(releasedChild.getLeft())的话,当getLeft()>0时,也会触发动画从而将关闭Activty。
        int right = -releasedChild.getLeft();
        //根据配置的系数,来确定临界值的大小。
        int distanceForRelease = (int) (mConfig.getDistanceForRelease() * getWidth());
        if (vel > 0) {
            //大于临界值
            if (right > distanceForRelease) {
                settleRight = -mScreenWidth;
            } else if (vel > mConfig.getMinVelocity()) {
                settleRight = -mScreenWidth;
            }
        } else if (vel == 0) {
            if (right > distanceForRelease) {
                settleRight = -mScreenWidth;
            }
        }
        if (right != 0) {
            //启动动画,将view设置到判定的值,正为右方向,负为左方向。
            viewDragHelper.settleCapturedViewAt(settleRight, releasedChild.getTop());
            invalidate();
        }
        //确定在松开手指时,该View是关闭还是打开
       return stateWillTo = settleRight != 0;
    }

    /**
     * 当触摸边界为上边时的手指松开后的判断
     * @param releasedChild
     * @param yvel
     * @return
     */
    public boolean releaseTop(View releasedChild, float yvel) {
        //判断在松开手指时的view滑动的位置
        int top = releasedChild.getTop();
        int distanceForRelease = (int) (mConfig.getDistanceForRelease() * mScreenHeight);
        int settleTop = 0;
        if (yvel > 0) {
            if (top > distanceForRelease) {
                settleTop = mScreenHeight;
            } else if (yvel > mConfig.getMinVelocity()) {
                settleTop = mScreenHeight;
            }
        } else if (yvel == 0) {
            if (top > distanceForRelease) {
                settleTop = mScreenHeight;
            }
        }
        if (top != 0) {
            viewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleTop);
            invalidate();
        }
        //确定在松开手指时,该View是关闭还是打开
        return stateWillTo = settleTop != 0;
    }

    /**
     * * 当触摸边界为下边时的手指松开后的判断
     * @param releasedChild
     * @param yvel
     * @return
     */
    public boolean releaseBottom(View releasedChild, float yvel) {
        //判断在松开手指时的view滑动的位置
        //注释请看releaseRight()方法
        int bottom = -releasedChild.getTop();
        int distanceForRelease = (int) (mConfig.getDistanceForRelease() * mScreenHeight);
        int settleBottom = 0;
        if (yvel > 0) {
            if (bottom > distanceForRelease) {
                settleBottom = -mScreenHeight;
            } else if (yvel > mConfig.getMinVelocity()) {
                settleBottom = -mScreenHeight;
            }
        } else if (yvel == 0) {
            if (bottom > distanceForRelease) {
                settleBottom = -mScreenHeight;
            }
        }
        if (bottom != 0) {
            //上为负,下为正
            viewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleBottom);
            invalidate();
        }
        //确定在松开手指时,该View是关闭还是打开
        return stateWillTo = settleBottom != 0;
    }

    /**
     * 获取触摸的边界
     * @param x
     * @param y
     * @return
     */
    private int getEdgesTouched(float x, float y) {
        int result = 0;
        if (x < this.getLeft() + mConfig.getEdgeSize(mScreenWidth)) result |= SlideEdgeDirect.EDGE_LEFT;
        if (y < this.getTop() + mConfig.getEdgeSize(mScreenHeight)) result |= SlideEdgeDirect.EDGE_TOP;
        if (x > this.getRight() - mConfig.getEdgeSize(mScreenWidth)) result |= SlideEdgeDirect.EDGE_RIGHT;
        if (y > this.getBottom() - mConfig.getEdgeSize(mScreenHeight)) result |= SlideEdgeDirect.EDGE_BOTTOM;

        //将边界判断的值和我们指定的方向值做与运算,得出最终的边界值
        result &=mConfig.getEdgeDirect();
        return result;
    }
    /**
     * The panel sliding interface that gets called
     * whenever the panel is closed or opened
     */
    public interface OnFrameSlideListener {

        void onStateChanged(int state);
        void onClosed();

        void onOpened();

        /**
         *
         * @param mEdgeTouched 手指触摸的边界
         * @param percent
         */
        void onSlideChange(int mEdgeTouched,float percent);
    }

    public static class OnSimpleFrameSlideListener implements OnFrameSlideListener {

        @Override
        public void onStateChanged(int state) {

        }

        @Override
        public void onClosed() {

        }

        @Override
        public void onOpened() {

        }

        @Override
        public void onSlideChange(int edgeDrag, float percent) {

        }

    }
}

我们首先通过在onInterceptTouchEvent()方法中判断是否可以实现触摸滑动。如果可以则isTouchEdge为true,并且记录当前触摸的是哪条边界mEdgeTouched的值。但如果是全屏滑动的话,则mEdgeTouched值为0。
在viewDragHelper的callBack中来实现滑动。
通过clampViewPositionHorizontal(View child, int left, int dx) 方法的返回值来获取我们的contentView.如果改方法返回true,则child就是我们要捕获的view,返回false则不是

 if (child.getId() == contentView.getId() && isTouchEdge) {
         return true;
 }

getViewHorizontalDragRange(View child)
该函数用来获得捕获的view在水平方向的拖动的最大值。
getViewVerticalDragRange(View child)
该函数用来获得捕获的view在竖直方向的拖动的最大值。

clampViewPositionHorizontal(View child, int left, int dx)
这个函数是用来确定捕获的view在水平方向的位置。left的值
clampViewPositionVertical(View child, int top, int dy)
这个函数是用来确定捕获的view在竖直方向的位置。top的值

拖动的关键就是根据上述两个函数的返回值来实现。请看具体注释

public int clampViewPositionHorizontal(View child, int left, int dx) {
            this.left = left;
            //如果触摸的边界不是在左边或者右边则返回0
            //全屏滑动则略过判断
            if (mConfig.isEdgeOnly()&&(((mEdgeTouched & SlideEdgeDirect.EDGE_LEFT) == 0) && (mEdgeTouched & SlideEdgeDirect.EDGE_RIGHT) == 0)) {
                return 0;
            }
            //一旦方向确定,则direct的值不再判断
            if (direct == -1) {
                direct = checkDirect(left,top);
            }
            if (direct == 0) {
                return left;
            }
            //如果是水平方向,则屏蔽竖直方向的滑动
            return 0;
        }
  public int clampViewPositionVertical(View child, int top, int dy) {
            this.top  = top;
            //如果触摸的边界不是在上边或者下边则返回0
            //如果支持全屏滑动则略过判断
            if (mConfig.isEdgeOnly()&&(((mEdgeTouched & SlideEdgeDirect.EDGE_TOP) == 0) && (mEdgeTouched & SlideEdgeDirect.EDGE_BOTTOM) == 0)) {
                return 0;
            }
            //一旦方向确定,则direct的值不再判断
            if (direct == -1) {
                direct = checkDirect(left,top);
            }
            if (direct == 1) {
                return top;
            }
            //如果是竖直方向,则屏蔽水平方向的滑动
            return 0;
        }      

其中拖动方向的确定则是在50的距离内得到。参看下面方法:

public int checkDirect(int left,int top) {
            //在50的范围内检测
            if (Math.abs(left) <= 50 && Math.abs(top) <= 50) {
                if (Math.abs(left) >= Math.abs(top)) {
                    return 0;
                } else {
                    return 1;
                }
            } else {
                return direct;
            }
        }

onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
这个方法会在view的left和top改变之后才会回调,也就是clampViewPositionHorizontal(View child, int left, int dx)和clampViewPositionVertical(View child, int top, int dy)的返回值。
在这里我们根据拖动的距离来设定背景的透明度。并且将拖动的边界和拖动的比例回调出去,来实现前一个Activity的动画效果。

onViewReleased(View releasedChild, float xvel, float yvel)
在手指松开时回调该方法。在这个方法中我们处理该View是否是打开还是关闭。
onViewDragStateChanged(int state)
状态回调,有三种状态STATE_IDLE、STATE_DRAGGING、STATE_SETTLING
当状态=IDLE时回调该Activity是否是打开还是关闭。

最后别忘了重写computeScroll()方法,当手指松开时能够fling.

介绍&和|运算

代码中出现mEdgeTouched & SlideEdgeDirect.EDGE_LEFT这种判断
复习一下知识:
&运算(与运算):当两个条件都是真,结果为真
|运算(或运算):当有一个条为真,结果为真
左移:下面的0和1都是2进制 1<<0 = 0001;1<<1 = 0010;1<<2 = 0100;1<<3 = 1000.
EDGE_LEFT、EDGE_RIGHT、EDGE_TOP、EDGE_BOTTOM分别对应上面的值。
假设mEdgeTouched = EDGE_LEFT|EDGE_RIGHT = 0011;
则mEdgeTouched & EDGE_LEFT = EDGE_LEFT;

实现前一个Activity滑动

首先要得到前一个Activity的引用。这个我是用LinkedList数组实现。在onCreate()方法中添加,在onDestroy()中移除。

package wzk.com.slidecloseactivity;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.widget.ViewDragHelper;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;

import com.nineoldandroids.view.ViewHelper;

import wzk.com.library.SlideClose;
import wzk.com.library.model.SlideConfig;
import wzk.com.library.model.SlideEdgeDirect;
import wzk.com.library.widget.SlideFrame;

public class BaseActivity extends AppCompatActivity {
    private boolean isSlideClose = true;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Config.addActivity(this);
        if (isSlideClose) {
            initSlideClose();
        }
    }

    public void initSlideClose() {
        final View childAt0;
        Activity beforeActivity = Config.getBeforeLastActivity();
        if (beforeActivity != null) {
            ViewGroup decorView = (ViewGroup) beforeActivity.getWindow().getDecorView();
            childAt0 = decorView.getChildAt(0);
        } else {
            childAt0 = null;
        }
        SlideConfig config = new SlideConfig.Builder()
                .setSensitivity(1)
                .setEdgeDirect(SlideEdgeDirect.EDGE_TOP|SlideEdgeDirect.EDGE_LEFT|SlideEdgeDirect.EDGE_RIGHT)
                .setEdgeOnly(false)
                .setDistanceForRelease(0.4f)
                .setMinVelocity(2000)
                .setOnFrameSlideListener(new SlideFrame.OnSimpleFrameSlideListener(){
                    @Override
                    public void onOpened() {
                        super.onOpened();
                        finish();
                        overridePendingTransition(0, 0);
                    }

                    @Override
                    public void onStateChanged(int state) {
                        super.onStateChanged(state);
                        switch (state) {
                            case ViewDragHelper.STATE_IDLE:
                                if (childAt0 != null) {
                                    ViewHelper.setTranslationX(childAt0, 0);
                                    ViewHelper.setTranslationY(childAt0, 0);
                                }
                                break;
                        }
                    }

                    @Override
                    public void onSlideChange(int mEdgeDrag,float percent) {
                        super.onSlideChange(mEdgeDrag, percent);
//                        Log.i("BaseActivity", "mEdgeDrag = "+mEdgeDrag+",percent = "+percent);
                        if (childAt0 != null) {
                            //这里改变了前一个view的位置,记得在滑动结束后将view复位
                            switch (mEdgeDrag) {
                                case SlideEdgeDirect.EDGE_LEFT:
                                    ViewHelper.setTranslationX(childAt0, -300 * (1 - percent));
                                    break;
                                case SlideEdgeDirect.EDGE_TOP:
                                    ViewHelper.setTranslationY(childAt0, -300 * (1 - percent));
                                    break;
                                case SlideEdgeDirect.EDGE_RIGHT:
                                    ViewHelper.setTranslationX(childAt0, 300 * (1 - percent));
                                    break;
                                case SlideEdgeDirect.EDGE_BOTTOM:
                                    ViewHelper.setTranslationY(childAt0, 300 * (1 - percent));
                                    break;
                            }
                        }
                    }
                }).build();
        SlideClose.initSlideClose(this, config);
    }

    public boolean isSlideClose() {
        return isSlideClose;
    }

    public BaseActivity setIsSlideClose(boolean isSlideClose) {
        this.isSlideClose = isSlideClose;
        return this;
    }

    @Override
    protected void onDestroy() {
        Config.removeActivity(this);
        super.onDestroy();
    }
}

让项目中的所有Activity继承这个BaseActivity()即可实现。
如果有Activity不需要滑动关闭,则在该Activity的onCreate()方法中调用setIsSlideClose(false);这句话一定要在super.onCreate(savedInstanceState);前面。

代码下载

https://github.com/haidaodashushu/SlideCloseActivity

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值