Android 一键打造仿IOS右滑退出Activity,非常简单的集成方式(SwipeFinishLayout)

时间:2021-10-20 23:40:47

相信很多玩过苹果的伙伴,都觉得苹果应用的可以右滑退出界面非常方便,但是Android的应用大多都是需要点击返回按钮才能退出,不过现在已经有几个App有了,比如:网易、今日头条和淘宝都有实现。现在网上已经有了这个控件,已经实现了,但是使用起来不是很方便,今天我们就把这个控件的使用方式简单化,只需要你继承一个BaseActivity就可以完成右滑控件的添加。

首先看效果:
Android 一键打造仿IOS右滑退出Activity,非常简单的集成方式(SwipeFinishLayout)

跟IOS的效果没有很大的区别吧?
下面看代码。
BaseSwipeFinishActivity:

public abstract class BaseSwipeFinishActivity extends Activity implements SwipeFinishLayout.SwipeToCloseLayoutAction {

private SwipeFinishLayout mSwipeToCloseLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

/**
* 此方法要在setContentView之后执行,用户添加右滑退出的布局。
*/

protected void addSwipeFinishLayout() {
mSwipeToCloseLayout = new SwipeFinishLayout(this);
mSwipeToCloseLayout.attachToActivity(this);
mSwipeToCloseLayout.setSwipeToCloseLayoutAction(this);
}

/**
* 是否可滑动退出
*
* @param enableGesture
*/

public void setEnableGesture(boolean enableGesture) {
if (mSwipeToCloseLayout != null) {
mSwipeToCloseLayout.setEnableGesture(enableGesture);
}
}

/**
* 全屏时滑动的处理
*
* @param fullScreen
*/

public void setActivityFullScreen(boolean fullScreen) {
if (mSwipeToCloseLayout != null) {
mSwipeToCloseLayout.setActivityFullScreen(fullScreen);
}
}

/**
* 向右滑动是否可关闭activity
*/

@Override
public boolean onScrollToClose() {
return true;
}

/**
* 是否点击在可左右滑动的views里面
*/

@Override
public boolean inChild(Rect rect, Point point) {
return false;
}

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

@Override
protected void onDestroy() {
super.onDestroy();
if (mSwipeToCloseLayout != null) {
mSwipeToCloseLayout.removeAllViews();
mSwipeToCloseLayout.setSwipeToCloseLayoutAction(null);
mSwipeToCloseLayout = null;
}
}

/**
* 关闭执行的动画
*/

@Override
public final void finish() {
super.finish();
overridePendingTransition(R.anim.push_none, R.anim.push_up_out);
}

/**
* 打开activity执行的方法
*/

@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
overridePendingTransition(R.anim.push_up_in, R.anim.push_none);
}

}

两个Activity代码:

public class MainActivity extends BaseSwipeFinishActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addSwipeFinishLayout();
//首页activtiy可设置为不能滑动关闭
setEnableGesture(false);
}

public void onClick(View view) {
startActivity(new Intent(this, SecondActivity.class));
}
}


public class SecondActivity extends BaseSwipeFinishActivity{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//添加布局之后需要执行添加右滑退出的布局
addSwipeFinishLayout();
}
}

右滑控件代码:
SwipeFinishLayout

public class SwipeFinishLayout extends FrameLayout implements SwipeFinishAction {

private boolean mEnableGesture = true;
private Scroller mScroller;
private SwipeFinishUtils mSwipeToClose;
private boolean mToCloseActivity;
private Activity mActivity;

public SwipeFinishLayout(Context context) {
super(context);
init(context);
}

public SwipeFinishLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

private void init(Context context){
mScroller = new Scroller(context);
mSwipeToClose = new SwipeFinishUtils(context, mScroller);

mSwipeToClose.setContentView(this);
mSwipeToClose.setSwipeToTouchAction(this);
setWillNotDraw(false);
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mEnableGesture && mSwipeToClose != null && mSwipeToClose.onInterceptTouchEvent(ev)) {
return true;
}
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
return (mEnableGesture && mSwipeToClose != null && mSwipeToClose.onTouchEvent(ev));
}

public void setAllAreaCanScroll(boolean allAreaCanScroll) {
}

public void setEnableGesture(boolean enableGesture) {
this.mEnableGesture = enableGesture;
}

public void setActivityFullScreen(boolean fullScreen){
this.mSwipeToClose.setActivityFullScreen(fullScreen);
}

@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
} else {
if (mToCloseActivity) {
if (mSwipeToCloseLayoutAction != null) {
mSwipeToCloseLayoutAction.onCloseAction();
}
}
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(Math.abs(getScrollX()) >= getWidth()){
return;
}
canvas.save();
int alpha = 160 * (getWidth() - Math.abs(getScrollX())) / getWidth();
int color = (alpha << 24 | 0x00000000);
canvas.drawColor(color);
canvas.restore();
}

@Override
public void startScroll(int startX, int dx, boolean finish) {
mToCloseActivity = dx < 0;
mScroller.startScroll(startX, 0, dx, 0, 500 * Math.abs(dx) / getWidth());
postInvalidate();
}

private SwipeToCloseLayoutAction mSwipeToCloseLayoutAction;

public void setSwipeToCloseLayoutAction(SwipeToCloseLayoutAction action) {
this.mSwipeToCloseLayoutAction = action;
}

public interface SwipeToCloseLayoutAction extends SwipeFinishTouchCheckAction {
/**
* 关闭界面
*/

void onCloseAction();
}

public void attachToActivity(Activity activity){
mActivity = activity;
ViewGroup viewGroup = (ViewGroup) activity.findViewById(android.R.id.content);
View contentView = viewGroup.getChildAt(0);
viewGroup.removeView(contentView);
if(contentView != null){
if(contentView.getBackground() == null){
contentView.setBackgroundColor(Color.WHITE);
}
attachView(contentView);
}
viewGroup.addView(this);
convertActivityFromTranslucent(activity);
}

public void attachView(View view) {
addView(view);
}
public void convertActivityFromTranslucent(Activity activity) {
try {
Method method = Activity.class
.getDeclaredMethod("convertFromTranslucent");
method.setAccessible(true);
method.invoke(activity);
} catch (Throwable t) {
}
}
public void convertActivityToTranslucent(Activity activity) {
if (activity == null)
return;
try {
Class<?>[] t = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
Class<?>[] method = t;
int len$ = t.length;
for (int i$ = 0; i$ < len$; ++i$) {
Class<?> clazz = method[i$];
if (clazz.getSimpleName().contains(
"TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
break;
}
}
if (Build.VERSION.SDK_INT >= 21) {
Class<?> ActivityOptions = Class
.forName("android.app.ActivityOptions");
Method var8 = Activity.class.getDeclaredMethod(
"convertToTranslucent",
translucentConversionListenerClazz, ActivityOptions);
var8.setAccessible(true);
var8.invoke(activity, new Object[] { null, null });
} else {
Method var8 = Activity.class.getDeclaredMethod(
"convertToTranslucent",
translucentConversionListenerClazz);
var8.setAccessible(true);
var8.invoke(activity, new Object[] { null });
}
} catch (Throwable e) {
}
}
@Override
public void onScroll() {
convertActivityToTranslucent(mActivity);
}

@Override
public void onTouchDown() {
convertActivityToTranslucent(mActivity);
}

@Override
public boolean inChild(Rect rect, Point point) {
return mSwipeToCloseLayoutAction != null && mSwipeToCloseLayoutAction.inChild(rect, point);
}

@Override
public boolean onScrollToClose() {
return mSwipeToCloseLayoutAction != null && mSwipeToCloseLayoutAction.onScrollToClose();
}

public void onActivityDestory() {
mSwipeToClose = null;
mScroller = null;
mActivity = null;
mToCloseActivity = false;
}

}

SwipeFinishUtils,主要的滑动处理基本都在这个里面,根据手指滑动的距离进行判断是否要关闭activity,这里是根据是否滑动到了屏幕的一半来判断的。滑动的时候移动当前activity的布局:

package com.xw.widget;

import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Scroller;

import java.lang.reflect.Field;

public class SwipeFinishUtils {

private static final int INVALID_POINTER = -1;

private boolean mIsBeingDragged;

private int mTouchSlop;
private int mActivePointerId;
private int mLastMotionX;
private int mLastMotionY;
private int mMoveMotionX;
private int mMoveMotionY;
private int mMaximumVelocity;
private int mMinimumVelocity;

private View mView;
private VelocityTracker mVelocityTracker;
private Scroller mScroller;
private SwipeFinishAction mAction;
private int mStatusBarHeight;

private boolean mFullScreen;
private boolean mDownOver = false;//距离左侧1/2才生效
private int mScreenWidth;
private boolean mInChild;

public SwipeFinishUtils(Context context, Scroller scroller) {
mScroller = scroller;
mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mStatusBarHeight =getStatusBarHeight(context);
}

public void setContentView(View view) {
this.mView = view;
}

public void setSwipeToTouchAction(SwipeFinishAction action) {
this.mAction = action;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
Log.i("TAG", "SwipeToClose----------------------onInterceptTouchEvent:" + ev.getAction() + ",mIsBeingDragged:" + mIsBeingDragged);
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
if (!mDownOver) {
break;
}
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
break;
}

final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
break;
}

final int x = (int) ev.getX(pointerIndex);
final int y = (int) ev.getY(pointerIndex);
final int xDiff = Math.abs(x - mLastMotionX);
final int yDiff = Math.abs(y - mLastMotionY);
boolean hasScrollToRight = x > mLastMotionX;
boolean hasCanScroll = !mInChild || (hasScrollToRight && mAction != null && mAction.onScrollToClose());
if (xDiff > yDiff && xDiff > mTouchSlop && hasCanScroll) {
mIsBeingDragged = true;
mLastMotionX = x;
mMoveMotionX = x;
mLastMotionY = y;
mMoveMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
final ViewParent parent = mView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
}

case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
mLastMotionX = x;
mMoveMotionX = x;
mLastMotionY = y;
mMoveMotionY = y;
mActivePointerId = ev.getPointerId(0);

initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);

mIsBeingDragged = !mScroller.isFinished();
Point point = new Point(mMoveMotionX, addYForSDK19(mLastMotionY));
Rect rect = new Rect();
mInChild = mAction != null && mAction.inChild(rect, point);

if (mAction != null && !mIsBeingDragged) {
mAction.onTouchDown();
}
mDownOver = mLastMotionX <= mScreenWidth / 2;
break;
}

case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
mDownOver = false;
mInChild = false;
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
return mIsBeingDragged;
}

private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionX = (int) ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}

public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
MotionEvent vtev = MotionEvent.obtain(ev);
final int actionMasked = ev.getActionMasked();

switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
if (mView instanceof ViewGroup) {
if (((ViewGroup) mView).getChildCount() == 0)
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = mView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}

if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}

mLastMotionX = (int) ev.getX();
mMoveMotionX = mLastMotionX;

mLastMotionY = (int) ev.getY();
mMoveMotionY = mLastMotionY;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
break;
}

final int x = (int) ev.getX(activePointerIndex);
int deltaX = mMoveMotionX - x;
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mMoveMotionY - y;
if (!mIsBeingDragged && Math.abs(deltaX) >= Math.abs(deltaY) && Math.abs(deltaX) > mTouchSlop) {
final ViewParent parent = mView.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
if (deltaX > 0) {
deltaX -= mTouchSlop;
} else {
deltaX += mTouchSlop;
}
}
if (mIsBeingDragged) {
if (mView.getScrollX() + deltaX >= 0) {
deltaX = -mView.getScrollX();
}
mView.scrollBy(deltaX, 0);
}
mMoveMotionX = x;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
int scrollX = mView.getScrollX();
int dx = Math.abs(scrollX);
boolean finish = false;
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
if (initialVelocity > 0) {
finish = true;
dx = -(mView.getWidth() - dx);
}
} else if (Math.abs(mLastMotionX - ev.getX()) > mView.getWidth() / 2) {
finish = true;
dx = -(mView.getWidth() - dx);
}
scrollTo(scrollX, dx, finish);
mActivePointerId = INVALID_POINTER;
}
mDownOver = false;
mInChild = false;
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mLastMotionX = (int) ev.getX(index);
mMoveMotionX = mLastMotionX;
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
mMoveMotionX = mLastMotionX;
break;
}

if (mVelocityTracker != null) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
if (mAction != null) {
mAction.onScroll();
}
return true;
}

private void scrollTo(int startX, int dx, boolean finish) {
if (mAction != null) {
mAction.startScroll(startX, dx, finish);
}
}


public void setActivityFullScreen(boolean fullScreen) {
mFullScreen = fullScreen;
}

private int addYForSDK19(int y) {
if (mFullScreen) {
return y;
} else {
return y + mStatusBarHeight;
}
}

private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}

private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}

private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}


/**
* 获取状态栏的高度
*
* @param context
* @return
*/

public int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
int x = (Integer) field.get(o);
statusBarHeight = context.getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
if (context instanceof Activity) {
Rect frame = new Rect();
((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
statusBarHeight = frame.top;
}
}
return statusBarHeight;
}


}

两个接口类:


public interface SwipeFinishAction extends SwipeFinishTouchCheckAction {

void startScroll(int startX, int dx, boolean finish);

void onTouchDown();

void onScroll();

}

public interface SwipeFinishTouchCheckAction {

/**
* 向右滑动是否可关闭activity
* @return
*/

boolean onScrollToClose();

/**
* 是否点击横向滑动的view
* @param rect
* @param point
* @return
*/

boolean inChild(Rect rect, Point point);
}

如何使用,已经写在了两个Activity里面了,就是在setContentView之后,执行BaseSwipeFinishActivity里面的addSwipeFinishLayout()方法就行了,还需要把App的主题换一下,然后就可以了。非常之简单。

    <style name="AppTheme" parent="android:Theme.Light">
//这里是颜色值为#00000000
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:splitMotionEvents">false</item>
<item name="android:overScrollMode">never</item>
<item name="android:scrollbars">none</item>
</style>

附上demo下载地址

如果有大神,有更加好的集成方式,请在下边留言哈…