根據(jù)深入了解ScrollView的學習茧妒,所以就照貓畫虎的做了一個帶阻尼回彈的ScrollView桐筏,效果還挺不錯的九昧,實現(xiàn)的原理很簡單铸鹰。但是必須要理解OverScroller和VelocityTracker類的用法展姐。
項目地址:https://github.com/cyuanyang/ScrollView.git
OverScroller的用法
這個其實是一個滾動幫助類圾笨,功能和Scroller類似擂达,但是多了一個springBack()滑出邊界回彈的功能板鬓。如果像做回彈效果非得它出馬才好實現(xiàn)俭令。
OverScroller使用起來非常的簡單,如果想讓ScrollView滾動就調(diào)用startScroll()傳入相應(yīng)參數(shù)赫蛇,會把計算的結(jié)果回調(diào)給View的computeScroll()方法棍掐,這個方法中一般這么寫作煌。然后根據(jù)得到的Y或者X的值做相應(yīng)的邏輯 粟誓,例如滾動View 鹰服。
if (mScroller.computeScrollOffset()) {
int curY = mScroller.getCurrY();
int curX = mScroller.getCurrX();
...
//根據(jù)得到的Y活著X的值做相應(yīng)的邏輯 套菜,例如滾動View
}
當手機離開后調(diào)用fling也會毀掉這個方法逗柴,在里面做相應(yīng)的邏輯處理即可戏溺。
VelocityTracker的用法
這個用法就更加單了旷祸,這個主要就是用來計算運動速率的,當手指離開后ScrollView還需要做一段慣性運動闰围,速率越大,fling的距離越遠涧狮。ScrollView滾動時者冤,手指離開后就可以調(diào)用OverScroller.fling()方法來讓其做fling滾動,而這個方法就需要velocityX和velocityY參數(shù)邢滑,正是由VelocityTracker得到的
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY)
down事件的時候調(diào)用 初始化VelocityTracker
//OnEvent事件的時候調(diào)用 初始化它
private void initVelocityTrackerIfNotExists(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
在Up事件中得到速率,得到之后一定要銷毀VelocityTracker
case MotionEvent.ACTION_UP:
... 其他代碼
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityY = (int) mVelocityTracker.getYVelocity();
... 其他代碼
fling(-velocityY);
... 其他代碼
//銷毀
recycleVelocityTracker();
break;
說到這里其實怎么實現(xiàn)ScrollView其實已經(jīng)很簡單了摇予。還是說一下關(guān)鍵的代碼
在onInterceptTouchEvent判斷是不是拖動
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
mDragging = false;
mLastY = ev.getY();
//計算點擊的時候是不是滑動
mScroller.computeScrollOffset();
mDragging = !mScroller.isFinished();
break;
case MotionEvent.ACTION_MOVE:
int deltaY = (int) Math.abs((int)ev.getY() - mLastY);
if (deltaY > mTouchSlop){
mDragging = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return mDragging;
}
在onTouchEvent處理
@Override
public boolean onTouchEvent(MotionEvent event) {
initVelocityTrackerIfNotExists(event);
int action = event.getActionMasked();
float y = event.getY();
switch (action)
{
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()){
mScroller.abortAnimation();
}
mLastY = (int) event.getY();
mActivePointerId = event.getPointerId(0);
return true;
case MotionEvent.ACTION_MOVE:
int dy = (int) (mLastY - y);
if (!mDragging && Math.abs(dy) > mTouchSlop) {
mDragging = true;
}
if (mDragging) {
//如果滑動超出邊界了 將dy按系數(shù)取值
if (getScrollY()<0 || getScrollY()>getScrollRange()){
dy /= mOverDyFactor;
}
overScrollBy(0 , dy, 0, getScrollY() , 0 , getScrollRange() , 0 , maxOverScrollY , true );
}
mLastY = y;
break;
case MotionEvent.ACTION_CANCEL:
mDragging = false;
recycleVelocityTracker();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_UP:
mDragging = false;
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityY = (int) mVelocityTracker.getYVelocity(mActivePointerId);
Log.i(TAG , "velocityY="+velocityY + " mMinimumVelocity = " +mMinimumVelocity);
if (getScrollY()>0 && getScrollY()<getScrollRange() && Math.abs(velocityY) > mMinimumVelocity) {
fling(-velocityY);
}else if (mScroller.springBack(0 , getScrollY() , 0 , 0 , 0 , getScrollRange())){
postInvalidateOnAnimation();
}
recycleVelocityTracker();
mActivePointerId = -1;
break;
}
return super.onTouchEvent(event);
}
onTouchEvent當中,當為move事件直接調(diào)用overScrollBy()蜕猫,這個方法是View的方法丹锹,可以幫我們計算滾動距離的楣黍,這個方法只是把值計算出來租漂,到底滾不滾動還是要看onOverScrolled()方法是怎么實現(xiàn)的。
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
scrollTo(scrollX , scrollY);
awakenScrollBars();
}
我這里只是滾動并喚醒滾動條這樣就實現(xiàn)了手指觸摸的滾動
手指離開后的調(diào)用用fling
public void fling(int velocityY) {
mScroller.fling(0, getScrollY() , 0, velocityY, 0, 0, 0, getScrollRange() , 0 , maxOverScrollY);
}
最終會回調(diào)computeScroll()
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
overScrollBy(0 , mScroller.getCurrY()-getScrollY() , 0 , getScrollY() , 0 , getScrollRange() , 0 , maxOverScrollY , false);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
這里我有繼續(xù)調(diào)用overScrollBy()來幫我們計算滾動,然后同理會把計算結(jié)果傳給onOverScrolled()從而完成手指離開后的fling滾動蒜胖。
遇到的問題1
在computeScroll()中不要直接調(diào)用scrollTo(),筆者頁試過台谢,因為這個滑動的時候mScroller.getCurrY()會和mScroller.getFinalY()的值有偏差朋沮,造成ScrolView在fling動作即將完成后總會跳動纠亚。
遇到的問題2
如果不重寫任何方法就調(diào)用awakenScrollBars();是看不到任何滾動條的菜枷。重寫下面幾個方法來控制滾動條啤誊。用一張圖來說明一下
- offset對應(yīng)computeVerticalScrollOffset方法,滑動的時候這里的返回值回根據(jù)offset變化
- extent對應(yīng)computeVerticalScrollExtent方法牡昆,返回看見區(qū)域的高度丢烘,大白話就是ScrollView的高度。
- computeVerticalScrollRange方法就是可以滑動的區(qū)域赢乓,這個一般需要計算得到牌芋。我們把黃色的當作LinerLayout的高度躺屁,那個可滑動的區(qū)域就等于 LinerLayout的高度-ScrollView的高度
@Override
protected int computeVerticalScrollExtent() {
return getHeight();
}
@Override
protected int computeVerticalScrollOffset() {
return Math.max(0, super.computeVerticalScrollOffset());
}
@Override
protected int computeVerticalScrollRange() {
final int count = getChildCount();
final int contentHeight = getHeight() - 0 - 0;
if (count == 0) {
return contentHeight;
}
int scrollRange = getChildAt(0).getBottom();
final int scrollY = getScrollY();
final int overScrollBottom = Math.max(0, scrollRange - contentHeight);
if (scrollY < 0) {
scrollRange -= scrollY;
} else if (scrollY > overScrollBottom) {
scrollRange += scrollY - overScrollBottom;
}
return scrollRange;
}