日常使用app的过程中,可以发现很多app都具备侧滑退出当前界面的功能,就像微信、贴吧极速版之类的。
这种操作对于提升使用体验还是有一些效果的,下面就来看一下,怎么通过ViewDragHelper去实现这样的效果。
关于ViewDragHelper,这里不做太多介绍,简单来说它是由V4包提供的用来帮助你更轻松的处理复杂的手势。
好了,下面是具体的实现,首先,创建一个View继承一个布局:
public class SwipeOutView extends LinearLayout {
private ViewDragHelper viewDragHelper;
private Point currentPoint = new Point(); //用来表示当前位置
public SwipeOutView(Context context) {
this(context, null);
}
public SwipeOutView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeOutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
viewDragHelper = ViewDragHelper.create(this, 1.0f, viewDragCallBack);
}
}
在ViewDragHelper.create()方法中三个参数分别如下:
- ViewGroup forParent:使用ViewDragHelper所指定的父布局,有了父布局,才能对子布局或者控件进行操作。
- float sensitivity:用于设置touchSlop的距离,表示系统判定的有效滑动距离,这个值越大,那么touchSlop就越小。
- ViewDragHelper.Callback cb:这个就是最重要的参数了,这个回调就是我们处理手势的核心。
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
接下来,我们实现回调方法:
private ViewDragHelper.Callback viewDragCallBack = new ViewDragHelper.Callback() {
//返回true表示捕获相关View,可以根据第一个参数child决定捕获哪个View。
@Override
public boolean tryCaptureView(View child, int pointerId) {
return false;
}
//获取水平方向的坐标,left表示child的x轴坐标(相对于ViewGroup)
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//getWidth():viewGroup的遍历每个子view,子view的layout()方法测量的结果。测量方式:getWidth=子布局右侧-子布局左侧;
// getMeasuredWidth():viewGroup的遍历每个子view,子view的最近一次调用measure()方法测量后得到的,就是View的宽度。
if (left < 0){
left = 0;
}
currentPoint.x = left;
return left;
}
//获取垂直方向的坐标,这里返回0用于阻止页面上下移动
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
currentPoint.y = top;
return 0;
}
//当拖拽的View被释放后的回调
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (viewDragHelper.isEdgeTouched(ViewDragHelper.EDGE_LEFT)){
//这里表示如果滑动距离超过屏幕二分之一,就滑到消失,否则当作没有滑动
if (currentPoint.x > getWidth() / 2){
viewDragHelper.settleCapturedViewAt(getWidth(), 0);
} else {
viewDragHelper.settleCapturedViewAt(0, 0);
}
}
currentPoint.x = 0;
currentPoint.y = 0;
invalidate(); //进行刷新
}
//开始边缘拖拽的回调
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
//这里去进行控件捕获,捕获对象是SwipeOutView的第一个子布局
viewDragHelper.captureChildView( getChildAt(0), pointerId);
}
//当控件停止时的位置
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//表示滑动完成
if (left >= getWidth()){
swipeCallBack .onFinish();
}
}
};
上面的方法中,我们在最后的onViewPositionChanged调用了一个回调的方法,这个方法可以用来退出Activity,首先创建这个回调接口:
public interface SwipeCallBack {
void onFinish();
}
在SwipeOutView中添加:
private SwipeCallBack swipeCallBack;
public SwipeOutView setSwipeCallBack(SwipeCallBack swipeCallBack) {
this.swipeCallBack = swipeCallBack;
return this;
}
当我们想通过滑动退出Activity的时候去实现这个接口就可以了。
不过这个时候当然是不能滑动的,毕竟我们连拦截事件和触摸事件都没处理呢:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
同时不要忘了设置拖拽模式:
viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
以及重写computeScroll()方法:
@Override
public void computeScroll() {
if (viewDragHelper.continueSettling(true)){
invalidate();
}
}
通过在computeScroll()中调用invalidate()方法,可以在滑动的时候对界面事实刷新,如果没有重写这个方法,你会发现,滑到一半松手,界面是会停在你松手的地方的。
接下来是主界面的布局以及代码了:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.thefirstgamenotebook.testforviewdraghelper.SwipeOutView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeOutView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_red_light"
android:padding="20dp"
android:text="测试Activity"
android:textColor="@android:color/white" />
</LinearLayout>
</com.example.thefirstgamenotebook.testforviewdraghelper.SwipeOutView>
由于是设置拖拽事件的是SwipeOutView的第一个子布局,所以上面的布局中所有的控件只能放在一个布局中去,这里我放到了LinearLayout中,接着是Activity的代码了:
MainActivity
public class MainActivity extends AppCompatActivity implements SwipeCallBack{
private SwipeOutView swipeOutView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeOutView = findViewById(R.id.swipeOutView);
swipeOutView.setSwipeCallBack(this);
}
@Override
public void onFinish() {
finish();
}
}
看一下运行效果怎么样:
可以看到,效果并不如理想中的样子,怎么去解决它呢?首先给Activity设置一个透明背景的主题,然后将SwipeOutView的唯一子布局背景设置为白色试试看:
在style.xml里的主题中添加:
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
同时给SwipeOutView下的LinearLayout添加:
android:background="@android:color/white"
再看看效果:
还是比较滑稽,不过这时候只要把theme设定为NoActionBar即可。
这样侧滑退出Activity就完成了!
不过这样的实现有一个小缺点,就是必须把SwipeOutView作为根布局,显然,这么做不够fasion,为了弥补这个短板,可以创建一个SwipeHelper类去完善相关逻辑:
public class SwipeHelper {
private Activity activity;
private SwipeCallBack swipeCallBack;
private SecondSwipeOutView secondSwipeOutView;
public SwipeHelper(Activity activity) {
this.activity = activity;
}
public void onActivityCreate() {
secondSwipeOutView = (SecondSwipeOutView) LayoutInflater.from(activity).inflate(R.layout.swipe_layout, null);
secondSwipeOutView.setSwipeCallBack(swipeCallBack);
secondSwipeOutView.attachToActivity(activity);
}
public SwipeHelper setSwipeCallBack(SwipeCallBack swipeCallBack) {
this.swipeCallBack = swipeCallBack;
return this;
}
}
同时在SwipeOutView中添加:
//核心代码,绑定到相应activity
public void attachToActivity(Activity activity) {
TypedArray typedArray = activity.getTheme().obtainStyledAttributes(new int[]{android.R.attr.windowBackground});
int background = typedArray.getResourceId(0, 0);
typedArray.recycle();
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);
decorChild.setBackgroundResource(background);
decorChild.setBackgroundColor(Color.parseColor("#ffffffff")); //将子布局背景设置为白色
decorView.removeView(decorChild);
addView(decorChild);
decorView.addView(this);
}
这里默认把SwipeOutView的子布局背景设置为白色,所以每次想要完成这个效果只需要对主题设置一下就ok了,在看一下第二个Activity的代码:
public class SecondActivity extends AppCompatActivity implements SwipeCallBack{
private SwipeHelper swipeHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
swipeHelper = new SwipeHelper(this);
swipeHelper.setSwipeCallBack(this);
swipeHelper.onActivityCreate();
}
@Override
public void onFinish() {
finish();
}
}
看一下运行效果:
大功告成!
最后是完整代码地址: