Android View的滑動(dòng)沖突解決方法

注意:

  1. 閱讀本文需要了解《Android事件分發(fā)機(jī)制》
  2. 在此知識(shí)點(diǎn)歹颓,本人也有部分困惑尚未完全解決,也會(huì)在文中標(biāo)出出來(lái)尚困。

常見的滑動(dòng)沖突場(chǎng)景及對(duì)應(yīng)的處理規(guī)則

  1. 外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致
    面對(duì)這種情況的滑動(dòng)沖突昧旨,解決規(guī)則是:根據(jù)滑動(dòng)是水平滑動(dòng)還是豎直滑動(dòng)來(lái)判斷由誰(shuí)來(lái)攔截事件查乒。判斷滑動(dòng)方向的方法是:比較水平方向和豎直方向滑動(dòng)距離的大小盯拱,或者滑動(dòng)路徑和水平方向的夾角盒发,或者根據(jù)水平方向和豎直方向的速度差。
  2. 外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致
    這種情況的滑動(dòng)沖突狡逢,無(wú)法根據(jù)滑動(dòng)的角度判斷宁舰,一般都是根據(jù)業(yè)務(wù)需要來(lái)進(jìn)行判斷。
  3. 上面兩種情況的結(jié)合
    這種情況比較復(fù)雜奢浑,一般也需要從業(yè)務(wù)上找到突破點(diǎn)明吩。

場(chǎng)景一

場(chǎng)景一:假如打算做個(gè)像ViewPager一樣的效果,父容器如horizonal方向的LinearLayout一樣殷费,容納了三個(gè)ListView。

外部攔截法

就是指點(diǎn)擊事件都先經(jīng)過父容器的攔截處理低葫,如果不需要此事件就不攔截详羡,這樣就可以解決滑動(dòng)沖突的問題。外部攔截法需要重寫父容器的onInterceptTouchEvent()方法嘿悬,在內(nèi)部完成相應(yīng)的攔截即可实柠。

public boolean onInterceptTouchEvent(MotionEvent event){
      boolean intercepted = false;
      int x = (int)event.getX();
      int y = (int)event.getY();
      switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                  intercepted = false;                                 //注解1
                  if (!mScroller.isFinished()){                       //注解2
                        mScroller.abortAnimation();
                        intercepted = true;
                  }
                  break;
            case MotionEvent.ACTION_MOVE:                 //注解3
                  int deltaX = x - mLastXIntercept;
                  int deltaY = y - mLastYintercept;
                  if (Math.abs(deltaX) > Math.abs(deltaY)){     //在這里的if中加入是否攔截的判斷
                        intercepted = true;
                  } else {
                        intercepted = false;
                  }
                  break;
            case MotionEvent.ACTION_UP:
                  intercepted = fasle;                       //注解4
                  break;
            default:
                  break;         
      }
      mLastXIntercept = x;
      mLastYintercept = y;
      
      return intercepted; 
}

注解1:
為什么要在ACTION_DOWN時(shí),intercepted = false善涨?因?yàn)槿绻贏CTION_DOWN時(shí)窒盐,intercepted == true草则,那么根據(jù)Android事件分發(fā)機(jī)制,后面的MOVE事件和UP都會(huì)無(wú)條件的交給父容器去處理蟹漓。這樣的話炕横,事件永遠(yuǎn)無(wú)法傳遞給子View。
此處的困惑:如果僅僅在父容器中設(shè)置ACTION_DOWN時(shí)葡粒,intercepted = false還是不夠的份殿。intercepted = false,然后DOWN事件傳遞給了子View嗽交,按照Android事件分發(fā)機(jī)制卿嘲,如果子View沒有成功處理DOWN事件(即返回了false),最終還是會(huì)調(diào)用父容器的處理方法夫壁。如果這樣的話拾枣,后面的MOVE事件和UP依然會(huì)無(wú)條件的交給父容器去處理。
注解2:
這個(gè)if內(nèi)的語(yǔ)句盒让,針對(duì)的是下面的情況:如果用戶此時(shí)在進(jìn)行父容器的滑動(dòng)方向(這里是水平滑動(dòng))梅肤,但是在水平滑動(dòng)之前如果用戶再迅速進(jìn)行豎直滑動(dòng),就會(huì)導(dǎo)致界面在水平方向無(wú)法滑動(dòng)到終點(diǎn)從而處于一種中間狀態(tài)糯彬。為了避免這種情況凭语,當(dāng)水平滑動(dòng)時(shí),下一個(gè)序列的點(diǎn)擊事件仍然交給父容器處理(哪怕豎直方向滑動(dòng)距離大于水平方向滑動(dòng)距離撩扒,此時(shí)仍然判定是水平滑動(dòng))似扔。
注解3:
這一塊代碼是解決滑動(dòng)沖突的關(guān)鍵。在MOVE事件中搓谆,判斷這一滑動(dòng)事件是水平滑動(dòng)還是豎直滑動(dòng)炒辉,如果是水平滑動(dòng),作為父容器就攔截事件泉手,如果是水平滑動(dòng)黔寇,就不攔截事件,交給子view去處理斩萌。
此處的困惑:如果在一系列的MOVE事件中缝裤,前部分是水平移動(dòng),后部分是豎直移動(dòng)的颊郎,那怎么辦憋飞?因?yàn)闆]有攔截DOWN事件,所有很有可能事件攔截過程中的mFirstTouchTarget != null姆吭,所以后部分的MOVE事件仍然要調(diào)用onInterceptTouchEvent()榛做,此時(shí),intercept = false;,那么接著交給子View處理检眯?
如何解答這個(gè)困惑呢厘擂?有一個(gè)結(jié)論是:

一旦父容器開始攔截任何一個(gè)事件,那么后續(xù)的事件都會(huì)交給它來(lái)處理锰瘸。

這個(gè)結(jié)論先記住吧刽严,暫時(shí)還沒有搞明白為什么會(huì)這樣。
注解4:
如果父容器在UP事件中返回了true获茬,就會(huì)導(dǎo)致子View無(wú)法接受到UP事件港庄,這個(gè)時(shí)候子元素中的onClick事件就無(wú)法處罰法。同樣的恕曲,

因?yàn)橐坏└溉萜鏖_始攔截任何一個(gè)事件鹏氧,那么后續(xù)的事件都會(huì)交給它來(lái)處理,所以UP作為最后一個(gè)事件也必定可以傳遞給父容器佩谣,即便父容器的onInterceptTouchEvent方法在UP時(shí)返回了false

但是依然沒有弄明白為什么有這個(gè)結(jié)論把还。

內(nèi)部攔截法

內(nèi)部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素茸俭,如果子元素需要此事件就直接消耗掉吊履,否則就交由父容器進(jìn)行處理。這種方法需要配合requestDisallowInterceptTouchEvent()方法才能正常工作调鬓。
第一步艇炎,修改父容器的onInterceptTouchEvent(),讓其在DOWN事件返回false腾窝,其他情況下返回true缀踪。

//父容器內(nèi)
public boolean onInterceptTouchEvent(MotionEvent event){
      int x = (int)event.getX();
      int y = (int)event.getY();
      int action = event.getAction();
      if (action == MotionEvent.ACTION_DOWN){                  //注解5
            mLastX = x;
            mLastY = y;
            if (!mScroller.isFinished()){
                  mScroller.abortAnimation()
                  return true;
            }
            return true;
      } else {
            return true;
      }
 }

注解5:
父容器攔截了除了DOWN事件以外的其他事件,這樣當(dāng)子元素調(diào)用parent.requestDisallowInterceptTouchEvent(false)方法時(shí)虹脯,父元素才能繼續(xù)攔截所需的事件驴娃。
第二步,修改子元素的dispatchTouchEvent()方法

//在子View中
public boolean dispatchTouchEvent(MotionEvent event){
      int x = (int) event.getX();
      int y = (int) event.getY();
      switch(event.getAction()){
      case MotionEvent.ACTION_DOWN:
            XXX.requestDisallowInterceptTouchEvent(true);
            break;
      case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if (Math.abs(deltaX) > Math.abs(deltaY)){                              //在這里的if中加入是否攔截的判斷
                  XXX.requestDisallowInterceptTouchEvent(false);
            }
            break;
      case MotionEvent.ACTION_UP:
            break;
      default:
            break;
      }
      mLastX = x;
      mLastY = y;
      return super.dispatchTouchEvent(event);               //注解6
}

注解6:因?yàn)樽觱iew是自定義view循集,重寫的dispatchTouchEvent()方法唇敞,在解決了滑動(dòng)沖突后,調(diào)用父類的dispatchTouchEvent()方法來(lái)進(jìn)行原來(lái)的事件分發(fā)咒彤。

場(chǎng)景二和場(chǎng)景三

在總體的實(shí)現(xiàn)方法和場(chǎng)景一是一樣的疆柔,僅僅是在MOVE事件中判斷的條件不一樣,場(chǎng)景一僅僅是通過滑動(dòng)方向來(lái)進(jìn)行判斷镶柱,而場(chǎng)景二和場(chǎng)景三需要判斷業(yè)務(wù)邏輯婆硬。這里就不詳細(xì)介紹了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奸例,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌查吊,老刑警劉巖谐区,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逻卖,居然都是意外死亡宋列,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門评也,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)炼杖,“玉大人,你說(shuō)我怎么就攤上這事盗迟±ば埃” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵罚缕,是天一觀的道長(zhǎng)艇纺。 經(jīng)常有香客問我,道長(zhǎng)邮弹,這世上最難降的妖魔是什么黔衡? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮腌乡,結(jié)果婚禮上盟劫,老公的妹妹穿的比我還像新娘。我一直安慰自己与纽,他們只是感情好侣签,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著渣锦,像睡著了一般硝岗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袋毙,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天型檀,我揣著相機(jī)與錄音,去河邊找鬼听盖。 笑死胀溺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皆看。 我是一名探鬼主播仓坞,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腰吟!你這毒婦竟也來(lái)了无埃?” 一聲冷哼從身側(cè)響起徙瓶,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嫉称,沒想到半個(gè)月后侦镇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡织阅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年壳繁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荔棉。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闹炉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出润樱,到底是詐尸還是另有隱情渣触,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布祥国,位于F島的核電站昵观,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舌稀。R本人自食惡果不足惜啊犬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望壁查。 院中可真熱鬧觉至,春花似錦、人聲如沸睡腿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)席怪。三九已至应闯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挂捻,已是汗流浹背碉纺。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刻撒,地道東北人骨田。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像声怔,于是被迫代替她去往敵國(guó)和親态贤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容