日本搞逼视频_黄色一级片免费在线观看_色99久久_性明星video另类hd_欧美77_综合在线视频

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

4AppBarLayout滑動原理

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

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

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 国产免费无遮挡 | 欧美日韩在线影院 | 国产免费一区 | 91精品国产综合久久小美女 | 午夜精品久久久久久99热软件 | 国产青草视频 | 国产经典一区二区三区 | www.一区 | 超碰三级电影 | av高清| 亚洲欧美日韩精品久久亚洲区 | 久久久青草婷婷精品综合日韩 | 国产黄a三级三级看三级 | 亚洲一区二区三区成人 | 久久精品亚洲一区二区 | 国产一区二区免费看 | 91视频在| 日韩在线播放视频 | 久久99精品久久久久久园产越南 | 欧美日韩中文 | 成人久久久精品乱码一区二区三区 | 成人久久网| 国产不卡视频在线 | 成人精品毛片 | 麻豆传媒免费看 | 欧美二区在线观看 | 免费成人高清在线视频 | 欧美激情免费 | 国产在线二区 | 亚洲国产黄色 | 国产精品99久久久久 | 不用播放器av | 亚洲第二区| 日韩三区在线 | 6080yy精品一区二区三区 | 亚洲国产精品久久人人爱 | 日本在线一区二区三区 | 久久久久久综合 | 亚洲一区久久久 | 国产欧美一区二区三区另类精品 | 日美一级毛片 |