大屏幕手机在返回前页操做时,点击左上角的 APP 内返回键或者手机自带的返回按键都不是很方便,这时候能经过屏幕侧滑退出当前页面体验就会好不少了。可是 Android 系统并无想 IOS 同样自带侧滑返回,好在 Android 轮子比较多,本文记录一下我的开源项目 PandaEye 中使用的侧滑返回库 SwipBackLayout 。该库参考 github 上的开源库 SwipeBackLayout 作了一些简化;php
侧滑返回的实现是基于 Activity 的,能够直接继承 Activity 或者继承本身应用实现的 BaseActivity 而后实现 SwipeBackLayout.SwipeListener 接口便可.java
public class SwipeBackActivity extends BaseActivity implements SwipeBackLayout.SwipeListener {
protected SwipeBackLayout layout;
private ArgbEvaluator argbEvaluator;
@SuppressLint("InflateParams")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = (SwipeBackLayout) LayoutInflater.from(this).inflate(
R.layout.swipeback_base, null);
layout.attachToActivity(this);
argbEvaluator = new ArgbEvaluator();
layout.addSwipeListener(this);
if (Build.VERSION.SDK_INT >= 23) {
currentStatusColor = getResources().getColor(R.color.colorPrimaryDark, null);
} else {
currentStatusColor = getResources().getColor(R.color.colorPrimaryDark);
}
}
// 提供给子类设置 ViewPager 的接口,用于 SwipeLayout 中处理滑动冲突
public void addViewPager(ViewPager pager) {
layout.addViewPager(pager);
}
}复制代码
须要侧滑返回的 Activity 继承 SwipeBackActivity 便可实现侧滑返回的功能了,可是侧滑过程当中返回界面会被默认的窗口背景颜色覆盖,所以咱们须要把实现侧滑返回的界面的 theme 作一些小小的优化,将背景设置为透明状态,并设置进入和退出的动画。
style 中的属性设置android
<!--全屏加透明-->
<style name="TranslucentFullScreenTheme" parent="AppTheme"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowIsTranslucent">true</item> <!--<item name="android:windowAnimationStyle">@android:style/Animation</item>--> <item name="android:windowAnimationStyle">@style/AnimationActivity</item> </style>
<!--动画设置-->
<style name="AnimationActivity" mce_bogus="1" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/base_slide_right_in</item> <item name="android:activityOpenExitAnimation">@anim/base_slide_right_out</item> <item name="android:activityCloseEnterAnimation">@anim/base_slide_right_in</item> <item name="android:activityCloseExitAnimation">@anim/base_slide_right_out</item> </style>复制代码
界面进入动画git
<!--base_slide_right_in-->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="300" android:fromXDelta="100.0%" android:interpolator="@android:anim/decelerate_interpolator" android:toXDelta="0.0%" />
</set>复制代码
界面退出动画github
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="300" android:fromXDelta="100.0%" android:interpolator="@android:anim/decelerate_interpolator" android:toXDelta="0.0%" />
</set>复制代码
而后在 manifest 文件中将继承 SwipeBackActivity 的 Activity 的 theme 设置为 TranslucentFullScreenTheme 便可解决滑动过程当中背景覆盖问题。ide
要明白侧滑返回的原理咱们得先明白 Android Activity 界面的视图层级关系:工具
经过 SDK 自带的视图分析工具 Hierarchy View 咱们能够看到视图的以下分布:布局
public void attachToActivity(Activity activity) {
mActivity = activity;
TypedArray a = activity.getTheme().obtainStyledAttributes(
new int[]{android.R.attr.windowBackground});
int background = a.getResourceId(0, 0);
a.recycle();
//获取到 DecorView 对象
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
Log.i("decorChildCount", decor.getChildCount() + "");
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
Log.i("decorChild", decorChild.toString());
//重置背景色资源
decorChild.setBackgroundResource(background);
//decorView 中将子布局移除
decor.removeView(decorChild);
//SwipeBackLayout 添加从decorView中移除布局
addView(decorChild);
//将ContentView设置为decorChild的父布局即添加进来的SwipeBackLayout
setContentView(decorChild);
//将SwipeBackLayout添加进DecorView
decor.addView(this);
}复制代码
从中我添加的注释不难看出,实现替换的流程:post
在 SwipeBackLayout 中经过重写 onInterceptTouchEvent(MotionEvent ev) 方法和 onTouchEvent(MotionEvent ev) 方法来实现侧滑返回事件的处理及对 ViewPager 滑动的兼容的。优化
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//处理ViewPager冲突问题
ViewPager mViewPager = getTouchViewPager(mViewPagers, ev);
//当无触摸ViewPager或者该ViewPager未滑动到最左则不对滑动时间进行拦截
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();
canSwipe = downX <= viewWidth / 2;
if (!canSwipe) {
return super.onInterceptTouchEvent(ev);
}
break;
case MotionEvent.ACTION_MOVE:
if (!canSwipe) {
return super.onInterceptTouchEvent(ev);
}
int moveX = (int) ev.getRawX();
// 知足此条件屏蔽SildingFinishLayout里面子类的touch事件
if (moveX - downX > mTouchSlop
&& Math.abs((int) ev.getRawY() - downY) < mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}复制代码
在手指按下的时候相较于 onTouchEvent() 方法 onInterceptTouchEvent() 方法会先执行,在此方法中先判断当前触摸是否为 ViewPager,是 ViewPager 则判断是否滑动到了 ViewPager 的最左侧。若是触摸的 ViewPager 且未滑动到最左侧则不对事件进行拦截交给 ViewPager 处理触摸事件,不然触摸位置进行判断,在有效区域内则记录触摸开始点,不然按系统默认方式处理。在移动事件中会根据按下事件的判断结果决定是否按默认方式处理,当须要处理侧滑时会再次判断若是 X 方向的滑动大于最小有效滑动距离 Y方向滑动距离小于最小有效滑动距离则这次事件将会被 SwipeBackLayout 所消费,将进入 SwipeBackLayout 的 onTouchEvent() 方法中的处理逻辑。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (!canSwipe) {
return super.onInterceptTouchEvent(event);
}
int moveX = (int) event.getRawX();
int deltaX = tempX - moveX;
tempX = moveX;
if (moveX - downX > mTouchSlop
&& Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
isSilding = true;
}
if (moveX - downX >= 0 && isSilding) {
//deltaX 为单次移动的距离向右滑为负数
// TODO: 2017/6/22 实现 y 方向的移动,即向右任意方向滑出界面
mContentView.scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
if (!canSwipe) {
return super.onInterceptTouchEvent(event);
}
isSilding = false;
if (mContentView.getScrollX() <= -viewWidth / 4) {
isFinish = true;
scrollRight();
} else {
scrollOrigin();
isFinish = false;
}
break;
}
return true;
}复制代码
一样此方法中也会根据 onInterceptTouchEvent() 中的 DOWN 事件的断定结果 canSwipe 来决定是否按默认方式消费事件,MOVE 事件中若是知足侧滑条件则会调用 scrollBy() 将 mContentView 按滑动方向进行移动,而此处的 mContentView 便是 SwipeBackLayout 自身,所以整个显示的界面会被按照滑动方向移动。当手指抬起时若是滑动距离超过 1/4 界面宽度(能够按本身需求调整),则视为侧滑返回完成,让 Scroller 自动完成剩余距离的滑动,不然让 Scroller 恢复到滑动起始位置
/** * 滚动出界面 */
private void scrollRight() {
final int delta = (viewWidth + mContentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,咱们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(mContentView.getScrollX(), 0, -delta + 1, 0,
Math.abs(delta));
postInvalidate();
}
/** * 滚动到起始位置 */
private void scrollOrigin() {
int delta = mContentView.getScrollX();
// 调用startScroll方法来设置一些滚动的参数,咱们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(mContentView.getScrollX(), 0, -delta, 0,
Math.abs(delta));
postInvalidate();
}
/** * 具体执行 Scroller 中的滚动及将滑动距离传递给外部接口 */
@Override
public void computeScroll() {
Log.i("computeScroll","computeScroll");
if (mSwipeListener != null) {
double scrollx = Math.abs(mContentView.getScrollX());
double offset = scrollx / viewWidth;
if (offset > 0.9) {
offset = 1d;
}
mSwipeListener.swipeValue(offset);
}
if (mScroller.computeScrollOffset()) {
Log.i("computeScroll","mScroller");
mContentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
if (mScroller.isFinished() && isFinish) {
mActivity.finish();
}
}
}复制代码
以上就是简化后的侧滑返回的基本使用和原理的简单分析,完整代码能够参考 PandaEye欢迎 Star。文章一遍过为反复检查若有不妥之处欢迎你们踊跃交流。