欣賞
線索
- 主要的成員標記
- 核心功能與方法
1. 成員
mScroller: 用來計算內(nèi)部內(nèi)容滾動的輔助類OverScroller雳殊,但是主要的滾動并沒有使用到他哦
mEdgeGlowTop: 當滾動到最頂部的時候的上邊界陰影效果
mEdgeGlowBottom: 當滾動到最底部時候的下邊界陰影效果
mChildToScrollTo: 記錄擁有焦點的view, 在layout的時候會將位置滾動到他的地方
mIsBeingDragged: true表示scrollview在滾動;注意在嵌套滾動的時候,不屬于scrollView的滾動窗轩,他為false;
mFillViewport: true, ScrollView模式不是MeasureSpec.UNSPECIFIED夯秃, 會用scrollView的高度來當自己的高度,重新在給子view測量一遍;一般他都不會是這個模式, 什么情況下ScrollView是UNSPECIFIED模式呢痢艺,從scollView的測量可以看出仓洼,他給子view測量的規(guī)格就是UNSPECIFIED,因此scrollView嵌套scrollView的時候堤舒, 第二個scrollView如果設置了mFillViewport色建,依然是不會重新測量他的子view的哦。
mActivePointerId: 當前激活的手指; 這個是scrollView處理多手指滑動的原則舌缤。他的基本規(guī)則是這樣的箕戳,當?shù)诙€手指落下滑動的時候某残,那么前面第一個手指滑動就失效了,因為將激活的手指給到了第二個手指陵吸,當?shù)诙€手指抬起的時候玻墅,又會把激活的點給到第一個手指。
其他的就不一一列出來了......
2. 功能與方法
普通的LinearLayout, RelativeLayout, FrameLayout他們的頁面內(nèi)容都是有限的壮虫,限于一屏之內(nèi)澳厢,而ScrollView卻不是哦,這就是他主要功能旨指。
- ScrollView的構造與初始化:
public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initScrollView();
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
//讀取是否設置了viewport屬性赏酥,讓子view填充scrollView高度
setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
a.recycle();
}
private void initScrollView() {
mScroller = new OverScroller(getContext());
setFocusable(true);
//優(yōu)先讓子view獲得焦點
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
//滾動識別距離,
mTouchSlop = configuration.getScaledTouchSlop();
//fling滑行的最小速度
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
//fling滑行的最大速度
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
}
總結一下谆构,其實構造和初始化比較簡單裸扶,就是讀取scrollView的一個_fillViewport屬性,如果他為true, 他的子child一般會經(jīng)過兩側(cè)測量搬素,最終的高度和scollView是一樣的; 然后就是讀取系統(tǒng)的默認配置呵晨,比如移動多少算是滾動,多大速度可以在松手時候認為是要去滑翔熬尺。
-
ScrollView只能有一個child摸屠,否則會拋異常, 看這里:
public void addView(View child) { if (getChildCount() > 0) { throw new IllegalStateException("ScrollView can host only one direct child"); } super.addView(child); }
-
setFillViewport,動態(tài)設定ScrollView的填充屬性
public void setFillViewport(boolean fillViewport) { if (fillViewport != mFillViewport) { mFillViewport = fillViewport; //因為要改變子child的高度粱哼,自然要重新測量季二,布局一次啦。 requestLayout(); } }
-
ScrollView的測量入口, onMeasure:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //主要的測量還是調(diào)用父類FrameLayout的測量揭措, super.onMeasure(widthMeasureSpec, heightMeasureSpec); //mFillViewport為true, 才需要重新測量一次 if (!mFillViewport) { return; } final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //scrollView本身的模式一般不會是這個胯舷,除非是scrollView的父容器也是scrollView if (heightMode == MeasureSpec.UNSPECIFIED) { return; } if (getChildCount() > 0) { final View child = getChildAt(0); int height = getMeasuredHeight(); //如果child的高度小于scrollView的高度啦, if (child.getMeasuredHeight() < height) { final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); height -= mPaddingTop; height -= mPaddingBottom; int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); //重新測量子child child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }
- 總結一下, 可以看出ScrollView的本身測量主要是調(diào)用父類FrameLayout的測量方法绊含,簡單說說FrameLayout的測量桑嘶,他其實是先遍歷所有的子view, 找到一個高度最大的view作為預備的高度,然后通過FrameLayout本身的測量規(guī)格(包含規(guī)格和父容器的剩余高度)躬充,來決定他的高度逃顶。比如FrameLayout的onMeasure傳遞過來的規(guī)格是EXACTLY,那么最終高度就是measureSpec解析出來的高度充甚,比如傳遞過來的是AT_MOST以政,并且我們的預備高度小于measureSpec解析的高度,那么最終高度就是我們前面計算的預備高度啦, 也就是我們的scrollView的最終高度伴找!
-
ScrollView的測量之二妙蔗,子Child的測量measureChildWithMargins:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //獲取child的寬度規(guī)格, final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); //構建高度的測量規(guī)格疆瑰,可以看到給到size只是一個上下margin, mode是無限制的眉反。根據(jù)標準的測量原則 //可以知道昙啄,子child想要多高就給多高啦。 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); //測量寸五。 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
- 總結一下梳凛,在測量scrollview的時候,其實是要在FrameLayout中先測量他的子view的寬高的梳杏,然后根據(jù)子view的高度來設定Scrollview的高度韧拒。scrollview重寫的measureChildWithMargins方法,他在測量子child時候給child的規(guī)格是MeasureSpec.UNSPECIFIED十性, 這個是很少見的叛溢,但原來也是很有用的,他可以讓子child得到他想要的任意高度劲适,ScrollView不限制他的子view高度楷掉, 在滾動容器中正好是需要的啦!
-
ScrollView的布局
protected void onLayout(boolean changed, int l, int t, int r, int b) { //依然還是調(diào)用frameLayout去布局他的child啦; super.onLayout(changed, l, t, r, b); // mIsLayoutDirty = false; // 如果記錄了焦點的child,那么就滾到到該child的位置霞势,注意可能不是ScrollView的直接子child哦 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) { scrollToChild(mChildToScrollTo); } mChildToScrollTo = null; if (!isLaidOut()) { if (mSavedState != null) { mScrollY = mSavedState.scrollPosition; mSavedState = null; } // mScrollY default value is "0" final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0; //計算可以滾動的距離 final int scrollRange = Math.max(0, childHeight - (b - t - mPaddingBottom - mPaddingTop)); // 范圍糾正 if (mScrollY > scrollRange) { mScrollY = scrollRange; } else if (mScrollY < 0) { mScrollY = 0; } } // 重新布局的時候不會丟失原來的滾動位置 scrollTo(mScrollX, mScrollY); }
- 總結一下烹植,可以看出,布局的主要手段還是借助了FrameLayout愕贡,這個其實也很簡單草雕,就一個child. ScrollView不本身做了焦點child的位置滾動,以及還原以前的scroll位置.
-
ScrollView的繪制固以,draw方法
public void draw(Canvas canvas) { //調(diào)用fm.draw繪制自己的內(nèi)容 super.draw(canvas); if (mEdgeGlowTop != null) { final int scrollY = mScrollY; if (!mEdgeGlowTop.isFinished()) {//拖到了邊界處沒有松手 final int restoreCount = canvas.save(); final int width = getWidth() - mPaddingLeft - mPaddingRight; canvas.translate(mPaddingLeft, Math.min(0, scrollY)); mEdgeGlowTop.setSize(width, getHeight()); //繪制他的上邊界 if (mEdgeGlowTop.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } if (!mEdgeGlowBottom.isFinished()) {//拖到了邊界處沒有松手 final int restoreCount = canvas.save(); final int width = getWidth() - mPaddingLeft - mPaddingRight; final int height = getHeight(); canvas.translate(-width + mPaddingLeft, Math.max(getScrollRange(), scrollY) + height); canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); //繪制他的下邊界 if (mEdgeGlowBottom.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } } }
- 總結一下墩虹,除了借助Fm去繪制child和本身外,這里主要的內(nèi)容就是當滾動了邊界處的時候憨琳,繪制上邊界的陰影和下邊界的陰影效果诫钓。
-
ScollView滾動之攔截,onInterceptTouchEvent, 如果攔截了觸摸栽渴,那么子view就不能順利地使用觸摸事件啦,比如ScrollView下面的Recyclerview等稳懒,看看代碼吧:
public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); //如果之前攔截了闲擦,檔次又是move,那么就攔下來,不去后面的計算啦 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } //沒得去滑了场梆,而且還要沒滑動過不攔截墅冷,奇怪,為什么要getScrollY==或油? if (getScrollY() == 0 && !canScrollVertically(1)) { return false; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); //如果move達到了滾動寞忿,并且不是嵌套滾動,立即攔截顶岸。 if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); mNestedYOffset = 0; if (mScrollStrictSpan == null) { mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); } final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } break; } case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY(); if (!inChild((int) ev.getX(), (int) y)) { //不攔截 mIsBeingDragged = false; recycleVelocityTracker(); break; } ........ //如果在fling狀態(tài)腔彰,立即攔截叫编。 mIsBeingDragged = !mScroller.isFinished(); if (mIsBeingDragged && mScrollStrictSpan == null) { mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); } startNestedScroll(SCROLL_AXIS_VERTICAL); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: //取消攔截 mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { postInvalidateOnAnimation(); } stopNestedScroll(); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } //攔截與否,主要還是i看這個標記呢霹抛, return mIsBeingDragged; }
- 總結一下搓逾,ScrollView有幾個地方會去攔截:當在down事件時候,如果當前的ScrollView在滑行狀態(tài)杯拐,會攔截下來不給子view使用霞篡,這個時候down事件都并不會下發(fā)下去。當move的時候端逼,如果不是嵌套滾動朗兵,一般ScrollView也將它攔截下來自己使用, 還有就是之前攔截了,這次又是move事件也會立刻攔截下來顶滩,整體上ScrollView就是這么處理攔截的啦余掖,不過攔截了也不是說子view就不能用,畢竟子child可以用requestDisallowInterceptTouchEvent來禁止他的攔截生效诲祸。
-
ScollView滾動之觸摸浊吏,這個算是滾動的最重要的地方之一啦, onTouchEvent, 根據(jù)手指的事件一個個地看吧:
case MotionEvent.ACTION_DOWN: { //沒有child,不消耗啦,沒搞頭啊 if (getChildCount() == 0) { return false; } //如果當前在滾動狀態(tài)救氯,說明之前自己消耗的找田,這次也要自己來,請求父容器不要消耗. if ((mIsBeingDragged = !mScroller.isFinished())) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } //這里有意思着憨,當上一次的滾動還在fling狀態(tài)的時候墩衙,手指一放下,那么就會停止滑動啦甲抖,不信試試 if (!mScroller.isFinished()) { //停止滑動漆改。 mScroller.abortAnimation(); if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } } // Remember where the motion event started mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0); //觸發(fā)嵌套滑動,ScrollView的嵌套滑動太雞肋准谚,這里覺得沒有分析的必要 startNestedScroll(SCROLL_AXIS_VERTICAL); break; } ........ return true;
case MotionEvent.ACTION_MOVE: //找到當前激活的手指挫剑,獲取他的手勢事件 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); break; } final int y = (int) ev.getY(activePointerIndex); //向上滑動,大于0 int deltaY = mLastMotionY - y; //嵌套滾動相關的柱衔,在ScrollView中的實現(xiàn)是很雞肋的,沒什么好說的 if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } //如果當前沒有被認定為scrollview的滾動樊破,會根據(jù)情況讓scollView去滾動,這也是scrollView實現(xiàn) //內(nèi)容滾動的核心地方唆铐。 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); //這里的意思是說哲戚,當move傳遞到了scrollView了,并且達到了滾動距離艾岂,就認為該讓scrollview去 //消耗事件了,請求不要攔截后續(xù)的事件就交給scrollview處理了顺少。但是這種實現(xiàn)有點詭異,因為如果ScollView //的父容器攔截了move,根本就不會走到這里來了脆炎,這里的請求不要攔截也不會生效梅猿,只有當父容器在move中攔截的條件還沒有生效,在這里設定才會起到作用腕窥。 if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } mIsBeingDragged = true; if (deltaY > 0) { deltaY -= mTouchSlop; } else { deltaY += mTouchSlop; } } if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int oldY = mScrollY; //子child.height - scrollview的height,得出可以滾動的范圍 final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); //如果是OVER_SCROLL_IF_CONTENT_SCROLLS粒没,那么必須child的內(nèi)容高度大于scollview的高度才可 //認為繪制過度滾動視覺陰影 boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); //overScollBy進行了主要的內(nèi)容哦你那個滾動啦; if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true) && !hasNestedScrollingParent()) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } //計算scrolledDeltaY滾動過的距離 final int scrolledDeltaY = mScrollY - oldY; //計算剩下的距離 final int unconsumedY = deltaY - scrolledDeltaY; //雞肋的嵌套滾動,讓剩下的滾動發(fā)給他的父容器...... if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { mLastMotionY -= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) {//支持過度繪制 final int pulledToY = oldY + deltaY; if (pulledToY < 0) {//如果累計的滾動已經(jīng)小于0了簇爆,說明滾動到了上邊緣了癞松,那么就 //開始計算我們的上邊緣效果啦,這個改變可以根據(jù)你的手勢的特點改變陰影的效果入蛆。 mEdgeGlowTop.onPull((float) deltaY / getHeight(), ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (pulledToY > range) {//如果大于我們剩下可以滾動的范圍响蓉,說明已經(jīng)拉到了 //下邊緣,計算下邊緣的陰影內(nèi)容哨毁,在繪制的時候可以統(tǒng)一繪制枫甲。 mEdgeGlowBottom.onPull((float) deltaY / getHeight(), 1.f - ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { postInvalidateOnAnimation(); } } } break;
- 總結一下,在onTouchEvent中的move中扼褪,處理scrollView的內(nèi)容滾動的關鍵地方想幻,同時還根據(jù)滑動的位置,去計算將要繪制的上下陰影的圖形效果呢话浇。這里要有一個地方注意的是脏毯,在scrollView的move中會根據(jù)實際滑動的距離來請求他的父容器不要攔截,但是這里我認為這樣的設計有太多的風險了幔崖,因為他要求父容器的攔截計算要很精確食店,如果在scrollview的請求不要攔截之前已經(jīng)預先攔截了,那么后續(xù)的move根本不會走到這個請求的邏輯赏寇,就涼涼了吉嫩。所以嵌套scrollview寫攔截邏輯,是要小心一點的.......
- 對了嗅定, ScrollView中關鍵滾動內(nèi)容的地方在overScrollBy這里自娩,還沒有說明呢:
----view.java protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { //獲取scrollView的模式:三種模式啦,if_content, none, alaways; final int overScrollMode = mOverScrollMode; ....... //computeVerticalScrollRange, 高度呢渠退,說白了child的實際高度; //computeVerticalScrollExtent---就是scrollView的高度 final boolean canScrollVertical = computeVerticalScrollRange() > computeVerticalScrollExtent(); final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS || (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical); ....... //新的滾動距離忙迁,等于老的+這次move移動的距離 int newScrollY = scrollY + deltaY; if (!overScrollVertical) { maxOverScrollY = 0; } // maxOverScrollY一般都是0,這里沒看頭,掠過 final int left = -maxOverScrollX; final int right = maxOverScrollX + scrollRangeX; final int top = -maxOverScrollY; final int bottom = maxOverScrollY + scrollRangeY; ......... //糾正智什,讓我們的新的滾動距離不要超過可滾動的界限 boolean clampedY = false; if (newScrollY > bottom) { newScrollY = bottom; clampedY = true; } else if (newScrollY < top) { newScrollY = top; clampedY = true; } //這里計算新的滾動距離后就調(diào)用scrollView的滾動方法动漾,他重寫了這個方法, onOverScrolled(newScrollX, newScrollY, clampedX, clampedY); //表示滾動到了邊界啦...... return clampedX || clampedY; }
- 總結一下, 這個方法之view中的方法丁屎,主要思路是根據(jù)scollView當次move的距離荠锭,進行一次距離糾正讓他不會劃出我們子child提供的最大范圍, 沒什么東西了.
- 接著再看上面的onOverScrolled方法:
protected void onOverScrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
//.
if (!mScroller.isFinished()) {//還沒滾動完
final int oldX = mScrollX;
final int oldY = mScrollY;
mScrollX = scrollX;
mScrollY = scrollY;
invalidateParentIfNeeded();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (clampedY) {
mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
}
} else {
//進行內(nèi)容的滾動哦!
super.scrollTo(scrollX, scrollY);
}
//喚醒scrollbar
awakenScrollBars();
}
- 從上面可以看到晨川,如果當次沒滾動完证九,是不會滾動的删豺,要等到下次再滾動哦.如果可以滾動就調(diào)用View.scrollTo去滑動內(nèi)容。
- 好了愧怜,繼續(xù)把onTouchEvent中的事件閱讀完:
case MotionEvent.ACTION_UP://主手指抬起
if (mIsBeingDragged) {
//計算速度
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
//判斷速度能不能達到滑行的標準
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
//滑行
flingWithNestedDispatch(-initialVelocity);
} else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
getScrollRange())) {
postInvalidateOnAnimation();
}
//清除手指活動目標
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
//清除手指活動目標
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {//副手指手指放下
final int index = ev.getActionIndex();
mLastMotionY = (int) ev.getY(index);
//將副手指替換成主手指
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP://副手指抬起
//將原來的第一個手指替換成現(xiàn)在的主手指呀页,或者抬起的是第一個手指拥坛,那么什么都不用做了丸氛。
onSecondaryPointerUp(ev);
mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
break;
- 總結一下谍咆,當主手指抬起來的時候,判斷一下要不要去滑行一下螃成。除此之外,這里有多手指操作的判斷望忆,他的邏輯是稿壁,當?shù)诙€手指放下的時候,會將前面記錄主手指的第一個手指換成第二個,然后后面的滑動第一個手指就沒有效果,滑動第二個才有效果,因為計算只是跟蹤主手指。現(xiàn)在第一個手指是副手指,第二個手指是主手指了。還有抬起的時候,如果抬起的是第二個手指即主手指抬起, 那么還要將前面的第一個手指又還原成主手指,后面滑動又跟蹤他了,如果抬起的是原來的第一個手指即非主手指嘉赎,那么就不做什么了....這種多指操作公条,也是android系統(tǒng)的標準行為拇囊。
- 看看onSecondaryPointerUp的實現(xiàn)吧....
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
//如果當前抬起的是主手指,就要進行主手指的重新定位靶橱,如果不是那么就啥都不做.....
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
3. 結束
- 好了寥袭, 原來ScollView的滾動還是利用View標準的scrollTo去滾動的呀奢米,對ScrollView的理解就到這里了吧, 說實話感覺代碼有點惡心纠永,代碼不算太多,但是功能不是特別明確谒拴,對應用開發(fā)可造成的隱性的問題不少尝江。