View滾動
view的移動動有兩個api方法:
1、scrollTo(int targetX,int targetY)——移動到座標點(targetX,targetY)處;
2、scrollBy(int deltaX,int deltaY)——在x軸上移動deltaX距離,在y軸上移動deltaY距離。實際上是通過當前位置和deltaX、deltaY,計算出最終位置,調用scrollTo()方法;
scrollTo()源碼如下:
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
邏輯就是,記錄最終位置,調用postInvalidateOnAnimation()請求重繪,然後就會回調draw()方法,draw()方法部分代碼如下:
。。。。。。
。。。。。。
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
。。。。。。
。。。。。。
draw()方法中使用剛纔存儲的“最終位置”,計算出上下左右座標值,在“最終位置”繪製view,達到移動的效果。
但是這樣的移動是瞬時完成的,沒有“滾動”效果,所以ScrollView、ListView等肯定有另一種機制,這種機制就是Scroller類。
Scroller
Scroller的源碼非常簡單,這個類只做兩件事:計算座標值、存儲座標值。在View類中,通過Scroller實例,拿到座標值,在draw()方法中進行“移動”。
Scroller常用的方法有兩個,startScroll()和computeScrollOffset(),源碼如下:
// 計算、存儲座標值
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
// 根據設置的滑動時間、當前已經滑動的時間,計算接下來應該滑動多少距離
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
// 已經滑動的時間
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
// 通過時間,計算應該滑動的距離,計算出接下來的目標位置
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
。。。。。。
。。。。。。
。。。。。。
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
ScrollView中使用了Scroller的子類,滑動邏輯也比較複雜,就不以ScrollView源碼來分析了,下面一個自定義View來分析、解讀Scroller的用法。
public class MyView extends LinearLayout {
private Scroller mScroller;
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScroller = new Scroller(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
public MyView(Context context) {
super(context);
mScroller = new Scroller(context);
}
@Override
// 爲了更簡單地說明Scroller類的用法,設定爲觸摸View時就向上滑動100距離,滑動時間爲10000毫秒
public boolean onTouchEvent(MotionEvent ev) {
// getScrollY()是View類的方法,返回View當前的y座標
mScroller.startScroll(0, getScrollY(), 0, 100, 10000);
// 進行重繪,回調draw()方法,draw()方法中會調用computeScroll()方法
invalidate();
return super.onTouchEvent(ev);
}
@Override
public void computeScroll() {
// 調用computeScrollOffset()計算已經滑動的時間、接下來該滑動的距離等等
if (mScroller.computeScrollOffset()) {
// 執行移動操作。getCurrX()就是在computeScrollOffset()中計算出來的
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 繼續請求重繪,再次回調computeScroll()方法,循環此過程,直到滑動時間10000毫秒用完,mScroller.computeScrollOffset()返回false,跳出循環
postInvalidate();
}
}
GestureDetector
先貼上使用方法:
public class MyView extends LinearLayout {
private GestureDetector mGesture;
// 還有OnDoubleTapListener、SimpleGestureListener等監聽器
private GestureDetector.OnGestureListener mGesListener = new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
// 手指按下時觸發
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// 手指按下、沒有鬆開時觸發
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// 手指按下、快速鬆開時觸發
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 手指按下、滑動時觸發
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// 長按時觸發
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 快速滑動時觸發
return false;
}
};
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mGesture = new GestureDetector(context,mGesListener);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mGesture = new GestureDetector(context,mGesListener);
}
public MyView(Context context) {
super(context);
mGesture = new GestureDetector(context,mGesListener);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mGesture.onTouchEvent(ev);
// 一定要返回true,GesListener裏的方法纔會全部回調
return true;
}
}
在onTouchEvent()方法中,讓GestureDetector接管MotionEvent事件,GestureDetector通過分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件,判斷出用戶的滑動動作,從而回調相應的方法。
總結
1、自己在onTouchEvent()方法裏對動作、時間進行計算,也可以判斷出用戶的動作,一般簡單一點的動作就直接在onTouchEvent()方法裏自己判斷,複雜一點的動作就通過GestureDetector來判斷;
2、Scroller一般和GestureDetector結合使用,在onScroll()、onFling()方法中進行控件移動操作;
3、Scroller類中mStartX、mCurrX、mFinalX分別是移動開始時的座標、移動過程中的實時座標(移動是耗時過程)、移動目標位置的座標;
4、View類中的getScrollX()方法返回值是mScrollX,而mScrollX在scrollTo(int x ,int y)方法中直接賦值mScrollX=y,因爲scrollTo()是瞬間完成的,所以getScrollX()得到的總是View的當前座標;