Android 經(jīng)典筆記之四: 事件沖突解決思路與方案

事件沖突解決思路與方案
目錄介紹
1.事件機(jī)制簡單介紹
1.1 觸摸事件
1.2 分發(fā)事件
1.3 攔截事件

2.解決滑動沖突的思路及方法
2.1 第一種情況肩袍,滑動方向不同
2.2 第二種情況安拟,滑動方法相同
2.3 第三種情況录别,以上兩種情況嵌套

3.案例解決方法
3.1 針對2問題的解決思路
3.2 滑動方向不同界赔,解決沖突的外部解決法
3.3 滑動方向不同伴逸,解決沖突的內(nèi)部解決法
3.4 ViewPager嵌套ViewPager內(nèi)部解決法
3.5 滑動方向相同侨歉,解決沖突的外部解決法
3.6 解決ScrollView和ViewPager,RecycleView滑動沖突

好消息

  • 博客筆記大匯總【16年3月到至今】攻锰,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客熟吏,Python學(xué)習(xí)筆記等等距糖,還包括平時開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題牵寺,長期更新維護(hù)并且修正悍引,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客帽氓,從12年起趣斤,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處黎休,謝謝浓领!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下势腮,謝謝联贩!當(dāng)然也歡迎提出建議,萬事起于忽微捎拯,量變引起質(zhì)變撑蒜!

Demo
https://github.com/yangchong211/YCEventConflict

1.事件機(jī)制簡單介紹
1.1 觸摸事件

/**
* 觸摸事件
* 如果返回結(jié)果為false表示不消費(fèi)該事件,并且也不會截獲接下來的事件序列,事件會繼續(xù)傳遞
* 如果返回為true表示當(dāng)前View消費(fèi)該事件座菠,阻止事件繼續(xù)傳遞
*
* 在這里要強(qiáng)調(diào)View的OnTouchListener狸眼。如果View設(shè)置了該監(jiān)聽,那么OnTouch()將會回調(diào)浴滴。
* 如果返回為true那么該View的OnTouchEvent將不會在執(zhí)行 這是因為設(shè)置的OnTouchListener執(zhí)行時的優(yōu)先級要比onTouchEvent高拓萌。
* 優(yōu)先級:OnTouchListener > onTouchEvent > onClickListener
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.e("onEvent","MyLinearLayout onTouchEvent");
    return super.onTouchEvent(event);
}

1.2 分發(fā)事件

/**
* 分發(fā)事件
* 根據(jù)內(nèi)部攔截狀態(tài),向其child或者自己分發(fā)事件
*
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.e("onEvent","MyLinearLayout dispatchTouchEvent");
    return super.dispatchTouchEvent(ev);
}

1.3攔截事件

/**
* 攔截事件
* 默認(rèn)實現(xiàn)是返回false升略,也就是默認(rèn)不攔截任何事件
*
* 判斷自己是否需要截取事件
* 如果該方法返回為true微王,那么View將消費(fèi)該事件,即會調(diào)用onTouchEvent()方法
* 如果返回false,那么通過調(diào)用子View的dispatchTouchEvent()將事件交由子View來處理
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.e("onEvent","MyLinearLayout onInterceptTouchEvent");
    return super.onInterceptTouchEvent(ev);
}

2.解決滑動沖突的思路及方法
2.1 第一種情況品嚣,滑動方向不同

Image.png

2.2 第二種情況炕倘,滑動方法相同
Image.png

2.3 第三種情況,以上兩種情況嵌套
Image.png

3.案例解決方法
3.1 針對2問題的解決思路

看了上面三種情況翰撑,我們知道他們的共同特點是 父View 和 子View 都想爭著響應(yīng)我們的觸摸事件罩旋,但遺憾的是我們的觸摸事件 同一時刻 只能被某一個View或者ViewGroup攔截消費(fèi),所以就產(chǎn)生了滑動沖突眶诈?那既然同一時刻只能由某一個View或者ViewGroup消費(fèi)攔截涨醋,那我們就只需要 決定在某個時刻由這個View或者ViewGroup攔截事件,另外的 某個時刻由 另外一個View或者ViewGroup攔截事件不就OK了嗎逝撬?綜上浴骂,正如 在 《Android開發(fā)藝術(shù)》 一書提出的,總共 有兩種解決方案

3.2 滑動方向不同宪潮,解決沖突的外部解決法【以ScrollView與ViewPager為例

舉例子:以ScrollView與ViewPager為例
從 父View 著手溯警,重寫 onInterceptTouchEvent 方法,在 父View 需要攔截的時候攔截狡相,不要的時候返回false梯轻,代碼大概如下

public class MyScrollView extends ScrollView {

    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);
    }

    @TargetApi(21)
    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private float mDownPosX = 0;
    private float mDownPosY = 0;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        final int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownPosX = x;
                mDownPosY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                final float deltaX = Math.abs(x - mDownPosX);
                final float deltaY = Math.abs(y - mDownPosY);
                // 這里是夠攔截的判斷依據(jù)是左右滑動,讀者可根據(jù)自己的邏輯進(jìn)行是否攔截
                if (deltaX > deltaY) {
                    return false;
                }
        }
        return super.onInterceptTouchEvent(ev);
    }
}

3.3 滑動方向不同谣光,解決沖突的內(nèi)部解決法【以ScrollView與ViewPager為例

從子View著手檩淋,父View 先不要攔截任何事件芬为,所有的 事件傳遞給 子View萄金,如果 子View 需要此事件就消費(fèi)掉,不需要此事件的話就交給 父View 處理媚朦。
實現(xiàn)思路 如下氧敢,重寫 子View 的 dispatchTouchEvent 方法,在 Action_down 動作中通過方法 requestDisallowInterceptTouchEvent(true) 先請求 父View 不要攔截事件询张,這樣保證 子View 能夠接受到Action_move事件孙乖,再在Action_move動作中根據(jù) 自己的邏輯是否要攔截事件,不要的話再交給 父View 處理

public class MyViewPager extends ViewPager {

    private static final String TAG = "yc";

    int lastX = -1;
    int lastY = -1;

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getRawX();
        int y = (int) ev.getRawY();
        int dealtX = 0;
        int dealtY = 0;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                dealtX = 0;
                dealtY = 0;
                // 保證子View能夠接收到Action_move事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                dealtX += Math.abs(x - lastX);
                dealtY += Math.abs(y - lastY);
                Log.i(TAG, "dealtX:=" + dealtX);
                Log.i(TAG, "dealtY:=" + dealtY);
                // 這里是夠攔截的判斷依據(jù)是左右滑動,讀者可根據(jù)自己的邏輯進(jìn)行是否攔截
                if (dealtX >= dealtY) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

3.4 ViewPager嵌套ViewPager內(nèi)部解決法

從 子View ViewPager著手唯袄,重寫 子View 的 dispatchTouchEvent方法弯屈,在 子View 需要攔截的時候進(jìn)行攔截,否則交給 父View 處理恋拷,代碼如下

public class ChildViewPager extends ViewPager {

    private static final String TAG = "yc";
    public ChildViewPager(Context context) {
        super(context);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int curPosition;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                curPosition = this.getCurrentItem();
                int count = this.getAdapter().getCount();
                Log.i(TAG, "curPosition:=" +curPosition);
                // 當(dāng)當(dāng)前頁面在最后一頁和第0頁的時候资厉,由父親攔截觸摸事件
                if (curPosition == count - 1|| curPosition==0) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                } else {//其他情況,由孩子攔截觸摸事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
        }
        return super.dispatchTouchEvent(ev);
    }
}

3.5 滑動方向相同蔬顾,解決沖突的外部解決法【解決ScrollView和RecycleView滑動沖突】

public class RecyclerScrollview extends ScrollView {

    private int downY;
    private int mTouchSlop;

    public RecyclerScrollview(Context context) {
        super(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public RecyclerScrollview(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public RecyclerScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downY = (int) e.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int moveY = (int) e.getRawY();
                if (Math.abs(moveY - downY) > mTouchSlop) {
                    return true;
                }
        }
        return super.onInterceptTouchEvent(e);
    }

}

**注意:RecycleView一定要被嵌套里面**
<!-- descendantFocusability該屬性是當(dāng)一個為view獲取焦點時宴偿,定義viewGroup和其子控件兩者之間的關(guān)系。
beforeDescendants:viewgroup會優(yōu)先其子類控件而獲取到焦點
afterDescendants:viewgroup只有當(dāng)其子類控件不需要獲取焦點時才獲取焦點
blocksDescendants:viewgroup會覆蓋子類控件而直接獲得焦點-->

<RelativeLayout
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:descendantFocusability="blocksDescendants">
<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</RelativeLayout>

**3.6 解決ScrollView和ViewPager,RecycleView滑動沖突**

對于ScrollView
public class BottomScrollView extends ScrollView {

    private OnScrollToBottomListener mOnScrollToBottomListener;

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

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public BottomScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt){
        super.onScrollChanged(l,t,oldl,oldt);
        // 滑動的距離加上本身的高度與子View的高度對比
        if(t + getHeight() >=  getChildAt(0).getMeasuredHeight()){
            // ScrollView滑動到底部
            if(mOnScrollToBottomListener != null) {
                mOnScrollToBottomListener.onScrollToBottom();
            }
        } else {
            if(mOnScrollToBottomListener != null) {
                mOnScrollToBottomListener.onNotScrollToBottom();
            }
        }
    }

    public void setScrollToBottomListener(OnScrollToBottomListener listener) {
        this.mOnScrollToBottomListener = listener;
    }

    public interface OnScrollToBottomListener {
        void onScrollToBottom();
        void onNotScrollToBottom();
    }
}

**// ViewPager滑動沖突解決**
mViewPager.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        if(action == MotionEvent.ACTION_DOWN) {
            // 記錄點擊到ViewPager時候诀豁,手指的X坐標(biāo)
            mLastX = event.getX();
        }
        if(action == MotionEvent.ACTION_MOVE) {
            // 超過閾值窄刘,禁止SwipeRefreshLayout下拉刷新,禁止ScrollView截斷點擊事件
            if(Math.abs(event.getX() - mLastX) > THRESHOLD_X_VIEW_PAGER) {
                mRefreshLayout.setEnabled(false);
                mScrollView.requestDisallowInterceptTouchEvent(true);
            }
        }
        // 用戶抬起手指舷胜,恢復(fù)父布局狀態(tài)
        if(action == MotionEvent.ACTION_UP) {
            mRefreshLayout.setEnabled(true);
            mScrollView.requestDisallowInterceptTouchEvent(false);
        }
        return false;
    }
});

**// ListView滑動沖突解決**
mListView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        if(action == MotionEvent.ACTION_DOWN) {
            mLastY = event.getY();
        }
        if(action == MotionEvent.ACTION_MOVE) {
            int top = mListView.getChildAt(0).getTop();
            float nowY = event.getY();
            if(!isSvToBottom) {
                // 允許scrollview攔截點擊事件, scrollView滑動
                mScrollView.requestDisallowInterceptTouchEvent(false);
            } else if(top == 0 && nowY - mLastY > THRESHOLD_Y_LIST_VIEW) {
                // 允許scrollview攔截點擊事件, scrollView滑動
                mScrollView.requestDisallowInterceptTouchEvent(false);
            } else {
                // 不允許scrollview攔截點擊事件娩践, listView滑動
                mScrollView.requestDisallowInterceptTouchEvent(true);
            }
        }
        return false;
    }
});

后續(xù):
平時喜歡寫寫文章,筆記逞带。別人建議我把筆記欺矫,以前寫的東西整理,然后寫成博客展氓,所以我會陸續(xù)整理文章穆趴,只發(fā)自己寫的東西,敬請期待:
知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
領(lǐng)英:https://www.linkedin.com/in/chong-yang-049216146/
簡書:http://www.reibang.com/u/b7b2c6ed9284
csdn:http://my.csdn.net/m0_37700275
網(wǎng)易博客:http://yangchong211.blog.163.com/
新浪博客:http://blog.sina.com.cn/786041010yc
github:https://github.com/yangchong211
喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
脈脈:yc930211
開源中國:https://my.oschina.net/zbj1618/blog
郵箱:yangchong211@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遇汞,一起剝皮案震驚了整個濱河市未妹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌空入,老刑警劉巖络它,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歪赢,居然都是意外死亡化戳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門埋凯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來点楼,“玉大人,你說我怎么就攤上這事白对÷永” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵甩恼,是天一觀的道長蟀瞧。 經(jīng)常有香客問我沉颂,道長,這世上最難降的妖魔是什么悦污? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任铸屉,我火速辦了婚禮,結(jié)果婚禮上切端,老公的妹妹穿的比我還像新娘抬探。我一直安慰自己,他們只是感情好帆赢,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布小压。 她就那樣靜靜地躺著,像睡著了一般椰于。 火紅的嫁衣襯著肌膚如雪怠益。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天瘾婿,我揣著相機(jī)與錄音蜻牢,去河邊找鬼。 笑死偏陪,一個胖子當(dāng)著我的面吹牛抢呆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笛谦,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抱虐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饥脑?” 一聲冷哼從身側(cè)響起恳邀,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灶轰,沒想到半個月后谣沸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡笋颤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年乳附,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伴澄。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡赋除,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秉版,到底是詐尸還是另有隱情贤重,我是刑警寧澤茬祷,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布清焕,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秸妥。R本人自食惡果不足惜滚停,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粥惧。 院中可真熱鬧键畴,春花似錦、人聲如沸突雪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咏删。三九已至惹想,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間督函,已是汗流浹背嘀粱。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留辰狡,地道東北人锋叨。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像宛篇,于是被迫代替她去往敵國和親娃磺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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