首先來了解一下餓了么的效果丙者,可以確定使用NestedScrolling機(jī)制實(shí)現(xiàn)最為簡單,其實(shí)這個(gè)確實(shí)很牛逼,可以設(shè)計(jì)出很多花哨的效果咬最。
大家知道的CoordinatorLayout + AppBarLayout + 子View(實(shí)現(xiàn)NestedScrollingChild)
確定了采用機(jī)制吠各,后面就是根據(jù)需求設(shè)計(jì)UI結(jié)構(gòu)了臀突,整體如右圖所示
UI結(jié)構(gòu)說明:
1、CoordinatorLayout 包含了 AppBarLayout(1)贾漏、自定義ViewPager 候学、 頂部標(biāo)題欄
2、ViewPager 包含3個(gè)Fragment (MenuFragment纵散、CommentFragment梳码、EmptyFragment)
3、MenuFragment中UI結(jié)構(gòu)如下圖:
原理分析:
一伍掀、NestedScrolling機(jī)制
這個(gè)是Android在發(fā)布5.0之后加入了嵌套滑動機(jī)制NestedScrolling,為嵌套滑動提供了更方便的處理方案掰茶。
網(wǎng)絡(luò)上對嵌套滑動機(jī)制有詳細(xì)的分析,這里就簡單說一下:
說道嵌套滑動蜜笤,離不開一下幾個(gè)類:
(1)NestedScrollingChild
嵌套滑動的子View需要實(shí)現(xiàn)此接口濒蒋,將嵌套滑動的手勢事件例如:start、stop瘩例、dispatch等事件通知父ViewRecyclerView其實(shí)就是NestedSrollingChild的實(shí)現(xiàn)類啊胶。
(2)NestedScrollingParent
嵌套滑動的父View需要實(shí)現(xiàn)此接口,協(xié)調(diào)處理嵌套滑動事件垛贤。
(3)NestedScrollingChildHelper
配套NestedScrollingChild使用焰坪。
(4)NestedScrollingParentHelper
配套NestedScrollingParent使用。
從上面的實(shí)現(xiàn)可以看出聘惦,基本上都是通過mParentHelper和mChildHelper來完成滑動的某饰,沒接觸過這方面的同學(xué)看著肯定覺得很難理解,的確有些跳躍性善绎,在說清楚這個(gè)問題之前必須先把這幾個(gè)類之間的交互邏輯理清楚才能不至于不知所云黔漂。
先來梳理一下子View和父View的接中都有哪些方法。這種套路一般都是子View發(fā)起的然后父View進(jìn)行回調(diào)從而完成配合禀酱。
子View | 父View |
---|---|
startNestedScroll | onStartNestedScroll炬守、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
二、自定義Behavior 自定義ViewPager
(1)對于Behavor我們只需要關(guān)注這幾個(gè)方法:
onStartNestedScroll 判斷是否觸發(fā)嵌套滑動剂跟,并反饋childView情況
onNestedPreScroll 預(yù)處理嵌套滑動减途,并反饋childView滑動處理情況
onStopNestedScroll 滑動結(jié)束處理
(2)對于NestedViewPager我們只需要關(guān)注這幾個(gè)方法:
onInterceptTouchEvent 判斷是否處理手勢事件
onTouchEvent 處理真正的手勢事件
這兩個(gè)方法大家應(yīng)該比較熟悉酣藻。也是基本的Android手勢處理方法,這里不是本文重點(diǎn)就不展開說明了鳍置。
下面我們來看看他們是怎么配合使用的
1辽剧、首先是DOWN事件,NestedViewPager 的onInterceptTouchEvent方法中通知Behavor的startNestedScroll
在Behavor的onStartNestedScroll 我們重寫了這個(gè)方法來屏蔽我們不需要的NestedFling事件
NestedViewPager 的onInterceptTouchEvent:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
boolean intercept = false;
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
boolean superIntercept = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastMotionX = mInitialMotionX = (int)ev.getX();
mLastMotionY = mInitialMotionY = (int)ev.getY();
intercept = mIsBeingDragged = false;
mIsScrollVertical =false;
superIntercept = super.onInterceptTouchEvent(ev);
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break;
case MotionEvent.ACTION_MOVE: {
final int x = (int)ev.getX();
final int dx = x - mLastMotionX;
final int xDiff = Math.abs(dx);
final int y = (int) ev.getY();
final int dy = y - mInitialMotionY;
final int yDiff = Math.abs(dy);
if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff && isInterceptNested(dy, x, y)) {
mLastMotionY = y;
intercept = true;
mIsScrollVertical = true;
mNestedYOffset = 0;
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
} else if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff && !canScrollHorizontal
(this, false, dx, x, y)) {
intercept = true;
mIsScrollVertical = false;
superIntercept = super.onInterceptTouchEvent(ev);
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
endDrag();
intercept = false;
superIntercept = super.onInterceptTouchEvent(ev);
break;
}
return intercept;
}
Behavor 的onStartNestedScroll事件處理:
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View
directTargetChild, View target, int nestedScrollAxes, int type) {
//屏蔽fling事件
final boolean started = super.onStartNestedScroll(parent, child, directTargetChild,
target, nestedScrollAxes, type) && (type == ViewCompat.TYPE_TOUCH);
if (started && mSpringRecoverAnimator != null && mSpringRecoverAnimator.isRunning()) {
mSpringRecoverAnimator.cancel();
}
return started;
}
2税产、對MOVE事件判斷是否攔截怕轿,當(dāng)ViewPager需要處理此事件就攔截事件,后續(xù)事件會來到onTouchEvent辟拷,如果是橫向滑動事件就調(diào)用默認(rèn)的處理方式撞羽;如果是豎向滑動事件則進(jìn)入第三點(diǎn)。
3衫冻、在onTouchEvent方法中先調(diào)用Behavor的onNestedPreScroll放吩,在Behavor的onNestedPreScroll 我們重寫了這個(gè)方法來根據(jù)滑動位置改變UI效果達(dá)到連續(xù)漸變、轉(zhuǎn)變的效果羽杰,并通過consumed[1]來反饋我們的滑動消耗距離
NestedViewPager 的onTouchEvent:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(!mIsScrollVertical) {
return super.onTouchEvent(ev);
}
initVelocityTrackerIfNotExists();
final int action = ev.getActionMasked();
MotionEvent vtev = MotionEvent.obtain(ev);
if (action == MotionEvent.ACTION_DOWN) {
mNestedYOffset = 0;
}
vtev.offsetLocation(0, mNestedYOffset);
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastMotionX = mInitialMotionX = (int)ev.getX();
mLastMotionY = mInitialMotionY = (int)ev.getY();
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break;
case MotionEvent.ACTION_MOVE:
final int y = (int) ev.getY();
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
deltaY -= mScrollConsumed[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
if (mIsBeingDragged) {
mLastMotionY = y - mScrollOffset[1];
if (dispatchNestedScroll(0, 0, 0, deltaY, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
mLastMotionY -= mScrollOffset[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
endDrag();
break;
}
vtev.recycle();
return true;
}
Behavor 的onNestedPreScroll處理具體UI效果:
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed, int type) {
if (dy > 0) {//上滑
if (mOffsetSpring - dy >= mAppbarLayoutMinOffset) {
consumed[1] = dy;
mOffsetSpring = mOffsetSpring - dy;
} else {
consumed[1] = dy;//mOffsetSpring - mAppbarLayoutMinOffset;
mOffsetSpring = mAppbarLayoutMinOffset;
}
} else {
if (mOffsetSpring - dy <= mAppbarLayoutMaxOffset) {
consumed[1] = dy;
mOffsetSpring = mOffsetSpring - dy;
} else {
consumed[1] = mOffsetSpring - mAppbarLayoutMaxOffset;
mOffsetSpring = mAppbarLayoutMaxOffset;
}
}
onHandleScroll(child, mNormalViewHeight + mOffsetSpring);
setTopAndBottomOffset((mOffsetSpring <= 0) ? mOffsetSpring : 0);
checkShouldSpring(coordinatorLayout, child, mOffsetSpring);
}
4、并得到Behavor處理嵌套滑動的處理結(jié)果到推。再根據(jù)結(jié)果調(diào)用Behavor的onNestedScroll
5考赛、最后UP事件處理StopNestedScroll相關(guān)
【原創(chuàng)出品 未經(jīng)授權(quán) 禁止轉(zhuǎn)載】
【歡迎微友分享轉(zhuǎn)發(fā) 禁止公號等未經(jīng)授權(quán)的】
【學(xué)習(xí)使用 如有觸犯相關(guān),請聯(lián)系作者莉测、配合刪改】