自定义View总结 – 触摸反馈
之前分析了Android触摸事件分发机制,在自定义View的时候进行触摸反馈,一般都是重写onTouchEvent,当然也有一些工具类可以使用,本文就对这些工具类进行总结,他们是ViewConfiguration,Scroller,OverScroller,VelocityTracker,GestureDetector,ScaleGestureDetector,ViewDragHelper。
ViewConfiguration
ViewConfiguration定义了一些UI系统用用到的常量,包括timeouts,sizes,distances。timeouts比如DEFAULT_LONG_PRESS_TIMEOUT,DOUBLE_TAP_TIMEOUT等,sizes包括SCROLL_BAR_SIZE等,distances我们平时自定义View的时候可能用的比较多,常用的有getScaledTouchSlop来判断是否是滑动,getScaledPagingTouchSlop来判断是否是翻页滑动,自己写ViewPager的时候可以用到,getScaledMaximumFlingVelocity和getMinimumFlingVelocity来对惯性滑动进行判断处理。
Scroller & OverScroller
View的scrollTo和scrollBy是瞬间完成的,如果需要View的滑动有个动画效果,说白了,就是View的位置移动有段时间间隔,可以使用Scroller或OverScroller来完成。Scroller本身无法让View滑动,它主要是个计算器,得配合View的computeScroll使用才能完成这个功能,或者不使用View的computeScroll,你自己写个Runnable,在Runnable里面进行Scroller计算完成的判断并调用View的scrollTo,然后再postOnAnimation(this)将自身传入再次调用即可。
使用computeScroll的样板代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
OverScroller的startScroll,fling方法和Scroller类似,不再赘述,除此之外OverScroller还有一个带over参数的fling函数public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)可以滑动超出View的边界。
VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度,包括水平和垂直方向的速度,一般配合Scroller的fling使用。它的使用过程很简单,首先,在View的onTouchEvent方法中追踪当前单击事件的速度:
1 2 3 | |
接着,在手指抬起,也就是ACTION_UP的时候,获取速度:
1 2 3 4 5 6 7 | |
速度的计算公式是速度=(终点位置 - 起点位置)/时间段,所以逆着手机坐标系的正方向滑动,所产生的速度为负值。另外记得要重置并回收VelocityTracker。
GestureDetector & ScaleGestureDetector
GestureDetector手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。ScaleGestureDetector主要是双指或多指的pinch zoom放大缩小行为。要使用GestureDetector也很简单,参考如下过程。
首先,创建GestureDetector对象并实现GestureDetector.OnGestureListener接口,根据需要也可以实现GestureDetector.OnDoubleTapListener接口或者GestureDetector.OnContextClickListener接口,或者使用SimpleOnGestureListener来在自己感兴趣的方法中做处理。
| 方法名 | 描述 | 所属接口 | |
|---|---|---|---|
| onDown | 手指轻触屏幕,由1个ACTION_DOWN触发 |
OnGestureListener | |
| onShowPress | 手指轻触屏幕,尚未松开或拖动,由1个ACTION_DOWN触发 |
OnGestureListener | |
| onSingleTapUp | 手指轻触屏幕后松开,随着ACTION_UP触发,这是单击行为 |
OnGestureListener | |
| onScroll | 手指按下屏幕并拖动,由1个ACTION_DOWN及多个ACTION_MOVE触发,这是拖动行为 |
OnGestureListener | |
| onLongPress | 长按 | OnGestureListener | |
| onFling | 按下屏幕快速滑动后松开,由1个ACTION_DOWN多个ACTION_MOVE和1个ACTION_UP触发,快速滑动 |
OnGestureListener | |
| onDoubleTap | 双击,由2次连续的单击组成,不可能和onSingleTapConfirmed共存 | OnDoubleTapListener | |
| onSingleTapConfirmed | 严格的单击行为 | OnDoubleTapListener | |
| onDoubleTapEvent | 发生了双击行为,在双击期间,ACTION_DOWN、ACTION_MOVE、ACTION_UP都会触发此回调 |
OnDoubleTapListener |
接着,接管目标View的onTouchEvent方法
1 2 3 | |
事件经过判断后就会回调我们实现的listener中的方法。如果只是监听滑动相关的可以自己在onTouchEvent方法的ACTION_MOVE中调用View的scrollTo(x,y)来实现View的滑动,如果是监听双击这种行为的话,就使用GestureDetector。
ScaleGestureDetector是处理放大缩小手势的,使用和GestureDetector类似。
| 方法名 | 描述 | 所属接口 | |
|---|---|---|---|
| public boolean onScale(ScaleGestureDetector detector); | 通过调用detector.getScaleFactor来获得放大的系数,来进行进一步处理,比如对ImageView的Matrix进行操作等等,返回值代表事件有没有被消费 | OnScaleGestureListener | | |
| public boolean onScaleBegin(ScaleGestureDetector detector); | 如果要检测放大缩小手势,返回true,类似于ACTION_DOWN对事件感兴趣返回true |
OnScaleGestureListener | | |
| public void onScaleEnd(ScaleGestureDetector detector); | 放大或缩小结束,可以调用detector.getFocusX()或detector.getFocusY()来获取焦点 | OnScaleGestureListener | |
ViewDragHelper & View.OnDragListener
ViewDragHelper可以实现各种不同的滑动、拖放需求,使用参考如下过程。ViewDragHelper一般在自定义ViewGroup中使用。
首先,初始化ViewDragHelper,实现ViewDragHelper.Callback。mViewDragHelper = ViewDragHelper.create(viewgroup, callback);
然后,接管ViewGroup的事件处理,样板代码如下:
1 2 3 4 5 6 7 8 9 10 | |
接着,处理computeScroll,ViewDragHelper内部也是通过Scroller来实现平滑移动的,样板代码如下:
1 2 3 4 5 6 | |
| 方法名 | 描述 | 所属接口 | |
|---|---|---|---|
| public abstract boolean tryCaptureView(View child, int pointerId); | 哪个子View可以被拖动就返回true | ViewDragHelper.Callback | | |
| public int clampViewPositionVertical(View child, int top, int dy) { return 0; } | 限制被捕捉的View垂直方向上活动的范围 | ViewDragHelper.Callback | | |
| public int clampViewPositionHorizontal(View child, int left, int dx) { return 0; } | 限制被捕捉View水平方向上活动的范围 | ViewDragHelper.Callback | | |
| public void onViewCaptured(View capturedChild, int activePointerId) {} | View被捕捉的时候被调用 | ViewDragHelper.Callback | | |
| public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {} | 被捕捉的View位置发生变化时调用 | ViewDragHelper.Callback | | |
| public void onViewDragStateChanged(int state) {} | drag state变化时调用,STATE_IDLE,STATE_DRAGGING,STATE_SETTLING | ViewDragHelper.Callback | | |
| public void onViewReleased(View releasedChild, float xvel, float yvel) {} | View被松开时调用 | ViewDragHelper.Callback | | |
| public void onEdgeTouched(int edgeFlags, int pointerId) {} | 没有View被捕捉,父View的边缘被touch到 | ViewDragHelper.Callback | | |
| public boolean onEdgeLock(int edgeFlags) { return false; } | ViewDragHelper.Callback | | ||
| public void onEdgeDragStarted(int edgeFlags, int pointerId) {} | ViewDragHelper.Callback | | ||
| public int getOrderedChildIndex(int index) { return index; } | ViewDragHelper.Callback | | ||
| public int getViewHorizontalDragRange(View child) { return 0; } | ViewDragHelper.Callback | | ||
| public int getViewVerticalDragRange(View child) { return 0; } | ViewDragHelper.Callback | |
View.OnDragListener只有一个方法boolean onDrag(View v, DragEvent event);,当拖拽事件被分发到View时调用。DragEvent有几个状态可以在其中做处理ACTION_DRAG_STARTED,ACTION_DRAG_ENDED,ACTION_DRAG_ENTERED,ACTION_DRAG_EXITED。View开始拖动可以调用ViewCompat.startDragAndDrop(@NonNull View v, ClipData data,View.DragShadowBuilder shadowBuilder, Object localState, int flags)来开始拖动,这样会在View上方出现一个Shadow来表示被拖动的View。