Activity右滑返回效果实现
1. 实现思路
- 参考文章(http://blog.csdn.net/xiaanming/article/details/20934541)
- 我们想要对一个Activity整体进行滑动,我们就需要对这个Activity布局文件的顶层布局的父布局进行滑动,我们应该在这个父布局上下功夫。通过源码可以知道,其实Android系统在加载自定义布局时,还会对我们的布局文件的最外层套一个FrameLayout,所以我们其实就是对FrameLayout进行滚动就行了,而这个FrameLayout我们可以通过window对象的getDecorView()方法获取到。
- 滑动相关操作我们借助Scroller类来做处理。
- 这次要实现的效果,就是在一个自定义View上结合Scroller类来做处理,从而达到想要的效果。在使用时,我们只要把Activity的顶层父布局替换成这个View就可以了。
2. 实现过程
2.1 实现的效果
- 可以设置屏幕响应区域,边缘响应还是全局响应
- 支持设置滑动区域超过多少才执行退出
- 支持设置一个速率,如果是快速滑动,直接退出
- 与 listView、scrollView、viewPager 冲突解决
2.2 SwipeBackLayout的关键代码
- Touch 事件的拦截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 处理ViewPager冲突问题
ViewPager mViewPager = getTouchViewPager(mViewPagers, ev);
Log.d(TAG, "mViewPager = " + mViewPager);
if (mViewPager != null && mViewPager.getCurrentItem() != 0) {
return super.onInterceptTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = tempX = (int) ev.getRawX();
downY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) ev.getRawX();
// 满足此条件屏蔽SwipeBackLayout里面子类的touch事件
if (downX <= mTouchLimit
&& moveX - downX > mTouchSlop
&& Math.abs((int) ev.getRawY() - downY) < mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
- Touch 事件的相关处理
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int deltaX = tempX - moveX;
tempX = moveX;
if (downX <= mTouchLimit
&& moveX - downX > mTouchSlop
&& Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
mIsSwiping = true;
}
if (moveX - downX >= 0 && mIsSwiping) {
mContentView.scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
mIsSwiping = false;
if (mVelocityTracker != null) {
mVelocityTracker.computeCurrentVelocity(1);// 判断每毫秒内滑动了多少px
Log.d("getXVelocity()","" +mVelocityTracker.getXVelocity(0));
if (mVelocityTracker.getXVelocity(0) > MOVE_SPEED_LIMIT) {
mIsFinished = true;
scrollRight(true);
break;
}
}
if (mContentView.getScrollX() <= -mLayoutWidth / 2) {
mIsFinished = true;
scrollRight(false);
} else {
mIsFinished = false;
scrollOrigin();
}
break;
}
return true;
}
- 整体滑动的相关处理
/**
* 滚动出界面
* @param isNeedQuick 是否需要快速滚出
*/
private void scrollRight(boolean isNeedQuick) {
MeilaLog.d(TAG, "scrollRight is doing, isNeedQuick: " + isNeedQuick);
final int delta;
if (isNeedQuick) {
delta = (mLayoutWidth + mContentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item,duration默认250
mScroller.startScroll(mContentView.getScrollX(), 0, -delta + 1, 0);
} else {
delta = (mLayoutWidth + mContentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(mContentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
}
postInvalidate();
}
/**
* 滚动到起始位置
*/
private void scrollOrigin() {
MeilaLog.d(TAG, "scrollOrigin is doing");
int delta = mContentView.getScrollX();
mScroller.startScroll(mContentView.getScrollX(), 0, -delta, 0,
Math.abs(delta));
postInvalidate();
}
@Override
public void computeScroll() {
// 调用startScroll的时候scroller.computeScrollOffset()返回true
if (mScroller.computeScrollOffset()) {
mContentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
if (mScroller.isFinished() && mIsFinished) {
releaseVelocityTracker();
mActivity.finish();
}
}
}
2.3 Activity 背景透明处理
因为该效果在当前Activity整体滑动时,要看见底部的另一个Activity,因此当前Activity的父布局背景需要设置为透明。
注意:要达到我们设想的效果,如果这里仅仅是把背景颜色设置为透明是不可行的,而需要设置Activity的theme。
其中下面这两个属性的设置是必须的:
“android:windowBackground” = “android:color/transparent”
“android:windowIsTranslucent” = “true”
3. 问题总结
3.1 遇到的疑难问题
设置了属性“android:windowIsTranslucent” = “true”后,出现了以下两个问题:
- 控件所监听的点击事件会被重复响应
- 原来设置的Activity的跳转动画失效了
3.2 问题的解决
- 点击事件的重复响应,通过拦截Touch事件,设置点击延时响应解决
private long lastClickTime = 0;
private int mDownInScreenX = 0;
private int mDownInScreenY = 0;
private int mUpInScreenX = 0;
private int mUpInScreenY = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
Log.d("mm","ACTION_UP");
mUpInScreenX = (int) ev.getRawX();
mUpInScreenY = (int) ev.getRawY();
if (Math.abs(mUpInScreenX - mDownInScreenX) < 5 &&
Math.abs(mUpInScreenY - mDownInScreenY) < 5) {
Log.d("mm",""+(System.currentTimeMillis() - lastClickTime));
if (System.currentTimeMillis() - lastClickTime < MeilaResource.MIN_CLICK_DELAY_TIME) {
lastClickTime = System.currentTimeMillis();
return true;
}
lastClickTime = System.currentTimeMillis();
}
break;
case MotionEvent.ACTION_DOWN:
MeilaLog.d("mm","ACTION_DOWN");
mDownInScreenX = (int) ev.getRawX();
mDownInScreenY = (int) ev.getRawY();
break;
}
return super.dispatchTouchEvent(ev);
}
- Activity的跳转动画通过设置
android:windowAnimationStyle
来解决
进入动画:设置windowEnterAnimation
退出动画:设置windowExitAnimation
4. 如何使用
在Activity 基类中添加以下逻辑:
if (isNeedSlipFinish()) {
mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(this).inflate(R.layout.slip_back_base_layout,
null);
mSwipeBackLayout.attachToActivity(this);
}
新建 Activity 继承于这个基类,即可拥有向右滑动返回效果。