高仿餓了么點(diǎn)餐


首先來了解一下餓了么的效果丙者,可以確定使用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)

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)如下圖:


Menu

原理分析:

一伍掀、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)

項(xiàng)目地址


【原創(chuàng)出品 未經(jīng)授權(quán) 禁止轉(zhuǎn)載】
【歡迎微友分享轉(zhuǎn)發(fā) 禁止公號等未經(jīng)授權(quán)的】
【學(xué)習(xí)使用 如有觸犯相關(guān),請聯(lián)系作者莉测、配合刪改】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颜骤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捣卤,更是在濱河造成了極大的恐慌忍抽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件董朝,死亡現(xiàn)場離奇詭異鸠项,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)子姜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門祟绊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哥捕,你說我怎么就攤上這事牧抽。” “怎么了遥赚?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵扬舒,是天一觀的道長。 經(jīng)常有香客問我凫佛,道長讲坎,這世上最難降的妖魔是什么孕惜? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮衣赶,結(jié)果婚禮上诊赊,老公的妹妹穿的比我還像新娘。我一直安慰自己府瞄,他們只是感情好碧磅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遵馆,像睡著了一般鲸郊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上货邓,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天秆撮,我揣著相機(jī)與錄音,去河邊找鬼换况。 笑死职辨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戈二。 我是一名探鬼主播舒裤,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼觉吭!你這毒婦竟也來了腾供?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤鲜滩,失蹤者是張志新(化名)和其女友劉穎伴鳖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徙硅,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榜聂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闷游。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峻汉。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脐往,靈堂內(nèi)的尸體忽然破棺而出休吠,到底是詐尸還是另有隱情,我是刑警寧澤业簿,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布瘤礁,位于F島的核電站,受9級特大地震影響梅尤,放射性物質(zhì)發(fā)生泄漏柜思。R本人自食惡果不足惜岩调,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赡盘。 院中可真熱鬧号枕,春花似錦、人聲如沸陨享。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抛姑。三九已至赞厕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間定硝,已是汗流浹背皿桑。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔬啡,地道東北人诲侮。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像箱蟆,于是被迫代替她去往敵國和親浆西。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354