滑動(dòng)
GestureDetector
GestureDetector手勢(shì)檢測(cè):常用用來(lái)檢測(cè)onSingleTapUp(單擊),onFling(快速滑動(dòng)),onScroll(拖動(dòng)),onLongPress(長(zhǎng)按),onDoubleTap(雙擊)
VelocityTracker
速度追蹤器,就是用來(lái)計(jì)算手指的滑動(dòng)速度
使用方法:
- ACTION_DOWN 事件到來(lái)時(shí),通過(guò) VelocityTracker.obtain()創(chuàng)建?個(gè)實(shí)例,或者使用 velocityTracker.clear() 把之前的某個(gè)實(shí)例重置
- 對(duì)于每個(gè)事件(包括 ACTION_DOWN 事件),使用velocityTracker.addMovement(event) 把事件添加進(jìn) VelocityTracker
- 在需要速度的時(shí)候(例如在 ACTION_UP 中計(jì)算是否達(dá)到 fling 速度),使用velocityTracker.computeCurrentVelocity(1000, maxVelocity) 來(lái)計(jì)算實(shí)時(shí)速度,并通過(guò)getXVelocity() / getYVelocity() 來(lái)獲取計(jì)算出的速度。
方法參數(shù)中的 1000 是指的計(jì)算的時(shí)間長(zhǎng)度,單位是 ms。例如這?填入 1000,那么getXVelocity() 返回的值就是每 1000ms (即?秒)時(shí)間內(nèi)手指移動(dòng)的像素?cái)?shù)。第?個(gè)參數(shù)是速度上限,超過(guò)這個(gè)速度時(shí),計(jì)算出的速度會(huì)回落到這個(gè)速度。例如這里填了 200,而實(shí)時(shí)速度是 300,那么實(shí)際的返回速度將是 200 ,maxVelocity 可以通過(guò) viewConfiguration.getScaledMaximumFlingVelocity()來(lái)獲取。
VelocityTracker velocityTracker = VelocityTracker.obtain();
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
velocityTracker.clear();
}
velocityTracker.addMovement(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
velocityTracker.computeCurrentVelocity(1000, maxVelocity);
break;
}
return true;
}
scrollTo,scrollBy,computeScroll
scrollTo(x, y)移動(dòng)的是絕對(duì)值;scrollBy(deltaX, deltaY)移動(dòng)的是相對(duì)值,內(nèi)部也是調(diào)用scrollTo方法。
scrollTo() 是瞬時(shí)方法,不會(huì)自動(dòng)使用動(dòng)畫(huà)。如果要用動(dòng)畫(huà),需要配合 View.computeScroll()方法。computeScroll() 在 View 重繪時(shí)被自動(dòng)調(diào)用
使用OverScroller實(shí)現(xiàn)緩慢滑動(dòng)
// onTouchEvent() 中:
overScroller.startScroll(startX, startY, dx, dy);
postInvalidateOnAnimation();
......
// onTouchEvent() 外:
@Override
public void computeScroll() {
if (overScroller.computeScrollOffset()) { // 計(jì)算實(shí)時(shí)位置
scrollTo(overScroller.getCurrX(),
overScroller.getCurrY()); // 更新界?
postInvalidateOnAnimation(); // 下?幀繼續(xù)
}
}
使用Scroller實(shí)現(xiàn)緩慢滑動(dòng)
實(shí)現(xiàn)原理:startScroll記錄下相關(guān)參數(shù),invalidate導(dǎo)致view重繪,view的draw方法中又調(diào)用computeScroll,而computeScroll又會(huì)向Scroller獲取當(dāng)前scrollX和scrollY,然后通過(guò)scrollTo去實(shí)現(xiàn)滑動(dòng)
private void smoothScrollTo(int destX, int destY){
int scrollX = getScrollX();
int deltaX = destX - scrollX;
int scrollY = getScrollY();
int deltaY = destY - scrollY;
//1000ms內(nèi)滑向destX,效果就是慢慢滑動(dòng)
mScroller.startScroll(scrollX,scrollY,deltaX,deltaY,1000);
invalidate();
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
super.computeScroll();
}
簡(jiǎn)單的自定義ViewPager
通過(guò)簡(jiǎn)單的自定義ViewPager來(lái)使用上面的VelocityTracker和computeScroll等使用
public class MyViewPager extends ViewGroup {
float downX;
float downY;
float downScrollX;
boolean scrolling;
float minVelocity;
float maxVelocity;
OverScroller overScroller;
ViewConfiguration viewConfiguration;
VelocityTracker velocityTracker = VelocityTracker.obtain();
public MyViewPager (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();
}
}
}
拖拽
OnDragListener
-
通過(guò) startDrag() 來(lái)啟動(dòng)拖拽
startDrag最后會(huì)調(diào)用startDragAndDrop,
startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,Object myLocalState, int flags)
內(nèi)部有四個(gè)參數(shù):
- ClipData data
其實(shí)就是一個(gè)封裝數(shù)據(jù)的對(duì)象,通過(guò)拖放操作傳遞給接受者。該對(duì)象可以存放一個(gè)Item的集合,Item可以存放如下數(shù)據(jù):
public static class Item {
final CharSequence mText;
final String mHtmlText;
final Intent mIntent;
Uri mUri;
}
- DragShadowBuilder shadowBuilder
用于創(chuàng)建拖拽view是的陰影,也就是跟隨手指移動(dòng)的視圖,通常直接使用默認(rèn)即可生成與一個(gè)原始view相同,帶有透明度的陰影 - Object myLocalState
當(dāng)你的拖拽行為是在同一個(gè)Activity中進(jìn)行時(shí)可以傳遞一個(gè)任意對(duì)象,在監(jiān)聽(tīng)中可以通過(guò){@link android.view.DragEvent#getLocalState()}獲得。如果是跨Activity拖拽中無(wú)法訪問(wèn)此數(shù)據(jù),getLocalState()將返回null。 - int flags
控制拖放操作的標(biāo)志。因?yàn)闆](méi)有標(biāo)志可以設(shè)置為0,flag標(biāo)志拖動(dòng)是否可以跨越窗口以及一些訪問(wèn)權(quán)限(需要API24+)
child.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
draggedView = v;
v.startDrag(null, new DragShadowBuilder(v), v, 0);
return false;
}
});
child.setOnDragListener(dragListener);
-
用setOnDragListener() 來(lái)監(jiān)聽(tīng)
目標(biāo)View:不是被拖拽的View,是要拖拽去哪個(gè)區(qū)域,這個(gè)區(qū)域就目標(biāo)View,它要設(shè)置OnDragListener監(jiān)聽(tīng)。
OnDragListener 內(nèi)部只有?個(gè)方法: onDrag()。View中onDragEvent() 方法也會(huì)收到拖拽回調(diào)(界?中的每個(gè) View 都會(huì)收到)
view.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
//v 永遠(yuǎn)是設(shè)置該監(jiān)聽(tīng)的view,這里即fl_blue
String simpleName = v.getClass().getSimpleName();
Log.w(BLUE, "view name:" + simpleName);
//獲取事件
int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
Log.i(BLUE, "開(kāi)始拖拽");
break;
case DragEvent.ACTION_DRAG_ENDED:
Log.i(BLUE, "結(jié)束拖拽");
break;
case DragEvent.ACTION_DRAG_ENTERED:
Log.i(BLUE, "拖拽的view進(jìn)入監(jiān)聽(tīng)的view時(shí)");
break;
case DragEvent.ACTION_DRAG_EXITED:
Log.i(BLUE, "拖拽的view離開(kāi)監(jiān)聽(tīng)的view時(shí)");
break;
case DragEvent.ACTION_DRAG_LOCATION:
float x = event.getX();
float y = event.getY();
long l = SystemClock.currentThreadTimeMillis();
Log.i(BLUE, "拖拽的view在監(jiān)聽(tīng)view中的位置:x =" + x + ",y=" + y);
break;
case DragEvent.ACTION_DROP:
Log.i(BLUE, "釋放拖拽的view");
break;
}
//是否響應(yīng)拖拽事件,true響應(yīng),返回false只能接受到ACTION_DRAG_STARTED事件,后續(xù)事件不會(huì)收到
return true;
}
});
ViewDragHelper
-
需要?jiǎng)?chuàng)建?個(gè) ViewDragHelper 和 Callback()
ViewDragHelper create(ViewGroup forParent, Callback cb);一個(gè)靜態(tài)的創(chuàng)建方法,
參數(shù)1:出入的是相應(yīng)的ViewGroup
參數(shù)2:是一個(gè)回調(diào)Callback,在后面介紹包括其中的方法
ViewDragHelper dragHelper;
ViewDragHelper.Callback dragListener = new DragListener();
public DragHelperLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
dragHelper = ViewDragHelper.create(this, dragListener);
viewConfiguration = ViewConfiguration.get(context);
}
- 需要寫(xiě)在 ViewGroup 里面,重寫(xiě) onIntercept() 和 onTouchevent()
- shouldInterceptTouchEvent(MotionEvent ev) 處理事件分發(fā)的(主要是將ViewGroup的事件攔截onInterceptTouchEvent,委托給ViewDragHelper進(jìn)行處理)
- processTouchEvent(MotionEvent event) 處理相應(yīng)TouchEvent的方法,這里要注意一個(gè)問(wèn)題,處理相應(yīng)的TouchEvent的時(shí)候要將結(jié)果返回為true,消費(fèi)本次事件!否則將無(wú)法使用ViewDragHelper處理相應(yīng)的拖拽事件!
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
- ViewDragHelper.Callback的API(也就是創(chuàng)建ViewDragHelper傳入的回調(diào)方法)
- tryCaptureView(View child, int pointerId) 這是一個(gè)抽象類(lèi),必須去實(shí)現(xiàn),也只有在這個(gè)方法返回true的時(shí)候下面的方法才會(huì)生效;相當(dāng)于事件的開(kāi)始
參數(shù)1:捕獲的View(也就是你拖動(dòng)的這個(gè)View)
參數(shù)2:這個(gè)參數(shù)我也不知道什么意思API中寫(xiě)的一個(gè)什么指針,這里沒(méi)有到也沒(méi)有注意 - onViewDragStateChanged(int state) 當(dāng)狀態(tài)改變的時(shí)候回調(diào),返回相應(yīng)的狀態(tài)(這里有三種狀態(tài))
STATE_IDLE 閑置狀態(tài)
STATE_DRAGGING 正在拖動(dòng)
STATE_SETTLING 放置到某個(gè)位置 - onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 當(dāng)你拖動(dòng)的View位置發(fā)生改變的時(shí)候回調(diào)
參數(shù)1:你當(dāng)前拖動(dòng)的這個(gè)View
參數(shù)2:距離左邊的距離
參數(shù)3:距離右邊的距離
參數(shù)4:x軸的變化量
參數(shù)5:y軸的變化量 - onViewCaptured(View capturedChild, int activePointerId)捕獲View的時(shí)候調(diào)用的方法
參數(shù)1:捕獲的View(也就是你拖動(dòng)的這個(gè)View)
參數(shù)2:這個(gè)參數(shù)我也不知道什么意思API中寫(xiě)的一個(gè)什么指針,這里沒(méi)有到也沒(méi)有注意 - onViewReleased(View releasedChild, float xvel, float yvel) 當(dāng)View停止拖拽的時(shí)候調(diào)用的方法,一般在這個(gè)方法中重置一些參數(shù),相當(dāng)于事件的結(jié)束
參數(shù)1:你拖拽的這個(gè)View
參數(shù)2:x軸的速率
參數(shù)3:y軸的速率 - clampViewPositionVertical(View child, int top, int dy) 豎直拖拽的時(shí)候回調(diào)的方法
參數(shù)1:拖拽的View
參數(shù)2:距離頂部的距離
參數(shù)3:變化量
7.clampViewPositionHorizontal(View child, int left, int dx) 水平拖拽的時(shí)候回調(diào)的方法
參數(shù)1:拖拽的View
參數(shù)2:距離左邊的距離
參數(shù)3:變化量
public class DragHelperLayout extends FrameLayout {
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
private class DragCallback extends ViewDragHelper.Callback {
float capturedOldLeft;
float capturedOldTop;
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
return true;
}
@Override
public void onViewDragStateChanged(int state) {
if (state == ViewDragHelper.STATE_IDLE) {
View capturedView = dragHelper.getCapturedView();
//。。。。
}
}
@Override
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
return top;
}
@Override
public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
capturedOldLeft = capturedChild.getLeft();
capturedOldTop = capturedChild.getTop();
}
@Override
public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
}
@Override
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
//放回到原來(lái)的位置m
dragHelper.settleCapturedViewAt((int) capturedOldLeft, (int) capturedOldTop);
postInvalidateOnAnimation();
}
}
}
如何成為自定義高手(一)繪制
如何成為自定義高手(二)動(dòng)畫(huà)
如何成為自定義高手(三)布局
如何成為自定義高手(四)觸摸反饋,事件分發(fā)機(jī)制
如何成為自定義高手(五)多點(diǎn)觸摸
如何成為自定義高手(六)滑動(dòng)和拖拽
如何成為自定義高手(七)滑動(dòng)沖突