效果圖
這次,我們來實現第二個模塊,即view 的滾動和使用 Scroller 平滑滾動,在這篇文章中,您將看到:
- View 的事件傳遞簡析
- ScrollerBy 和 ScrollerTo 的區別,以及使用 Scroller 實現平滑過渡
前面中,我們已經通過 FlowLayout 實現測量和佈局,這次新建一個類 ScrollFlowLayout 是專門實現滾動邏輯
一、View 的事件傳遞
當點擊一個控件的時候,它的向下傳遞過程大致如下: activity --> window – > viewGroud --> view 。當然第一次走的是 disPatchTouchEvent 方法;通過源碼知道,如果我們對 onInterceptTouchEvent 返回true,則父控件接管當前觸摸事件,不再往下傳遞,而是回調自己的 onTouchEvent 方法。
View 的事件傳遞是基操,大家自行查閱啦
那麼,我們就可以在 onInterceptTouchEvent 去這樣寫:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = ev.getX();
//拿到上次的down座標
mMoveX = ev.getX();
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mLastX;
if (Math.abs(dx) >= mTouchSlop) {
//由父控件接管觸摸事件
return true;
}
mLastX = ev.getX();
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
二、View 的滾動和 Scroller
在 onTouchEvent 中,拿到了移動的偏移量,那怎麼實現 View 自身的移動呢?
沒錯,就是使用 ScrollerBy 和 ScrollerTo,它們只改變 View 的內容而不會改變 View 的座標 ,這正是我們需要的,需要注意的是,向左滑爲正,向右爲負。
- ScrollerTo(int x,int y) 絕對座標移動,以原點爲參考點
- ScrollerBy(int x,int y) 相對座標移動,以上一次座標爲參考點
那麼,onTouchEvent 就可以這樣寫了:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
//scroller 向右爲負,向左爲正
int dx = (int) (mMoveX - event.getX());
scrollBy(dx, 0);
mMoveX = event.getX();
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.onTouchEvent(event);
}
效果如下:
嗯,還差邊界判斷,首先,拿到右邊邊界:
mRightBound = child.getRight() + getPaddingRight();
接着,在move中去判斷邊界:
case MotionEvent.ACTION_MOVE:
//scroller 向右爲負,向左爲正
int dx = (int) (mMoveX - event.getX());
/**
* 判斷左右邊界
*/
int scrollX = getScrollX();
if (scrollX + dx <= 0) {
scrollTo(0, 0);
return true;
}
if (scrollX + dx >= mRightBound - mScreenWidth) {
scrollTo(mRightBound - mScreenWidth, 0);
return true;
}
scrollBy(dx, 0);
mMoveX = event.getX();
break;
接着,再運行一下:
邊界加上了,但是總感覺有點卡頓,不夠流暢,我們接着優化一下:
三、使用 Scroller 優化滑動卡頓
上面看到,當通過手指按住滑動之後,應該要有個滾動速度;從 Scroller 的 API 中,我們發現可以使用 Scroller 中的 Fling 方法;
先初始化 Scroller
mScroller = new Scroller(context);
而這個橫向的滾動速度怎麼來呢?
可以有兩種,第一個是通過手勢 Gestrue 的 Fling 拿到,還有一種則是 VelocityTracker ;這裏,我們直接用 VelocityTracker 來拿到橫向速度;
首先 VelocityTracker 通過 obtain 來拿到實例,並通過 addMoveMent 拿到 MotionEvent,這樣才能正確的速度:
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
在 up 的時候,拿到橫向速度:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mVelocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
int velocityX = (int) mVelocityTracker.getXVelocity();
if (Math.abs(velocityX) >= mMinimumVelocity) {
mCurScrollX = getScrollX();
mScroller.fling(mCurScrollX, 0, velocityX, 0, 0, getWidth(), 0, 0);
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
break;
其中 mVelocityTracker.getXVelocity() 表示的是 1s 內偏移的像素點;接着再把它賦值給 mScroller.fling();
當調用了 fling 之後,Scroller 就會調用 computeScroll 方法了。在 computeScroll 方法,拿到當前偏移的像素,與上次的對比,即可讓它平滑滾動,如下:
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()){
int dx = mCurScrollX - mScroller.getCurrX();
// 超出右邊界,進行修正
if (getScrollX() + dx >= mRightBound - mScreenWidth) {
dx = mRightBound - mScreenWidth - getScrollX();
}
// 超出左邊界,進行修正
if (getScrollX() + dx <= 0) {
dx = -getScrollX();
}
scrollBy(dx,0);
postInvalidate();
}
}
最終效果如下 (gif 看起來一般般,建議運行看效果):