咱們知道,Desgin包中的AppBarLayout配合CollapsingToolbarLayout能夠實現摺疊效果。可是頂部在快速滑動到摺疊狀態時,底部的NestedScrollChild不會由於慣性跟着滑動,整個滑動過程瞬間中止,給人一種很不流暢的感受。爲了能讓咱們的AppBarLayout能Fling更流暢,咱們須要在從新修改源碼,定製一個FlingAppBarLayout,可以實現相似餓了麼首頁效果 java
咱們知道AppBarLayout之因此可以有摺疊效果,是由於有一個默認的Behavior,並且AppBarLayout在快速滑動時,佈局也可以快速展開和收縮,所以能夠猜想內部有可能處理了Fling事件。經過源碼,找到對應的Behavior,它繼承自HeaderBehavior,經過onTouchEvent方法,找到了對應對於Fling事件的處理git
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
}
複製代碼
進入fling方法,找到了scroller對象,AppBarLayout的快速滑動效果就是經過它來實現的。至於爲何AppBarLayout向上快速滑動到邊界時,忽然中止,沒有慣性滑動,是由於scroller在調用fling方法時設置了minOffset(向上滑動邊界)github
final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset, int maxOffset, float velocityY) {
if (mFlingRunnable != null) {
layout.removeCallbacks(mFlingRunnable);
mFlingRunnable = null;
}
if (mScroller == null) {
mScroller = new OverScroller(layout.getContext());
}
mScroller.fling(
0, getTopAndBottomOffset(), // curr
0, Math.round(velocityY), // velocity.
0, 0, // x
minOffset, maxOffset); // y
if (mScroller.computeScrollOffset()) {
mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
ViewCompat.postOnAnimation(layout, mFlingRunnable);
return true;
} else {
onFlingFinished(coordinatorLayout, layout);
return false;
}
}
複製代碼
而具體的view的移動,則是經過FlingRunnable來實現。ide
private class FlingRunnable implements Runnable {
private final CoordinatorLayout mParent;
private final V mLayout;
FlingRunnable(CoordinatorLayout parent, V layout) {
mParent = parent;
mLayout = layout;
}
@Override
public void run() {
if (mLayout != null && mScroller != null) {
if (mScroller.computeScrollOffset()) {
setHeaderTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
// Post ourselves so that we run on the next animation
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
}
}
複製代碼
經過這個FlingRunnable類,咱們知道AppBarLayout能快速展開和收縮,就是經過它實現的。佈局
首先,咱們把design中的AppBarLayout源碼複製到本身的package中,引入報紅的相關文件,具體以下: post
其中ScrollItem,ReflectUtil,ViewPagerUtil爲咱們本身定義的,其餘都是design包拷貝的。 經過前面三塊代碼,咱們知道AppBarLayout的Fling效果是經過scroller實現的,滑動的邊界時經過minOffset和maxOffset來控制的,當滑動的offset超出範圍時,scroller調用computeScrollerOffset就爲false,頂部view就中止移動了。所以爲了能讓AppBarLayout在向上滑動到minOffset邊界時不中止移動,把這個minOffset保存到FlingRunnable中,在scroller.fling方法中這個更小的offset,這個在滑動到minOffset時,computeScrollerOffset就不會爲false,而且在FlingRunnable中由於有minOffset,咱們能夠在mScroller.computeScrollOffset裏判斷是否滑出邊界,經過差值,繼續滑動底部的可滑動佈局。this
mScroller.fling(
0, getTopAndBottomOffset(), // curr
0, Math.round(velocityY), // velocity.
0, 0, // x
minOffset-5000, maxOffset); // 設置一個很大的值,在向上滑動時不會由於低於minOffset而中止滑動
複製代碼
在FlingRunnable中新增minOffset字段,run方法中,若是currY<minOffset表示AppBarLayout向上滑動值收縮狀態,能夠滑動底部佈局了,scrollNext(),傳入偏移量minOffset-currYspa
class FlingRunnable implements Runnable {
private final CoordinatorLayout mParent;
private final V mLayout;
private int minOffset;
FlingRunnable(CoordinatorLayout parent, V layout, int min) {
mParent = parent;
mLayout = layout;
minOffset = min;
}
@Override
public void run() {
if (mLayout != null && mScroller != null) {
if (mScroller.computeScrollOffset()) {
int currY = mScroller.getCurrY();
if (currY < 0 && currY < minOffset) {
scrollNext(minOffset - currY);
setHeaderTopBottomOffset(mParent, mLayout, minOffset);
} else {
setHeaderTopBottomOffset(mParent, mLayout, currY);
}
// Post ourselves so that we run on the next animation
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
}
}
複製代碼
在構造FlingRunnable時傳入minOffsetcode
final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset, int maxOffset, float velocityY) {
if (mFlingRunnable != null) {
layout.removeCallbacks(mFlingRunnable);
mFlingRunnable = null;
}
if (mScroller == null) {
mScroller = new OverScroller(layout.getContext());
}
mScroller.fling(
0, getTopAndBottomOffset(), // curr
0, Math.round(velocityY), // velocity.
0, 0, // x
minOffset-5000, maxOffset); // y
if (mScroller.computeScrollOffset()) {
mFlingRunnable = new FlingRunnable(coordinatorLayout, layout, minOffset);
ViewCompat.postOnAnimation(layout, mFlingRunnable);
return true;
} else {
...
}
}
複製代碼
接着就是具體scrollNext方法了,具體就是找到底部的NestedScrollingChild(如RecyclerView,NestedScrollView,ViewPager,主要是這三個)。 在FlingRunnable中新增ScrollItem字段用於處理scroll邏輯cdn
class FlingRunnable implements Runnable {
private final CoordinatorLayout mParent;
private final V mLayout;
private int minOffset;
private ScrollItem scrollItem;
FlingRunnable(CoordinatorLayout parent, V layout, int min) {
mParent = parent;
mLayout = layout;
minOffset = min;
initNextScrollView(parent);
}
private void initNextScrollView(CoordinatorLayout parent) {
int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
View v = parent.getChildAt(i);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) v.getLayoutParams();
if (lp.getBehavior() instanceof AppBarLayout.ScrollingViewBehavior) {
scrollItem = new ScrollItem(v);
}
}
@Override
public void run() {
if (mLayout != null && mScroller != null) {
if (mScroller.computeScrollOffset()) {
int currY = mScroller.getCurrY();
if (currY < 0 && currY < minOffset) {
scrollItem.scroll(minOffset - currY); //處理邏輯在ScrollItem中
setHeaderTopBottomOffset(mParent, mLayout, minOffset);
} else {
setHeaderTopBottomOffset(mParent, mLayout, currY);
}
// Post ourselves so that we run on the next animation
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
}
}
複製代碼
而在新增的ScrollItem中,咱們來處理對應的scroll操做(NestedScrollView能夠經過scrollTo,而RecyclerView則須要用LinearLayoutManager來控制了)
public class ScrollItem {
private int type; //1: NestedScrollView 2:RecyclerView
private WeakReference<NestedScrollView> scrollViewRef;
private WeakReference<LinearLayoutManager> layoutManagerRef;
public ScrollItem(View v) {
findScrollItem(v);
}
/** * 查找須要滑動的scroll對象 * * @param v */
protected boolean findScrollItem(View v) {
if (findCommonScroll(v)) return true;
if (v instanceof ViewPager) {
View root = ViewPagerUtil.findCurrent((ViewPager) v);
if (root != null) {
View child = root.findViewWithTag("fling");
return findCommonScroll(child);
}
}
return false;
}
private boolean findCommonScroll(View v) {
if (v instanceof NestedScrollView) {
type = 1;
scrollViewRef = new WeakReference<NestedScrollView>((NestedScrollView) v);
stopScroll(scrollViewRef.get());
return true;
}
if (v instanceof RecyclerView) {
RecyclerView.LayoutManager lm = ((RecyclerView) v).getLayoutManager();
if (lm instanceof LinearLayoutManager) {
LinearLayoutManager llm = (LinearLayoutManager) lm;
type = 2;
layoutManagerRef = new WeakReference<LinearLayoutManager>(llm);
stopScroll((RecyclerView) v);
return true;
}
}
return false;
}
/** * 中止NestedScrollView滾動 * * @param v */
private void stopScroll(NestedScrollView v) {
try {
Field field = ReflectUtil.getDeclaredField(v, "mScroller");
if (field == null) return;
field.setAccessible(true);
OverScroller scroller = (OverScroller) field.get(v);
if (scroller != null) scroller.abortAnimation();
} catch (Exception e) {
e.printStackTrace();
}
}
/** * 中止RecyclerView滾動 * * @param */
private void stopScroll(RecyclerView rv) {
try {
Field field = ReflectUtil.getDeclaredField(rv, "mViewFlinger");
if (field == null) return;
field.setAccessible(true);
Object obj = field.get(rv);
if (obj == null) return;
Method method = obj.getClass().getDeclaredMethod("stop");
method.setAccessible(true);
method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
public void scroll(int dy) {
if (type == 1) {
scrollViewRef.get().scrollTo(0, dy);
} else if (type == 2) {
layoutManagerRef.get().scrollToPositionWithOffset(0, -dy);
}
}
}
複製代碼
至於ViewPager,由於getChildAt會有空值問題,這裏是經過adapter獲取fragment而後獲取rootView作處理
public class ViewPagerUtil {
public static View findCurrent(ViewPager vp) {
int position = vp.getCurrentItem();
PagerAdapter adapter = vp.getAdapter();
if (adapter instanceof FragmentStatePagerAdapter) {
FragmentStatePagerAdapter fsp = (FragmentStatePagerAdapter) adapter;
return fsp.getItem(position).getView();
} else if (adapter instanceof FragmentPagerAdapter) {
FragmentPagerAdapter fp = (FragmentPagerAdapter) adapter;
return fp.getItem(position).getView();
}
return null;
}
}
複製代碼
這裏暫時沒作PagerAdapter的處理邏輯,ViewPager找到當前item界面rootView後,須要找到須要繼續慣性滑動到RecyclerView或NestedScrollView,爲方便查找,咱們給fragment佈局中須要滑動的組件添加tag:「fling」,這樣就能夠經過findViewWithTag("fling")找到它。 好了,基本的滑動邏輯處理完了,咱們本身的AppBarLayout能夠慣性fling了。會看ScrollItem代碼,我加了stopScroll的邏輯。那是由於在底部recyclerView或NestedScrollView快速向下滑動至AppBarLayout展開,而這時在AppBarLayout想要快速向上滑動,應爲底部正在滑動,致使二者衝突,不能正常向上滑動,因此AppBarLayout在向上快速滑動時,要中止底部滑動。經過NestedScrollView和RecyclerView的源碼,咱們找到控制滑動邏輯的OverScroller和ViewFlinger,咱們能夠經過反射來中止對應的滑動。
FlingAppBarLayout 具體的效果在github上下載,工程還有關於SmartRefreshLayout兼容適配