多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > 4AppBarLayout滑動原理

4AppBarLayout滑動原理

來源:程序員人生   發布時間:2016-10-08 16:06:12 閱讀次數:2512次

4AppBarLayout滑動原理

在CoordinatorLayout的measure和layout里,其實介紹過1點AppBarLayout,這篇將重點講授AppBarLayout的滑動原理和behavior是如何影響onTouchEvent與onInterceptTouchEvent的。

基本原理

介紹AppBarLayout的mTotalScrollRange,mDownPreScrollRange,mDownScrollRange,滑動的基本概念
mTotalScrollRange內部可以滑動的view的高度(包括上下margin)總和

官方介紹

先來看看google的介紹
AppBarLayout is a vertical LinearLayout which implements many of the features of material designs app bar concept, namely scrolling gestures.

Children should provide their desired scrolling behavior through setScrollFlags(int) and the associated layout xml attribute: app:layout_scrollFlags.

This view depends heavily on being used as a direct child within a CoordinatorLayout. If you use AppBarLayout within a different ViewGroup, most of it’s functionality will not work.

AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. The binding is done through the AppBarLayout.ScrollingViewBehavior behavior class, meaning that you should set your scrolling view’s behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the full class name is available.

簡單的整理下,AppBarLayout是1個vertical的LinearLayout,實現了很多material的概念,主要是跟滑動相干的。AppBarLayout的子view需要提供layout_scrollFlags參數。AppBarLayout和CoordinatorLayout強相干,1般作為CoordinatorLayout的子類,配套使用。
按我的理解,AppBarLayout內部有2種view,1種可滑出(屏幕),另外一種不可滑出,根據app:layout_scrollFlags辨別。1般上邊放可滑出的下邊放不可滑出的。

舉個例子以下,內有個Toolbar、TextView,Toolbar寫了app:layout_scrollFlags=”scroll”表示可滑動,Toolbar高200dp,TextView高100dp。Toolbar就是可滑出的,TextView就是不可滑出的。此時框高300(200+100),內容300,可滑動范圍200

總高度300,可滑出部份高度200,剩下100不可滑出

<android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="200dp" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll" app:popupTheme="@style/AppTheme.PopupOverlay" /> <TextView android:background="#ff0000" android:layout_width="match_parent" android:layout_height="100dp"></TextView> </android.support.design.widget.AppBarLayout>

效果以下所示

這個跟ScrollView有所不同,框的大小和內容大小1樣,這樣上滑的時候,底部必定會空出1部份(200),ScrollView的實現是通過修改scrollY,而AppBarLayout的實現是直接修改top和bottom的,其實就是把全部AppBarLayout內部的東西往上平移。

down事件

來看看上圖的事件傳遞的順序,先看down。簡單來講,這個down事件被傳遞下來,1直無人處理,然后往上傳到CoordinatorLayout被處理。但實際上CoordinatorLayout本身沒法處理事件(他只是個殼),內部實際交由AppBarLayout的behavior處理。

整體分析

首先,down事件從CoordinatorLayout傳到AppBarLayout再到TextView,沒人處理,然后回傳回來到AppBarLayout的onTouchEvent,不處理,再回傳給CoordinatorLayout的onTouchEvent,這里主要看L10 performIntercept,type為TYPE_ON_TOUCH。

@Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = false; boolean cancelSuper = false; MotionEvent cancelEvent = null; final int action = MotionEventCompat.getActionMasked(ev); //此處會分發事件給behavior if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) { // Safe since performIntercept guarantees that // mBehaviorTouchView != null if it returns true final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams(); final Behavior b = lp.getBehavior(); if (b != null) { handled = b.onTouchEvent(this, mBehaviorTouchView, ev); } } // Keep the super implementation correct if (mBehaviorTouchView == null) { handled |= super.onTouchEvent(ev); } else if (cancelSuper) { if (cancelEvent == null) { final long now = SystemClock.uptimeMillis(); cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); } super.onTouchEvent(cancelEvent); } if (!handled && action == MotionEvent.ACTION_DOWN) { } if (cancelEvent != null) { cancelEvent.recycle(); } if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { resetTouchBehaviors(); } return handled; }

再看performIntercept,type為TYPE_ON_TOUCH,首先獲得topmostChildList,這是把child依照z軸排序,最上面的排前面,CoordinatorLayout跟FrameLayout類似,越后邊的child,在z軸上越靠上。所以,這里topmostChildList就是FloatingActionButton、AppBarLayout。然后在for循環里調用behavior的onTouchEvent。此時AppBarLayout.Behavior的onTouchEvent會返回true(具體后邊分析),所以intercepted就為true,mBehaviorTouchView就會設置為AppBarLayout,然后performIntercept結束返回true。這個mBehaviorTouchView就相當于1般的ViewGroup里的mFirstTouchTarget的作用。再回頭看上邊代碼,performIntercept返回true了,那就可以進入L13,會調用mBehaviorTouchView.behavior.onTouchEvent,在這里把CoordinatorLayout的onTouchEvent,傳遞給了AppBarLayout.Behavior的onTouchEvent
而L16也會返回true,那全部CoordinatorLayout的onTouchEvent就返回true了,依照事件分發的規則,此時這個down事件被CoordinatorLayout消費了。但是實際上down事件的處理者是AppBarLayout.Behavior。他們之間通過mBehaviorTouchView連接。

private boolean performIntercept(MotionEvent ev, final int type) { boolean intercepted = false; boolean newBlock = false; MotionEvent cancelEvent = null; final int action = MotionEventCompat.getActionMasked(ev); final List<View> topmostChildList = mTempList1; getTopSortedChildren(topmostChildList); // Let topmost child views inspect first final int childCount = topmostChildList.size(); for (int i = 0; i < childCount; i++) { final View child = topmostChildList.get(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Behavior b = lp.getBehavior(); 。。。 if (!intercepted && b != null) { switch (type) { case TYPE_ON_INTERCEPT: intercepted = b.onInterceptTouchEvent(this, child, ev); break; case TYPE_ON_TOUCH: intercepted = b.onTouchEvent(this, child, ev); break; } if (intercepted) { mBehaviorTouchView = child; } } ... } topmostChildList.clear(); return intercepted; }

AppBarLayout.Behavior的onTouchEvent為什么返回true

上文說了“此時AppBarLayout.Behavior的onTouchEvent會返回true”,我們來具體分析下。來看AppBarLayout.Behavior的onTouchEvent。AppBarLayout.Behavior的onTouchEvent代碼在HeaderBehavior內,看L12只要觸摸點在AppBarLayout內,而且canDragView,那就返回true,否則返回false。在AppBarLayout內明顯是滿足的,那就看canDragView。

@Override public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { if (mTouchSlop < 0) { mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop(); } switch (MotionEventCompat.getActionMasked(ev)) { case MotionEvent.ACTION_DOWN: { final int x = (int) ev.getX(); final int y = (int) ev.getY(); if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) { mLastMotionY = y; mActivePointerId = MotionEventCompat.getPointerId(ev, 0); ensureVelocityTracker(); } else { return false; } break; } 。。。 } if (mVelocityTracker != null) { mVelocityTracker.addMovement(ev); } return true; }

下邊是AppBarLayout的canDragView,此時mLastNestedScrollingChildRef為null,所以走的是L16,返回true,那回頭看上邊的onTouchEvent也返回true。

@Override boolean canDragView(AppBarLayout view) { if (mOnDragCallback != null) { // If there is a drag callback set, it's in control return mOnDragCallback.canDrag(view); } // Else we'll use the default behaviour of seeing if it can scroll down if (mLastNestedScrollingChildRef != null) { // If we have a reference to a scrolling view, check it final View scrollingView = mLastNestedScrollingChildRef.get(); return scrollingView != null && scrollingView.isShown() && !ViewCompat.canScrollVertically(scrollingView, -1); } else { // Otherwise we assume that the scrolling view hasn't been scrolled and can drag. return true; } }

ps

可以看出在CoordinatorLayout的onTouchEvent處理down事件的進程中,調用了2次AppBarLayout.Behavior的onTouchEvent

MOVE事件

由上文可知down事件被CoordinatorLayout消費,所以move事件不會走到CoordinatorLayout的onInterceptTouchEvent,而直接進入onTouchEvent。此時mBehaviorTouchView就是AppBarLayout。看L10,直接進入,然后把move事件發給了AppBarLayout.Behavior。

@Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = false; boolean cancelSuper = false; MotionEvent cancelEvent = null; final int action = MotionEventCompat.getActionMasked(ev); //此處會分發事件給behavior if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) { // Safe since performIntercept guarantees that // mBehaviorTouchView != null if it returns true final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams(); final Behavior b = lp.getBehavior(); if (b != null) { handled = b.onTouchEvent(this, mBehaviorTouchView, ev); } } 。。。 return handled; }

AppBarLayout.Behavior處理move事件的代碼比較簡單,判斷超過mTouchSlop就調用scroll,而scroll等于調用setHeaderTopBottomOffset。這里主要關注scroll的后2個參數,minOffset和maxOffset,minOffset傳的是getMaxDragOffset(child)即AppBarlayout的-mDownScrollRange。這里就是AppBarlayout的可滑動范圍,即toolbar的高度(包括margin)的負值。minOffset和maxOffset代表的是滑動上下限制,這個很好理解,由于移動的時候改的是top和bottom,比如top范圍就是[initTop-滑動范圍,initTop],所以這里的minOffset是-mDownScrollRange,maxOffset是0.

//HeaderBehavior @Override public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { if (mTouchSlop < 0) { mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop(); } switch (MotionEventCompat.getActionMasked(ev)) { case MotionEvent.ACTION_MOVE: { final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); if (activePointerIndex == -1) { return false; } final int y = (int) MotionEventCompat.getY(ev, activePointerIndex); int dy = mLastMotionY - y; if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) { mIsBeingDragged = true; if (dy > 0) { dy -= mTouchSlop; } else { dy += mTouchSlop; } } if (mIsBeingDragged) { mLastMotionY = y; // We're being dragged so scroll the ABL scroll(parent, child, dy, getMaxDragOffset(child), 0); } break; } } if (mVelocityTracker != null) { mVelocityTracker.addMovement(ev); } return true; } final int scroll(CoordinatorLayout coordinatorLayout, V header, int dy, int minOffset, int maxOffset) { return setHeaderTopBottomOffset(coordinatorLayout, header, getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset); }

再看scroll里面,簡單調用setHeaderTopBottomOffset,重點看第3個參數getTopBottomOffsetForScrollingSibling() - dy,這個算出來的就是經過這次move行將到達的offset(不是top哦,top=offset+mLayoutTop)。getTopBottomOffsetForScrollingSibling就是獲得當前的偏移量,這個命名我不太理解。setHeaderTopBottomOffset就是給header設置1個新的offset,這個offset用1個min1個max來制約,很簡單。setHeaderTopBottomOffset可以認為就是view的offsetTopAndBottom,調劑top和bottom到達平移的效果

發現AppBarlayout對getTopBottomOffsetForScrollingSibling復寫了,加了個mOffsetDelta,但是mOffsetDelta1直是0.

@Override int getTopBottomOffsetForScrollingSibling() { return getTopAndBottomOffset() + mOffsetDelta; }

measure進程

在http://blog.csdn.net/litefish/article/details/52327502曾分析過簡單情況下CoordinatorLayout的布局進程。這里稍有變化,主要在于第3次measure RelativeLayout的時候getScrollRange不再是0
final int height = availableHeight - header.getMeasuredHeight()
+ getScrollRange(header);
就是availableHeight-AppBar.measuredheight+toolbar高度,結果就是availableHeight。
所以此時RelativeLayout的終究measure高度是1731,這個高度是成心義的,他比不可轉動的appbar多了1個toolbar的高度,這么高的1個RelativeLayout在當前屏幕是放不下的,所以RelativeLayout常常會用1個可轉動的view來替換,比如Recyclerview或NestedScrollView。

上滑可以滑到狀態欄

上滑用的是setTopAndBottomOffset,其實不會重新measure,layout,而fitSystemWindow是在measure,layout的時候發揮作用的

AppBarLayout的range

mTotalScrollRange 525
mDownPreScrollRange ⑴
mDownScrollRange 525

總結

1、ScrollView滑動的實現是通過修改scrollY,而AppBarLayout的實現是通過直接修改top和bottom的,其實就是把全部AppBarLayout內部的東西往上平移。
2、CoordinatorLayout里的mBehaviorTouchView就相當于1般的ViewGroup里的mFirstTouchTarget的作用
3、和嵌套滑動1樣始終只有1個view可以fling,不可能A fling完 B fling

參考文章

http://dk-exp.com/2016/03/30/CoordinatorLayout/
http://www.jianshu.com/p/99adaad8d55c
https://code.google.com/p/android/issues/detail?id=177729

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 国内精品一级毛片免费看 | 俄罗斯18videosex性欧美成人 | 日韩精品中文字幕视频一区 | 国产精品二区三区免费播放心 | 中文字幕在线观看国产 | 伊人网大| 日本乱码一卡二卡三卡永久 | 国产中文欧美 | 日韩视频观看 | 女人18毛片视频一级毛片容 | 精品国产欧美另类一区 | 日本一级毛片在线看 | 国内精自视频品线六区免费 | 日本性欧美 | 亚州一二区 | 欧美一级成人毛片视频 | 欧美69xxxx | 亚洲欧美在线观看视频 | 在线欧美日韩精品一区二区 | 欧美亚洲另类图片 | 亚洲国产网址 | 国产精品v片在线观看不卡 国产精品v在线播放观看 | 亚洲乱码视频在线观看 | 亚洲精品第1页 | 一区二区自拍 | 成 人 a v黄 色 | 亚洲高清在线视频 | 日本aa在线| 国产亚洲欧美久久久久 | 亚洲久久久久久久 | 97色婷婷成人综合在线观看 | 亚欧成人毛片一区二区三区四区 | 欧美操美女 | 国内精品视频 在线播放 | 欧美久久久久欧美一区 | 国产福利不卡视频在免费 | tom影院亚洲国产日本一区 | 成人久久久观看免费毛片 | 亚洲丶国产丶欧美一区二区三区 | 欧美性生交xxxxx久久久 | 精品一区二区三区中文字幕 |