AppBarLayout
有一個很是噁心的設計,就是將自身的Fling徹底內部消化,從而致使了RecyclerView
之類的控件不能Fling。本文打算採用自定義Behavior
的方法來解決該問題,應該是現在網上最簡單的方法。 我在思考本身的解決方法以前,在網上簡單的搜索一番前人的答案,發現已知的答案都很是的麻煩,好多的方法都是將AppBarLayout
相關代碼拷貝出來,而後簡單修改,工做量很是的大和複雜。本文將介紹做者本人思考出來的一種方法,本文的好處就是:你只定義兩個Behavior
就能實現。bash
在解決的問題,咱們先來看一下這個問題產生的緣由,問題的緣由在HeaderBehavior
裏面有一段代碼:ide
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1, 0.01f);
mVelocityTracker.computeCurrentVelocity(1000);
float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
}
複製代碼
fling的範圍是[ -getScrollRangeForDragFling(child), 0]
,這就致使了AppBarLayout
Fling到頂部就當即中止,緣由的就是這樣,很是的簡單。post
咱們要想解決這個問題,就要從根本入手。既然Fling不能傳遞出去致使的這個問題,咱們就將它傳遞出去,如何傳遞出去呢?本文采用嵌套滑動方式傳遞。ui
AppBarLayout
的Behavior
關鍵在於怎麼將Fling傳遞出去,咱們來看看,主要分爲三步:this
- 將
HeaderBehavior
中,將onTouchEvent
方法及其相關方法代碼拷貝出來- 在
AppBarLayout
Fling時,調用咱們本身的fling方法(這裏雖然說是本身的fing方法,其實就是將原來的fling方法代碼拷貝出來)。- 改寫
FlingRunnable
,使Fling事件能順利傳遞出去。
在這其中,咱們須要注意一點:spa
- 調用fling方法進行fling時,
minOffset
傳爲Integer.MIN_VALUE
,這樣這個Fling事件就能夠肆意妄爲了,不用侷限於某一個範圍。
@Override
public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
// ······
case MotionEvent.ACTION_UP:
if (velocityTracker != null) {
velocityTracker.addMovement(ev);
velocityTracker.computeCurrentVelocity(1000);
float yvel = velocityTracker.getYVelocity(activePointerId);
flingV2(parent, child, Integer.MIN_VALUE, 0, yvel);
}
// ······
}
複製代碼
如上的代碼就是咱們改寫了的部分,其中flingV2
方法就是咱們自定義的fling方法,代碼與原來的fling
方法如出一轍。 接下來改寫FlingRunnable
類,咱們來看看:設計
private class FlingRunnable implements Runnable {
private final CoordinatorLayout parent;
private final AppBarLayout layout;
FlingRunnable(CoordinatorLayout parent, AppBarLayout layout) {
this.parent = parent;
this.layout = layout;
}
@Override
public void run() {
if (layout != null && scroller != null) {
if (scroller.computeScrollOffset()) {
scroll();
} else {
onFlingFinished(parent, layout);
}
}
}
private void scroll() {
// 若是AppBarLayout滑到閾值,此時須要將fling事件傳遞下去
if (scroller.getCurrY() <= -getScrollRangeForDragFling(layout) && scroller.getStartX() > scroller.getFinalY()) {
setHeaderTopBottomOffset(parent, layout, -getScrollRangeForDragFling(layout));
if (layout.startNestedScroll(View.SCROLL_AXIS_VERTICAL)) {
layout.dispatchNestedFling(scroller.getCurrVelocity(), scroller.getCurrVelocity(), false);
}
scroller.forceFinished(true);
} else { // 若是AppBarLayout尚未滑到閾值,就讓開心的滑動。
setHeaderTopBottomOffset(parent, layout, scroller.getCurrY());
}
ViewCompat.postOnAnimation(layout, this);
}
}
複製代碼
AppBarLayout
自定義Behavior
流程到此結束了,接下來咱們看一下RecyclerView
的Behavior
。code
代碼以下:cdn
public class FlingScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
public FlingScrollingViewBehavior() {
}
public FlingScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return true;
}
@Override
public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {
if (child != target) {
((RecyclerView) (child)).fling((int) velocityX, (int) velocityY);
}
return true;
}
}
複製代碼
而後咱們在xml中將Behavior
進行綁定,你就發現,AppBarLayout
能夠Fling了,效果以下: xml
本方法很是的簡單,到此就結束了,不像網上其餘方法那樣長篇大論。在這裏,我對本文作一個簡單的總結。
- 自定義AppBarLayout的Behavior,這其中必定保證把Fling事件傳遞出去。
- 自定義RecyclerView的Behavior中,能夠經過
RecyclerView
的fling方法進行fling。
原本上傳源碼供你們參考的,可是介於一部分的我的緣由,暫時不能上傳源碼,實在抱歉!後續我會補上的,有想要源碼的同窗能夠先加個人QQ:2196995023。