常常可以看到,很多Android应用都有这么一个功能,就是滑动关闭Activity,比如微信,CSDN移动端,百度贴吧移动端等。我自己也想写个滑动关闭Activity,最近事情没有那么多,我就google了一下,查看了一下实现滑动关闭Activity的实现方法,其中,有个思路,我觉得很不错,因此,在这里,我通过别人的思路,自己实现了一下滑动关闭Activity的方法,在此记录一下。
首先我们先看下实现效果:
要写滑动关闭Activity,有几个问题要解决:
透明的显示底层Activity,可以使用透明主题,也可以使用其他主题,但是必须修改主题的几个属性,来达到透明的效果,如:
<style name="SwipeBackStyle" parent="AppTheme">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
谷歌在V4包中,增加ViewDragHelper类,这个类能够对滑动,边界检测,自动滚动等功能,提供了很好的实现。因此在这里我们选择ViewDragHelper来实现滑动功能。
阴影绘制,Paint画笔来绘制。我们选择在dispatchDraw()方法中绘制,为什么不用onDraw(),因为onDraw有时候在ViewGroup中不会执行。我们使用画笔的setShader(),通过写一个LinearGradient(),再绘制一个矩形,得到阴影效果。
最核心的原理就是在于,替换Window的DecorView下的LinearLayout。下面从代码直观的说明:
public class SwipeBackLayout extends FrameLayout {
//当前Activity的DecorView
private ViewGroup mDecorView;
//DecorView下的LinearLayout
private View mRootView;
// Activity
private Activity mActivity;
private ViewDragHelper mDragHelper;
//触发退出当前Activity的宽度
private float mSlideWidth;
//屏幕的宽和高
private int mScreenWidth;
private int mScreenHeight;
//画笔,用来绘制阴影效果
private Paint mPaint;
//用于记录当前滑动距离
private int curSlideX;
public SwipeBackLayout(@NonNull Context context) {
super(context);
init(context);
}
private void init(Context context) {
//必须是传入Activity
mActivity = (Activity) context;
mDragHelper = ViewDragHelper.create(this, new SwipeBackDragCallback());
// 设置从左边缘捕获view
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
//初始化画笔
mPaint = new Paint();
mPaint.setStrokeWidth(2);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
//绑定方法,在Activity的DecorView下插入当前ViewGroup,原来的RootView放于当前ViewGroup下
public void bind() {
mDecorView = (ViewGroup) mActivity.getWindow().getDecorView();
mRootView = mDecorView.getChildAt(0);
mDecorView.removeView(mRootView);
this.addView(mRootView);
mDecorView.addView(this);
//计算屏幕宽度
DisplayMetrics dm = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
mScreenWidth = dm.widthPixels;
mScreenHeight = dm.heightPixels;
mSlideWidth = dm.widthPixels * 0.28f;
}
@Override
public boolean onInterceptHoverEvent(MotionEvent event) {
return mDragHelper.shouldInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
//使用settleCapturedViewAt方法是,必须重写computeScroll方法,传入true
//持续滚动期间,不断刷新ViewGroup
if (mDragHelper.continueSettling(true))
invalidate();
}
@Override
protected void dispatchDraw(Canvas canvas) {
//进行阴影绘制,onDraw()方法在ViewGroup中不一定会执行
drawShadow(canvas);
super.dispatchDraw(canvas);
}
private void drawShadow(Canvas canvas) {
canvas.save();
//构造一个渐变
Shader mShader = new LinearGradient(curSlideX - 40, 0, curSlideX, 0, new int[]{Color.parseColor("#00dddddd"), Color.parseColor("#33666666"), Color.parseColor("#90666666")}, null, Shader.TileMode.REPEAT);
//设置着色器
mPaint.setShader(mShader);
//绘制时,注意向左边偏移
RectF rectF = new RectF(curSlideX - 40, 0, curSlideX, mScreenHeight);
canvas.drawRect(rectF, mPaint);
canvas.restore();
}
class SwipeBackDragCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return false;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//当前回调,松开手时触发,比较触发条件和当前的滑动距离
int left = releasedChild.getLeft();
if (left <= mSlideWidth) {
//缓慢滑动的方法,小于触发条件,滚回去
mDragHelper.settleCapturedViewAt(0, 0);
} else {
//大于触发条件,滚出去...
mDragHelper.settleCapturedViewAt(mScreenWidth, 0);
}
//需要手动调用更新界面的方法
invalidate();
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
curSlideX = left;
//当滑动位置改变时,刷新View,绘制新的阴影位置
invalidate();
//当滚动位置到达屏幕最右边,则关掉Activity
if (changedView == mRootView && left >= mScreenWidth) {
mActivity.finish();
mActivity.overridePendingTransition(0, 0);
}
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//限制左右拖拽的位移
left = left >= 0 ? left : 0;
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//上下不能移动,返回0
return 0;
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
//触发边缘时,主动捕捉mRootView
mDragHelper.captureChildView(mRootView, pointerId);
}
}
}
我在代码中,进行详细的注释。总体来说,不难理解。我们在使用的时候,在布局文件中,一定要在根布局设置背景颜色,否则整个布局将会是透明的。下面是使用方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe_back);
SwipeBackLayout mSwipeBackLayout = new SwipeBackLayout(this);
mSwipeBackLayout.bind();
}