如何成為自定義高手(七)滑動沖突

View的滑動沖突場景

常見的滑動沖突可以簡單分為如下三種

  • 場景1:外部滑動方向和內(nèi)部滑動方向不一致
  • 場景2:外部滑動方向和內(nèi)部滑動方向一致
  • 場景3:上面兩種情況的嵌套


    滑動沖突.png

View的滑動沖突解決方式

  1. 外部攔截法:在onInterceptTouchEvvent方法中若债,首先在ACTION_DOWN這個(gè)事件以躯,父容器必須放回false报嵌,即不攔截ACTION_DOWN事件这难,這是因?yàn)橐坏└溉萜鲾r截了ACTION_DOWN,那么后續(xù)的ACTION_MOVE和ACTION_UP事件直接傳遞給父容器處理,沒法傳遞給子元素副瀑。
  2. 內(nèi)部攔截法:父容器不攔截任何事件,所有的事情都傳遞給子元素恋谭。如果子元素需要此事件就直接消耗掉糠睡,否則就交由父容器進(jìn)行處理。如下代碼是內(nèi)部攔截法的典型代碼疚颊,除了子元素需要做處理以外狈孔,父元素也要默認(rèn)攔截除了ACTION_DOWN以外的其他事件。

場景1的沖突材义,利用外部攔截法解決

1. 外部攔截法常規(guī)解決思路均抽,重寫onInterceptTouchEvent方法。
  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要當(dāng)前點(diǎn)擊事件){
                    intercept = true;
                }else{
                    intercept = false;
                }
               break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                break;
        }
        return intercept;
    }
2. 場景1沖突實(shí)例

一個(gè)可以水平滑動的HorizontalScrollView和一個(gè)垂直滑動的ListView就會產(chǎn)生滑動沖突其掂。其實(shí)在ViewPager+ListView是不會有滑動沖突的油挥,因?yàn)閂iewPager內(nèi)部已經(jīng)解決。
思路:判斷水平滑動還是垂直滑動款熬,可以通過在水平方向上deltaX和垂直方向上deltaY的絕對比較就可以明白用戶想要水平還是垂直滑動喘漏。
自定義MyViewPager代碼

public class MyViewPager extends HorizontalScrollView {

    private boolean intercept;
    private float lastX,lastY,x,y;
    private float deltaX,deltaY;
    private float totalX = 0;
    private static final String TAG = "MyViewPager";
    private LinearLayout mContentLL;
    public MyViewPager(Context context) {
        this(context,null);
    }

    public MyViewPager(Context context, AttributeSet attrs) {

        this(context, attrs,0);
    }

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentLL = findViewById(R.id.contentLayout);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                lastX = ev.getX();
                lastY = ev.getY();
                Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                x = ev.getX();
                y = ev.getY();
                deltaX = x - lastX;
                deltaY = y - lastY;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE: deltX =  " + deltaX + " , deltY = " + deltaY);
                if(Math.abs(deltaX) > Math.abs(deltaY)){//水平滑動 , 當(dāng)前父ViewGroup攔截后交給onTouchEvent去處理具體的操作
                    intercept = true;
                }else{
                    intercept = false;
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                break;
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                x = ev.getX();
                deltaX = x - lastX;
                totalX += deltaX;
                Log.i(TAG, "onTouchEvent: totalX = " +  totalX + " , deltaX = " + deltaX);
                if(mContentLL != null){
                    mContentLL.scrollBy((int) -deltaX,0);
                }
                lastX = x;
                break;
        }
        return super.onTouchEvent(ev);
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager
        android:layout_width="1500dp"
        android:layout_height="match_parent"
        >
        <LinearLayout
            android:id="@+id/contentLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <ListView
                android:id="@+id/listView1"
                android:layout_width="500dp"
                android:layout_height="match_parent"/>
            <ImageView
                android:layout_width="500dp"
                android:layout_height="match_parent"
                android:background="@mipmap/bg"/>
            <ListView
                android:id="@+id/listView2"
                android:layout_width="500dp"
                android:layout_height="match_parent"/>
        </LinearLayout>
    </com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager>
</android.support.constraint.ConstraintLayout>

完美解決沖突


外部滑動方向和內(nèi)部滑動方向不一致.gif

場景2的沖突,利用內(nèi)部攔截法解決

1. 內(nèi)部攔截法解決思路华烟,重寫子元素的dispatchTouchEvent方法,和父元素的onInterceptTouchEvent方法持灰。

父元素的onInterceptTouchEvent盔夜。ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)記位的控制,所以一旦父容器攔截ACTION_DOWN事件堤魁,那么所有的事件都無法傳遞到子元素中去喂链,這樣內(nèi)部攔截就無法起作用。

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            return false;
        }else {
            return true;
        }
    }

子元素的onInterceptTouchEvent妥泉。內(nèi)部攔截法的典型代碼椭微,面對不同的滑動策略時(shí)只需要修改里面的條件即可。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要此類事件){//事件處理交個(gè)父容器
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

2. 場景2沖突實(shí)例

一個(gè)可以垂直滑動的MyVerticalViewGroup盲链,內(nèi)部包含一個(gè)可以垂直滑動的MyRecyclerView蝇率,兩者都可以垂直滑動,需要根據(jù)自己需求判斷垂直滑動時(shí)到底滑動哪一個(gè)控件刽沾。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup
        android:id="@+id/contentLayout"
        android:layout_width="match_parent"
        android:layout_height="1000dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageView"
            android:layout_marginTop="-200dp"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@mipmap/bg"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#789933"/>

        <com.example.chenpeng.julyapplication.SlidingConflict.MyRecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="800dp"/>

    </com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup>

</android.support.constraint.ConstraintLayout>

自定義MyVerticalViewGroup

public class MyVerticalViewGroup extends LinearLayout {

    private LinearLayout mContentll;
    private float y,lastY,deltaY;
    private int mImageViewHeight;
    private static final String TAG = "SlidingView03";


    public MyVerticalViewGroup(Context context) {
        this(context,null);
    }

    public MyVerticalViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentll = findViewById(R.id.contentLayout);
        mImageViewHeight = (int) (200 * getResources().getDisplayMetrics().density+0.5);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            lastY = ev.getY();
            return false;
        }else {
            lastY = ev.getY();
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {//父容器的事件處理
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastY = ev.getY();
                Log.i(TAG, "parent onTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                y = ev.getY();
                deltaY = y - lastY;
                if(mContentll != null){
                    mContentll.scrollBy(0, -(int) deltaY);
                }
                lastY = y;
                Log.i(TAG, "parent onTouchEvent: ACTION_MOVE ACTION_UP");
                break;
        }
        return super.onTouchEvent(ev);
    }

    @Override
    public void scrollTo(int x, int y) {
        //邊界控制
        if(y > 0 ){
            y = 0;
        }
        if( y < 0 && Math.abs(y) > mImageViewHeight){
             y = -mImageViewHeight;
        }
        super.scrollTo(x, y);
    }
}

自定義MyRecyclerView

public class MyRecyclerView extends RecyclerView {


    private static final String TAG = "SlidingView03";
    private float y,lastY,deltaY;


    public MyRecyclerView(Context context) {
        this(context,null);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }



    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getY();
                deltaY = y - lastY;//deltaY > 0 表示向下滑動 本慕; deltaY < 0 表示向上滑動
                lastY = y;
                boolean isRecyclerViewOnTop = isTop();
                Log.i(TAG, "child dispatchTouchEvent: deltaY = " + deltaY + " ,isTop() =  " + isRecyclerViewOnTop + ", ViewCompat.canScrollVertically(this, 1) = " + ViewCompat.canScrollVertically(this, 1));
                if(( isRecyclerViewOnTop && deltaY > 0 ) || ( deltaY < 0  && !ViewCompat.canScrollVertically(this, 1))){//事件處理交個(gè)父容器 -1判讀是否可以下滑 1判斷是否可以上滑
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
//                Log.i(TAG, "onTouchEvent: ACTION_DOWN");
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getY();
                deltaY = y - lastY;
                lastY = y;
//                Log.i(TAG, "onTouchEvent: ACTION_MOVE totalY = " + totalY + " , y = " + y + " ,lastY = " + lastY);
                break;
            case MotionEvent.ACTION_UP:
//                Log.i(TAG, "onTouchEvent: ACTION_UP");
                lastY = ev.getY();
                break;

        }
        return super.onTouchEvent(ev);
    }

    public boolean isTop(){
        LinearLayoutManager layoutManager = (LinearLayoutManager) this.getLayoutManager();
        int position = layoutManager.findFirstVisibleItemPosition();
        View firstVisibleChildView = layoutManager.findViewByPosition(position);
        Log.i(TAG, "isTop: position = 0" +" , firstVisibleChildView.getTop() = " + firstVisibleChildView.getTop() + ",RecyclerView " + getScrollY() );
        if(position == 0 && firstVisibleChildView.getTop() == 0){
            return true;
        }else{
            return false;
        }
    }
}

實(shí)例演示

SlidingConfict2.gif

總結(jié)

如何成為自定義高手(一)繪制
如何成為自定義高手(二)動畫
如何成為自定義高手(三)布局
如何成為自定義高手(四)觸摸反饋顿乒,事件分發(fā)機(jī)制
如何成為自定義高手(五)多點(diǎn)觸摸
如何成為自定義高手(六)滑動和拖拽
如何成為自定義高手(七)滑動沖突
利用Android自帶嵌套滑動控件解決滑動沖突(NestingScroll,CoordinatorLayout與Behavior)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末议街,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子淆游,更是在濱河造成了極大的恐慌傍睹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犹菱,死亡現(xiàn)場離奇詭異拾稳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)腊脱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門访得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陕凹,你說我怎么就攤上這事悍抑。” “怎么了杜耙?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵搜骡,是天一觀的道長。 經(jīng)常有香客問我佑女,道長记靡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任团驱,我火速辦了婚禮摸吠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚎花。我一直安慰自己寸痢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布紊选。 她就那樣靜靜地躺著啼止,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兵罢。 梳的紋絲不亂的頭發(fā)上族壳,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音趣些,去河邊找鬼仿荆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拢操。 我是一名探鬼主播锦亦,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼令境!你這毒婦竟也來了杠园?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舔庶,失蹤者是張志新(化名)和其女友劉穎抛蚁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惕橙,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞧甩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弥鹦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肚逸。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彬坏,靈堂內(nèi)的尸體忽然破棺而出朦促,到底是詐尸還是另有隱情,我是刑警寧澤栓始,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布务冕,位于F島的核電站,受9級特大地震影響幻赚,放射性物質(zhì)發(fā)生泄漏禀忆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一坯屿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巍扛,春花似錦领跛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胧瓜,卻和暖如春矢棚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背府喳。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工蒲肋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓兜粘,卻偏偏與公主長得像申窘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子孔轴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

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