前言
在如今 app 泛滥的年代里,越来越多的开发者注重用户体验这个方面了。其中,有很多的 app 都有一种功能,那就是滑动返回。比如知乎、百度贴吧等,用户在使用这一类的 app 都可以滑动返回上一个页面。不得不说这个设计很赞,是不是心动了呢?那就继续往下看吧!
在GitHub上有实现该效果的开源库 SwipeBackLayout ,可以看到该库发展得已经非常成熟了。仔细看源码你会惊奇地发现其中的奥秘,没错,正是借助了 ViewDragHelper 来实现滑动返回的效果。ViewDragHelper 我想不必多说了,在我的博客中有很多的效果都是通过它来实现的。那么,下面我们就使用 ViewDragHelper 来实现这个效果吧。
自定义属性
首先,我们应该先定义几个自定义属性,比如说支持用户从左边或者右边滑动返回,丰富用户的选择性。所以现在 attrs.xml 中定义如下属性:
1 | <?xml version="1.0" encoding="utf-8"?> |
从上面的 xml 中可知,定义了一个枚举属性,左边为0,右边为1。
然后主角 SwipeBackLayout 就要登场了。
public class SwipeBackLayout extends FrameLayout {
private ViewDragHelper mViewDragHelper;
// 主界面
private View mainView;
// 主界面的宽度
private int mainViewWidth;
// 模式,默认是左滑
private int mode = MODE_LEFT;
// 监听器
private SwipeBackListener listener;
// 是否支持边缘滑动返回, 默认是支持
private boolean isEdge = true;
private int mEdge;
// 阴影Drawable
private Drawable shadowDrawable;
// 阴影Drawable固有宽度
private int shadowDrawbleWidth;
// 已经滑动的百分比
private float movePercent;
// 滑动的总长度
private int totalWidth;
// 默认的遮罩透明度
private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
// 遮罩颜色
private int scrimColor = DEFAULT_SCRIM_COLOR;
// 透明度
private static final int ALPHA = 255;
private Paint mPaint;
/**
* 滑动的模式,左滑
*/
public static final int MODE_LEFT = 0;
/**
* 滑动的模式,右滑
*/
public static final int MODE_RIGHT = 1;
// 最小滑动速度
private static final int MINIMUM_FLING_VELOCITY = 400;
private static final String TAG = "SwipeBackLayout";
public SwipeBackLayout(Context context) {
this(context, null);
}
public SwipeBackLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeBackLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout);
// 得到滑动模式,默认左滑
mode = a.getInt(R.styleable.SwipeBackLayout_swipe_mode, MODE_LEFT);
a.recycle();
initView();
}
...
}
initView
在构造器主要做的就是得到滑动模式,默认是左边滑动。之后调用 initView() 。那么我们来看看 initView() 的代码:
1 | // 初始化阴影Drawable |
在 initView() 中,设置了 mViewDragHelper 的最小滑动速度,并且设置了 mViewDragHelper 回调的接口。回调接口中的方法都有注释,相信大家应该都能看懂。另外在 initView() 中初始化了阴影图片,以备下面中使用。
drawChild
想要阴影在滑动中绘制出来,我们必须重写 drawChild(Canvas canvas, View child, long drawingTime) 方法,并且在 onTouchEvent(MotionEvent event) 里 invalidate() ,保证用户滑动过程中调用 drawChild(Canvas canvas, View child, long drawingTime) 方法。
1 | @Override |
在 drawChild(Canvas canvas, View child, long drawingTime) 中调用 drawShadowDrawable(Canvas canvas, View child) 来绘制阴影以及 drawScrimColor(Canvas canvas, View child) 来绘制遮罩层。下面分别是两个方法的源码:
1 | // 绘制阴影 |
mainView 、阴影、遮罩层的关系示意图如下:

onViewReleased
看完了上面的两个方法的代码,最后就是当用户手指抬起时判断逻辑的代码了:
1 | /** |
相应的代码还是比较简单的,主要使用了 smoothSlideViewTo(View view, int left, int top) 的方法来滑动到指定位置。若是结束当前界面的话,回调监听器的接口。
啰嗦了这么多,我们来看看运行时的效果图吧:

尾语
好了,SwipeBackLayout 大致的逻辑就是上面这样子的。整体来说还是比较通俗易懂的,而且对 ViewDragHelper 熟悉的人会发现,使用 ViewDragHelper 自定义一些 ViewGroup 的套路都是大同小异的。以后想要自定义一些 ViewGroup 都是得心应手了。
如果对此有疑问的话可以在下面留言。
最后,国际惯例,附上 SwipeBackLayout Demo 的源码:
Goodbye!