用过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);前面。