手写 ViewPager,以及 Android 中的拖拽操作
14_手写 ViewPager,以及 Android 中的拖拽操作
ViewGroup 的触摸反馈、ViewPager
ViewGroup的触摸反馈
-
除了重写 onTouchEvent(),还需要重写 onInterceptTouchEvent()
-
onInterceptTouchEvent()中,ACTION_DOWN 事件做的事和 onTouchEvent()基本一致或完全一致
- 原因:ACTION_DOWN在多数手势中起到的是起始记录的作用(例如记录手指落点),而 onInterceptTouchEvent()调用后,onTouchEvent()未必会被调用,因此需要把这个记录责任转交给 onInterceptTouchEvent()。
- 有时ACTION_DOWN事件也会在经过onInterceptTouchEvent()之后再转交给自己的 onTouchEvent()(例如当没有触摸到子View或者触摸到的子View没有消费事件时)。因此需要确认在onInterceptTouchEvent()和onTouchEvent()都被调用时,程序状态不会出问题。
-
onInterceptTouchEvent()中,ACTION_MOVE 一般的作用是确认滑动,即当用户朝某一方向滑动一段距离(touch slop)后,ViewGroup要向自己的子View和父View确认自己将消费事件。
- 确认滑动的方式:Math.abs(event.getX() - downX) > ViewConfiguration.getXxxSlop()
- 告知子View的方式:在onInterceptTouchEvent()中返回true,子View会收到 ACTION_CANCEL事件,并且后续事件不再发给子View
- 告知父 View 的方式:调用 getParent().requestDisallowInterceptTouchEvent(true)。这个方法会递归通知每一级父View,让他们在后续事件中不要再尝试通过 onInterceptTouchEvent()拦截事件。这个通知仅在当前事件序列有效,即在这组事件结束后(即用户抬手后),父View会自动对后续的新事件序列启用拦截机制
VelocityTracker
-
如果GestureDetector不能满足需求,或者觉得GestureDetector过于复杂,可以自己处理 onTouchEvent()的事件。但需要使用VelocityTracker来计算手指移动速度。
-
使用方法:
-
在每个事件序列开始是(即ACTION_DOWN事件到来时),通过VelocityTracker.obtain() 创建一个实例,或者使用velocityTracker.clear()把之前的某个实例重置
-
对于每个事件(包括ACTION_DOWN事件),使用 velocityTracker.addMovement(event)把事件添加进 VelocityTracker
-
在需要速度的时候(例如在ACTION_UP中计算是否达到fling速度),使用 velocityTracker.computeCurrentVelocity(1000, maxVelocity)来计算实时速度,并通过 getXVelocity() / getYVelocity()来获取计算出的速度
-
方法参数中的1000是指的计算的时间长度,单位是ms。例如这里填入1000,那么 getXVelocity()返回的值就是每1000ms (即一秒)时间内手指移动的像素数
-
第二个参数是速度上限,超过这个速度时,计算出的速度会回落到这个速度。例如这里填了200,而实时速度是300, 那么实际的返回速度将是200
- maxVelocity 可以通过 viewConfiguration.getScaledMaximumFlingVelocity() 来获取
-
-
scrollTo / scrollBy 和 computeScroll()
-
scrollTo() / scrollBy()会设置绘制时的偏移,通常用于滑动控件设置偏移
-
scroll值表示绘制行为在控件内部内容的起始偏移(类似:我要从内容的第300个像素开始绘制),因此scrollTo()内的参数填正值时,绘制内容会向负向移动
-
scrollTo()是瞬时方法,不会自动使用动画。如果要用动画,需要配合View.computeScroll()方 法
-
computeScroll()在View重绘时被自动调用
-
使用方式:
// onTouchEvent()中: overScroller.startScroll(startX, startY, dx, dy); postInvalidateOnAnimation(); // onTouchEvent()外: @Override public void computeScroll() { if (overScroller.computeScrollOffset()) { // 计算实时位置 scrollTo(overScroller.getCurrX(), overScroller.getCurrY()); // 更新界面 postInvalidateOnAnimation(); // 下一帧继续 } }
完整代码
public class TwoPager extends ViewGroup {
float downX;
float downY;
float downScrollX;
boolean scrolling;
float minVelocity;
float maxVelocity;
OverScroller overScroller;
ViewConfiguration viewConfiguration;
VelocityTracker velocityTracker = VelocityTracker.obtain();
public TwoPager(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
overScroller = new OverScroller(context);
viewConfiguration = ViewConfiguration.get(context);
maxVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
minVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
int childTop = 0;
int childRight = getWidth();
int childBottom = getHeight();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(childLeft, childTop, childRight, childBottom);
childLeft += getWidth();
childRight += getWidth();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
velocityTracker.clear();
}
velocityTracker.addMovement(ev);
boolean result = false;
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
scrolling = false;
downX = ev.getX();
downY = ev.getY();
downScrollX = getScrollX();
break;
case MotionEvent.ACTION_MOVE:
float dx = downX - ev.getX();
if (!scrolling) {
if (Math.abs(dx) > viewConfiguration.getScaledPagingTouchSlop()) {
scrolling = true;
getParent().requestDisallowInterceptTouchEvent(true);
result = true;
}
}
break;
}
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
velocityTracker.clear();
}
velocityTracker.addMovement(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
downScrollX = getScrollX();
break;
case MotionEvent.ACTION_MOVE:
float dx = downX - event.getX() + downScrollX;
if (dx > getWidth()) {
dx = getWidth();
} else if (dx < 0) {
dx = 0;
}
scrollTo((int) (dx), 0);
break;
case MotionEvent.ACTION_UP:
velocityTracker.computeCurrentVelocity(1000, maxVelocity);
float vx = velocityTracker.getXVelocity();
int scrollX = getScrollX();
int targetPage;
if (Math.abs(vx) < minVelocity) {
targetPage = scrollX > getWidth() / 2 ? 1 : 0;
} else {
targetPage = vx < 0 ? 1 : 0;
}
int scrollDistance = targetPage == 1 ? (getWidth() - scrollX) : - scrollX;
overScroller.startScroll(getScrollX(), 0, scrollDistance, 0);
postInvalidateOnAnimation();
break;
}
return true;
}
@Override
public void computeScroll() {
if (overScroller.computeScrollOffset()) {
scrollTo(overScroller.getCurrX(), overScroller.getCurrY());
postInvalidateOnAnimation();
}
}
}
本文地址:https://blog.csdn.net/aha_jasper/article/details/110672173