关于View的滑动,Android中提供了许多方法,具体可以分为一下几类:
layout
在ACTION_MOVE中通过获取x、y的偏移量动态布局view,并禁止向父控件传递事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
layout(
getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY
);
break;
}
return true;
}
offsetLeftAndRight和offsetTopAndBottom
将上述layout方法改为该两个方法的组合,偏移量计算同上:
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
break;
LayoutParams
实现方法和layout相似,动态设置布局参数,缺点是如果view参数加了rules,会导致无法滑动:
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
ViewGroup.MarginLayoutParams params
= (ViewGroup.MarginLayoutParams) getLayoutParams();
params.leftMargin = getLeft() + offsetX;
params.topMargin = getTop() + offsetY;
setLayoutParams(params);
break;
ScrollBy
scrollBy移动的是view中的内容或者ViewGroup中的所有子view,故需先获得父控件的实例;同时scrollBy参数的正负是和android坐标轴的正负方向相反的,故需队offset值取反:
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
ViewDragHelper
该类非常强大,基本可以各种不同的滑动、拖放需求。support库中DragLayout也是基于此类实现侧滑效果。使用该类一般需要如下几个步骤:
- 继承自ViewGroup,在构造中初始化ViewDragHelper对象,传入的两个参数分别为当前容器对象和ViewDragHelper中的回调接口,该回调中封装了子view的滑动操作相关api
public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewDragHelper = ViewDragHelper.create(this, mCallBack);
}
- 触摸事件拦截与处理,需要将事件传递给ViewDragHelper处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
- 处理ViewDragHelper中回调Callback
private ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {
/**
* 指定可以滑动的子View,比如当前只有mMainView可以滑动
* @param child
* @param pointerId
* @return true 指定的子view可以滑动
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mMainView;
}
/**
* 控制子view水平滑动的距离,默认是0 不可以滑动
* @param child
* @param left
* @param dx
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
/**
* 功能同上,垂直方向滑动的距离
* @param child
* @param top
* @param dy
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
/**
* 当拖拽子view之后手指离开屏幕时触发;
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
}
};
以上代码即可实现view滑动的功能。但如果想要实现手指离开屏幕view自动滑动到指定位置的功能时,需要在Callback中重写onViewReleased()
方法。ViewDragHelper提供了smoothSlideViewTo()
方法,可以实现view的自动滑动效果,当然其内部也是通过Scroller类实现的,故需要重写computeScroll()
方法。
如下是一个模拟google DragLayout的一个Demo,当然只实现了其中侧滑的一个小功能,其他事件分发处理逻辑都没有具体实现。
package com.ts.test.widget;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
/**
* Created by tiansen on 17-11-18.
*/
public class MyDragLayout extends FrameLayout {
private static final String TAG = "MyDragLayout";
private ViewDragHelper mViewDragHelper;
/**
* 侧边栏View
*/
private View mMenuView;
/**
* 主View
*/
private View mMainView;
/**
* 侧边栏View宽度
*/
private int mMenuWidth;
/**
* 主View宽度
*/
private int mMainWidth;
private ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {
/**
* 指定可以滑动的子View
* @param child
* @param pointerId
* @return true 指定的子view可以滑动
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mMainView;
}
/**
* 控制子view水平滑动的距离,默认是0 不可以滑动
* @param child
* @param left
* @param dx
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// 控制滑动边界
return (left < 0 ? 0 : left) < mMenuWidth ? left : mMenuWidth;
}
/**
* 功能同上,垂直方向滑动的距离
* @param child
* @param top
* @param dy
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
/**
* 当拖拽子view之后手指离开屏幕时触发;
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mMainView.getLeft() < mMenuWidth / 2) {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(MyDragLayout.this);
} else {
mViewDragHelper.smoothSlideViewTo(mMainView, mMenuWidth, 0);
ViewCompat.postInvalidateOnAnimation(MyDragLayout.this);
}
}
};
public MyDragLayout(Context context) {
this(context, null);
}
public MyDragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewDragHelper = ViewDragHelper.create(this, mCallBack);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMainWidth = mMainView.getMeasuredWidth();
mMenuWidth = mMenuView.getMeasuredWidth();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}