完美解決Android中的ScrollView嵌套ScrollView滑動衝突問題

疫情期間閒在家中實在是無聊,遊戲也提不起興趣,一天天除了喫就是睡,哪兒也去不了,本以爲自己挺宅的,沒想到疫情讓我從新認識了自己,原來自己也是有一顆嚮往自由的心啊!突然就理解了那些長期被關在家中盼望出去撒潑的狗子是一種怎樣的心情了!

罷了罷了,抱着想起電腦中留有不少以前寫的Demo,看看能不能撿個出來寫(shui)篇博客的想法,於是就產生了這篇文章。

滑動衝突在Android中是一個比較常見的問題,尤其是在一些效果比較複雜的頁面會經常遇到,今天我就ScrollView嵌套ScrollView滑動衝突問題,從問題解析、解決思路、實際代碼來一步步給大家進行講解,如果哪裏講的不好,還希望大家勇於閉嘴。

問題解析

追根溯源,滑動衝突會產生的根本原因在於滑動沒有被正確的執行者執行,屬於內部的滑動被外部劫持消耗掉了。

要想理解這個問題需要我們對Android中的事件分發機制有所瞭解,如果對此機制不瞭解的夥伴可以查看這篇博客—包你看懂Android中的事件分發機制

那麼,我們看看ScrollView中對於滑動事件是如何做處理的。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        
        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        * 當用戶手指在屏幕上處於滑動狀態時,攔截這個動作。
        */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;
        }

        /*...*/
}

看到了不,有理有據,這鍋ScrollView自己背了。

解決思路

1、內部獲取滑動控制權

那麼我想問你一下電腦前的各位,發現了原因後你們知道怎麼解決了嗎?其實很簡單,控制外層不要讓它攔截屬於內層的滑動就好了,那麼怎麼做呢,我發現在ScrollView的onInterceptTouchEvent()中並沒有對用戶手指摁下的動作進行攔截,也就是沒有對MotionEvent.ACTION_DOWN進行攔截,這就意味着當用戶手指摁下時,這個動作是可以傳遞到ScrollVIew內部中其他VIew上的(不然ScrollView中其他View的點擊長摁事件怎麼響應),那麼我們就可以重寫一個ScrollView,當它接收到MotionEvent.ACTION_DOWN這個動作時請求外層不對動作進行攔截。這點很重要,因爲這時內部的ScrollVIew就掌握了主動權,可以處理用戶手指在屏幕上的一系列操作,

2、內外層滑動關聯

那麼,到這裏就解決完了嗎?不不不,這只是開頭,如果此時你跟着我上面的思路敲完了代碼,你會發現內外兩個ScrollVIew的滑動事件是獨立的,雖然內部的ScrollView可以滑動,但是當它滑動到頂部或底部時並沒有跟外部的ScrollView進行聯動,這樣用戶的滑動體驗會很差,怎麼解決呢?發動你機智的小腦瓜想一想,對咯,那就是在合適的時機將外層的攔截權還給它。那什麼是合適的時機呢?無非就是內部ScrollView滑動到頂或者底的時候。

OK,問題都清楚了,方案也提供了,現在你就可以試着寫寫代碼嘗試解決衝突了。

實際代碼

這裏我把我寫的代碼放出來,供大家參考。

public class MyScrollView extends ScrollView {
    
    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //在Y軸上可以滑動的最大距離 = 總長度 - 當前展示區域長度
    private int maxY = getChildAt(0).getMeasuredHeight() - getHeight();
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (getScrollY() > 0 && getScrollY() < maxY)
                    getParent().requestDisallowInterceptTouchEvent(true);
                else
                    getParent().requestDisallowInterceptTouchEvent(false);
                /*if (getScrollY()==0)
                    Log.i("kkk","滑到頭了");
                if (getChildAt(0).getMeasuredHeight() == getScrollY() + getHeight())
                    Log.i("kkk","滑到底了");*/
                break;
            case MotionEvent.ACTION_UP:
                getParent().getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

重寫後的ScrollView嵌套滑動效果

最後

在我最開始接觸Android開發的時候,對這種事件衝突的問題是一點辦法都沒有,因爲不知道怎麼排查問題,也不知道如何下手,不過隨着對Android知識的不斷加深,反過頭來在看這些問題也不過如此,人生又何嘗不是這樣呢,不管過去還是未來,總是有着我們不懂的問題在等着我們去解決,而我們則在這個解決的過程中不斷的進步和成長,成就更好的自己。對於今年突發的新冠狀病毒,已經不僅僅是我們個人要面臨的問題,更是我們所有中國人需要共同面對去解決的問題,真心希望這場疫情可以快點過去,也祝願那些在抗疫一線的工作人員能夠保重身體,你們是最美最帥的!中國加油!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章