android 滑動(dòng)沖突

這個(gè)知識(shí)點(diǎn)是在太大了讲冠,是年多個(gè)知識(shí)點(diǎn)的匯總,很難搞俭正,高級(jí)的頁(yè)面視圖效果和動(dòng)畫(huà)都離不開(kāi)他奸鬓,我們必須想一切辦法搞明白~

這對(duì)這部分內(nèi)容我也是新手,本文負(fù)責(zé)記錄下找到的資料掸读,分類(lèi)匯總下全蝶。

處理思路


在開(kāi)發(fā)中,滑動(dòng)沖突有很多寺枉,比如ScrollView嵌套ListView抑淫、ScrollView嵌套ViewPager、ViewPager嵌套ScrollView等等各種嵌套之后的滑動(dòng)沖突

歸根結(jié)底可以分為兩種:

  • 同方向滑動(dòng)沖突:
    比如ScrollView嵌套ListView姥闪,或者是ScrollView嵌套自己

  • 不同方向滑動(dòng)沖突:
    比如ScrollView嵌套ViewPager始苇,或者是ViewPager嵌套ScrollView,這種情況其實(shí)很典型】鹪現(xiàn)在大部分應(yīng)用最外層都是ViewPager+Fragment 的底部切換(比如微信)結(jié)構(gòu)催式,這種時(shí)候,就很容易出現(xiàn)滑動(dòng)沖突避归。不過(guò)ViewPager里面無(wú)論是嵌套ListView還是ScrollView荣月,滑動(dòng)沖突是沒(méi)有的,畢竟是官方的東西梳毙,可能已經(jīng)考慮到了這些哺窄,所以比較完善。

處理思路也可以分2種:

  • 從外層處理
  • 從內(nèi)層處理

從外層處理

比如在外層滑動(dòng)容器中账锹,判斷滑動(dòng)方向萌业,不是外層滑動(dòng)容易方向而是內(nèi)層滑動(dòng)控件方向的,外層就不攔截了奸柬,交給內(nèi)層

從內(nèi)層處理

一般生年,官方提供的 layout,view 都不會(huì)攔截 action_down,這個(gè)事件廓奕,所以內(nèi)層滑動(dòng)控件的 dispatchTouchEvent -> onInterceptTouchEvent 肯定執(zhí)行一次的抱婉,這時(shí)我們可以在內(nèi)層 view 的 onInterceptTouchEvent 方法中請(qǐng)求忽略外層容器攔截事件 getParent().requestDisallowInterceptTouchEvent(true) 档叔,然后我們判斷是不是需要我們消費(fèi)的事件,不是的話我們不消費(fèi)蒸绩,交給上一層去處理蹲蒲。

內(nèi)層控件若是 view 的話,是沒(méi)有 onInterceptTouchEvent 方法的侵贵,那么 getParent().requestDisallowInterceptTouchEvent(true) 寫(xiě)在哪里呢 setOnTouchListener届搁,OnTouchListener.onTouch 方法在 view 的事件處理中最先執(zhí)行,是個(gè)合適的位置

參考資料看這個(gè)

大體的思路基本都是根據(jù)這個(gè)邏輯走的窍育,下面我們看看具體的滑動(dòng)沖突的情況卡睦。

ScrollView嵌套ViewPager沖突


自定義一個(gè)MyScrollView繼承ScrollView,重寫(xiě) onInterceptTouchEvent方法漱抓,在 Action_Move事件中判斷表锻,如果水平滑動(dòng)距離大于豎直滑動(dòng)距離,則return false乞娄,表示不攔截事件瞬逊,把事件分發(fā)到下一級(jí)控件,交由下一級(jí)處理

public class MyScrollView extends ScrollView{

    private float xDistance;
    private float yDistance;
    private float xLast;
    private float yLast;

    /**
     * 在該方法中進(jìn)行判斷
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0.0f;
                xLast = ev.getX();
                yLast = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();

                xDistance += Math.abs(curX - xLast);
                yDistance += Math.abs(curY - yLast);

                if(xDistance > yDistance)
                    return false;

                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

參考資料:

ScrollView 嵌套 ScrollView


在 onInterceptTouchEvent 事件攔截函數(shù)內(nèi)巧用 getParent().requestDisallowInterceptTouchEvent(true) 就可以讓所有的父控件不攔截我們的事件了仪或。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.onInterceptTouchEvent(ev);
    }

參考例子:

NestedScrollView 嵌套 RecyclerView


這是最簡(jiǎn)單的處理嵌套滾動(dòng)沖突的辦法了确镊,典型案例:NestedScrollView 嵌套 RecyclerView

參考例子:

給 RecyclerView 設(shè)置禁止?jié)L動(dòng),RecyclerView 就會(huì)忽略 view 的復(fù)用機(jī)制范删,當(dāng)前屏幕控件恩那個(gè)顯示多少條 item 蕾域,就顯示多少條 item,剩下的會(huì)被忽略到旦。

這適用于 item 數(shù)量能偶一屏顯示的列表旨巷。對(duì)于數(shù)量多的列表需要考重寫(xiě)布局管理器

//布局文件的RecyclerView中設(shè)置
android:nestedScrollingEnabled="false" 
//或者Java代碼設(shè)置
recyclerView.setNestedScrollingEnabled(false);

說(shuō)下缺點(diǎn),禁用 RecyclerView 的滾動(dòng)之后添忘,在滾動(dòng)嵌套中采呐,局域內(nèi)層的 RecyclerView 還是會(huì)首先收到觸摸事件的,對(duì)于非滾動(dòng)方向的事件在處理后搁骑,上層滾動(dòng)控件比如 NestedScrollView 是收不到的斧吐。

典型的例子就是在這個(gè)嵌套頁(yè)面中,我們?cè)趦?nèi)層列表的位置斜的角度稍微大點(diǎn)上下滑靶病,你會(huì)發(fā)現(xiàn)我滑動(dòng)的手勢(shì)被列表吃了会通,外面的滾動(dòng)控件不會(huì)滾動(dòng)口予。

viewpager嵌套 webview


webview 中加載的網(wǎng)頁(yè)有時(shí)是需要處理手勢(shì)操作的娄周,比如頁(yè)面頭部位有一個(gè) binner ,是頁(yè)面的部分需要手勢(shì)操作而不是頁(yè)面全部沪停,頁(yè)面的手勢(shì)操作需要 webview 能夠消費(fèi)手勢(shì)事件才行煤辨。若是此時(shí) webview 的外部是像 viewpager 這樣也需要消費(fèi)手勢(shì)事件的話就產(chǎn)生事件沖突了裳涛,那么應(yīng)該怎么做到平衡呢,原則是內(nèi)層的控件需要時(shí)優(yōu)先吧手勢(shì)事件給內(nèi)層控件众辨,內(nèi)層控件不需要時(shí)聲明不消費(fèi)事件端三,交給上一級(jí)處理。

參考這個(gè)例子:

網(wǎng)頁(yè)提供 js 方法鹃彻,告知我們頁(yè)面可滑動(dòng)元素距屏幕頂部的位置郊闯,并調(diào)用 JAVA 方法同步我們的代碼內(nèi),然后在 webview 的 onTouch (view 沒(méi)有 onInterceptTouchEvent 方法) 方法中獲取當(dāng)前觸摸點(diǎn)的距離屏幕左上角的位置蛛株,然后和頁(yè)面中可滑動(dòng)元素距屏幕左上角的位置做比對(duì)团赁,觸摸點(diǎn)在頁(yè)面可滑動(dòng)元素范圍內(nèi),webview 就接受事件谨履,若不再那么 webview 就不接受事件欢摄,

@Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            //獲取y軸坐標(biāo)
            float y = motionEvent.getRawY();
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    getHTMLPosition();
                    if (null != mPagerDesc) {
                        int top = mPagerDesc.top;
                        int bottom = top + (mPagerDesc.bottom - mPagerDesc.top);
                        //將css像素轉(zhuǎn)換為android設(shè)備像素并考慮通知欄高度
                        top = (int) (top * metric.density) + height 
                        bottom = (int) (bottom * metric.density) + height    
                        //如果觸摸點(diǎn)的坐標(biāo)在輪播區(qū)域內(nèi),則由webview來(lái)處理事件笋粟,否則由viewpager來(lái)處理
                        if (y > top && y < bottom) {
                            webview.requestDisallowInterceptTouchEvent(true);
                        } else {
                            webview.requestDisallowInterceptTouchEvent(false);
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
            }

禁止 Viewpager 滑動(dòng)


平時(shí)我們有時(shí)是由這個(gè)需求的怀挠,那么怎么處理 Viewpager 呢,其實(shí)看過(guò)上面之后害捕,這個(gè)問(wèn)題給為其實(shí)可以自己解決的了了绿淋。

我們不讓 Viewpager 攔截事件,Viewpager 就那不到事件就不能滑動(dòng)尝盼,我們不讓 Viewpager 消費(fèi)事件躬它,那么在 Viewpager 內(nèi)層的 view 不處理事件時(shí)把事件回傳給上級(jí)的 Viewpager 時(shí),Viewpager 也不會(huì)實(shí)際產(chǎn)生話滑動(dòng)东涡,我們把 Viewpager 實(shí)際處理事件的代碼全部刪掉冯吓。然后這么寫(xiě)就行

public class CustomViewPager extends ViewPager {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

最后說(shuō)下


我們?cè)谔幚?view 時(shí),大家要是不想在每次都重寫(xiě) view.setOnTouchListener 方法疮跑,那么就自己定義一個(gè)繼承 view 的類(lèi)组贺,然后在構(gòu)造方法中自己調(diào)一下 setOnTouchListener 方法,傳自己的 OnTouchListener 對(duì)象進(jìn)去

小例子:view 只處理 x 軸放先發(fā)的滑動(dòng)祖娘,y 軸方向的發(fā)回給外層容器

xml

    <com.bloodcrown.aaa02.MyLayout
        android:layout_width="match_parent"
        android:layout_height="350dp"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent">=

        <com.bloodcrown.aaa02.MyView
            android:id="@+id/view_a"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_gravity="center"
            android:background="@color/colorPrimaryDark"/>

    </com.bloodcrown.aaa02.MyLayout>

外層容器

public class MyLayout extends FrameLayout {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        if( event.getAction() == MotionEvent.ACTION_MOVE ){
            Log.d("AAA", " 外層容器_onInterceptTouchEvent: ");
            return true;
        }
        return false;
    }
}

自定義 view

public class MyView extends View {

    public MyView(Context context) {
        super(context);
        setOnTouchListener(touchListener);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setOnTouchListener(touchListener);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(touchListener);
    }

    View.OnTouchListener touchListener = new View.OnTouchListener() {

        int lastX, lastY;
        int x, y;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            getParent().requestDisallowInterceptTouchEvent(true);

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                lastX = (int) event.getX();
                lastY = (int) event.getY();
            }

            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                x = (int) event.getX();
                y = (int) event.getY();
                if (Math.abs(x - lastX) >= Math.abs(y - lastY)) {
                    Log.d("AAA", "內(nèi)層獲取事件失尖,判定方向?yàn)?x 軸滑動(dòng),處理事件");
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    Log.d("AAA", "內(nèi)層獲取事件渐苏,判定方向?yàn)?y 軸滑動(dòng)掀潮,事件交給外層容器處理");
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
            }
            return true;
        }
    };

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市琼富,隨后出現(xiàn)的幾起案子仪吧,更是在濱河造成了極大的恐慌,老刑警劉巖鞠眉,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薯鼠,死亡現(xiàn)場(chǎng)離奇詭異择诈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)出皇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)羞芍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人郊艘,你說(shuō)我怎么就攤上這事荷科。” “怎么了纱注?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵步做,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我奈附,道長(zhǎng)全度,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任斥滤,我火速辦了婚禮将鸵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佑颇。我一直安慰自己顶掉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布挑胸。 她就那樣靜靜地躺著痒筒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茬贵。 梳的紋絲不亂的頭發(fā)上簿透,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音解藻,去河邊找鬼老充。 笑死,一個(gè)胖子當(dāng)著我的面吹牛螟左,可吹牛的內(nèi)容都是我干的啡浊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胶背,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼巷嚣!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起钳吟,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廷粒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后砸抛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體评雌,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡树枫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年直焙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了景东。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奔誓,死狀恐怖斤吐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情厨喂,我是刑警寧澤和措,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站蜕煌,受9級(jí)特大地震影響派阱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弊仪。 院中可真熱鬧争剿,春花似錦、人聲如沸旬蟋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)橘原。三九已至,卻和暖如春涡上,著一層夾襖步出監(jiān)牢的瞬間趾断,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工吩愧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留歼冰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓耻警,卻偏偏與公主長(zhǎng)得像隔嫡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甘穿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359