在[UI]抽屜菜單DrawerLayout分析(一)和[UI]抽屜菜單DrawerLayout分析(二)中分别介紹了DrawerLayout得基本架構結構和ViewDragerHelper的作用以及手勢分發,本文一起來分析其中的Scroller的使用情況。
在ViewDragerHelper中可以發現private ScrollerCompat mScroller;說明抽屜菜單的具體滑動也是依賴于Scroller的使用,檢索一下mScroller的引用,定位到forceSettleCapturedViewAt,這個方法回調用Scroller的startScroll來計算位移,它本身适用于計算和儲存位移在特定時間的變化情況,最終的在繪制view時我可以擷取其儲存的x,y坐标值。
/**
* Settle the captured view at the given (left, top) position.
*
* @param finalLeft Target left position for the captured view
* @param finalTop Target top position for the captured view
* @param xvel Horizontal velocity
* @param yvel Vertical velocity
* @return true if animation should continue through {@link #continueSettling(boolean)} calls
*/
private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
final int startLeft = mCapturedView.getLeft();
final int startTop = mCapturedView.getTop();
final int dx = finalLeft - startLeft;
final int dy = finalTop - startTop;
if (dx == 0 && dy == 0) {
// Nothing to do. Send callbacks, be done.
mScroller.abortAnimation();
setDragState(STATE_IDLE);
returnfalse;
}
final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
mScroller.startScroll(startLeft, startTop, dx, dy, duration);
setDragState(STATE_SETTLING);
returntrue;
}
這裡用的是v4擴充包裡的ScrollerCompat用于低版本相容,它繼承自ScrollerCompatImpl,可以看到裡面主要的方法聲明:
interface ScrollerCompatImpl{
Object createScroller(Context context, Interpolator interpolator);
boolean isFinished(Object scroller);
int getCurrX(Object scroller);
int getCurrY(Object scroller);
float getCurrVelocity(Object scroller);
boolean computeScrollOffset(Object scroller);
void startScroll(Object scroller, int startX, int startY, int dx, int dy);
void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration);
void fling(Object scroller, int startX, int startY, int velX, int velY,
int minX, int maxX, int minY, int maxY);
int minX, int maxX, int minY, int maxY, int overX, int overY);
void abortAnimation(Object scroller);
void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX);
void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY);
boolean isOverScrolled(Object scroller);
int getFinalX(Object scroller);
int getFinalY(Object scroller);

從Scroller一直往上追溯,可以得到如圖的調用流程。
當滑動螢幕時,DrawerLayout中的手勢分發被觸發,先執行onInterceptTouchEvent根據傳回結果确定是否執行onTouchEvent,之後就是一些和ViewDragHelper之間的回調接口處理。
接下來追蹤一下什麼時候從Scroller中取出x,y來使用:
在View裡面有一個實作為空的computeScroll,DrawerLayout對它進行重寫,這個方法應該是在view自動重繪是會被調用,回到continueSettling:
* Move the captured settling view by the appropriate amount for the current time.
* If <code>continueSettling</code> returns true, the caller should call it again
* on the next frame to continue.
* @param deferCallbacks true if state callbacks should be deferred via posted message.
* Set this to true if you are calling this method from
* {@link android.view.View#computeScroll()} or similar methods
* invoked as part of layout or drawing.
* @return true if settle is still in progress
public boolean continueSettling(boolean deferCallbacks) {
if (mDragState == STATE_SETTLING) {
boolean keepGoing = mScroller.computeScrollOffset();
final int x = mScroller.getCurrX();
final int y = mScroller.getCurrY();
final int dx = x - mCapturedView.getLeft();
final int dy = y - mCapturedView.getTop();
if (dx != 0) {
mCapturedView.offsetLeftAndRight(dx);
}
if (dy != 0) {
mCapturedView.offsetTopAndBottom(dy);
if (dx != 0 || dy != 0) {
mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
// Close enough. The interpolator/scroller might think we're still moving
// but the user sure doesn't.
mScroller.abortAnimation();
keepGoing = mScroller.isFinished();
if (!keepGoing) {
if (deferCallbacks) {
mParentView.post(mSetIdleRunnable);
} else {
setDragState(STATE_IDLE);
}
}
return mDragState == STATE_SETTLING;
當狀态處于STATE_SETTLING時開始擷取Scroller中的x,y值,結合目前運動view的left,top位置,計算出偏移量,通過offsetLeftAndRight設定,裡面是一些具體的位置改變,挺複雜的。
* Offset this view's horizontal location by the specified amount of pixels.
* @param offset the number of pixels to offset the view by
public void offsetLeftAndRight(int offset) {
if (offset != 0) {
updateMatrix();
final boolean matrixIsIdentity = mTransformationInfo == null
|| mTransformationInfo.mMatrixIsIdentity;
if (matrixIsIdentity) {
if (mDisplayList != null) {
invalidateViewProperty(false, false);
} else {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
int minLeft;
int maxRight;
if (offset < 0) {
minLeft = mLeft + offset;
maxRight = mRight;
} else {
minLeft = mLeft;
maxRight = mRight + offset;
}
r.set(0, 0, maxRight - minLeft, mBottom - mTop);
p.invalidateChild(this, r);
}
}
} else {
invalidateViewProperty(false, false);
}
mLeft += offset;
mRight += offset;
if (mDisplayList != null) {
mDisplayList.offsetLeftAndRight(offset);
if (!matrixIsIdentity) {
invalidateViewProperty(false, true);
invalidateParentIfNeeded();
小結
至此DrawerLayout的基本工作流程分析完畢,簡單做一個總結,v4包提供了ViewDragHelper類,裡面封裝了對scroller合view的位移操作,和Callback接口,通過DrawerLayout内的onInterceptTouchEvent和onTouchEvent的重載,觸發ViewDragHelper内的相關方法,同時在DrawerLayout内實作ViewDragHelp.Callback.
作者:小文字
出處:http://www.cnblogs.com/avenwu/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.