滑动冲突

在读完了上一节 

android事件分发(三)重要的函数requestDisallowInterceptTouchEvent

之后,可能有点意犹未尽的感觉,这一篇将说明requestDisallowInterceptTouchEvent在解决滑动冲突时的巨大作用。 在有多个滚动控件的时候常常提到滑动冲突,那什么是滑动冲突,又如何解决呢?为什么原生的控件一般都不存在滑动冲突的问题?这就是本文要说的故事

滑动冲突概念

什么是滑动冲突,比如我有一个listview,还有一个viewpager,listview可以纵向滑动,viewpager可以横向滑动,我在viewpager上横向滑了一大段,在这个滑动过程中,很可能我们的手不仅仅横向滑了,纵向也滑了,谁也无法保证自己滑的是一条直线,很可能是横向滑了100dp,纵向抖动了3dp,此时理想的情况是只有viewpager滑动,listview不要瞎JB乱动,但是现实往往是残酷的,viewpager动了,listview也动了,这就是滑动冲突。我们想要的效果是如果viewpager开始滑了,那么listview就不准滑,除非手指抬起再按下,也就是说在一个cycle内,listview不准滑,这让我们想起了上节 的requestDisallowInterceptTouchEvent,简直是量身定做的啊。

滑动冲突举例

下面用一个例子来说明如何解决滑动冲突,我们有个listview,listview内的item是可滑动的,左划会出现删除按钮,效果如下,存在滑动冲突




代码非常简单,工程是ScrollConflict

package com.fish.scrollconflict;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;

import java.util.List;

public class MyBaseAdapter extends BaseAdapter {

    private Context mContext;
    private List<String> mData;
    private LayoutInflater mInflater;

    public static final class ViewHolder {

        public TextView textView;
        public Button btn;
    }

    public MyBaseAdapter(Context context, List<String> data) {
        mContext = context;
        mData = data;
        this.mInflater = LayoutInflater.from(context);
    }


    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int arg0) {
        return mData.get(arg0);
    }

    @Override
    public long getItemId(int arg0) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

//        LogUtil.fish("getview "+position);
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.item, parent, false);

            holder.textView = (TextView) convertView.findViewById(R.id.tv);
            holder.btn = (Button) convertView.findViewById(R.id.btn);
            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.textView.setText(mData.get(position));
//        holder.textView.setText((String) mData.get(position).get("textView"));
        //给每一个列表后面的按钮添加响应事件
        holder.btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                showInfo();
            }
        });


        return convertView;
    }


}

package com.fish.scrollconflict;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

/**
 * Created by fish on 16/6/28.
 */
public class ScrollLayout extends LinearLayout {

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

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

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

    TextView tv;
    Button btn;
    float mLastX;
    float mLastY;
    boolean isScrolling;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        tv = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = event.getX();
                mLastY = event.getY();
                isScrolling = false;
                return true;
            case MotionEvent.ACTION_MOVE:

                //左划,应该为负的
                float xDiff = event.getX() - mLastX;
    
                mLastX = event.getX();
                LinearLayout.LayoutParams params = (LayoutParams) tv.getLayoutParams();
                params.leftMargin += xDiff;
                if (params.leftMargin > 0) {
                    params.leftMargin = 0;
                }
                tv.setLayoutParams(params);
                return true;
        }
        return super.onTouchEvent(event);
    }
}

<?xml version="1.0" encoding="utf-8"?>
<com.fish.scrollconflict.ScrollLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="350dp"
    android:layout_height="100dp"
    android:gravity="center_vertical"
    android:background="#44ff0000">

    <TextView
        android:textSize="20sp"
        android:id="@+id/tv"
        android:layout_width="350dp"
        android:layout_height="wrap_content"
        android:text="Hello 我是无辜的!" />

    <Button
        android:textSize="20sp"
        android:id="@+id/btn"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="删除" />
</com.fish.scrollconflict.ScrollLayout>
很明显上图中存在滑动冲突,那么怎么解决呢?
很简单,在onTouchEvent处理move事件的时候,调用一遍getParent.requestDisallowInterceptTouchEvent(true),告诉父亲,这是我爱吃的,你以后不准抢,这样listview就不会来拦截这个事件了,修改部分如下,
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = event.getX();
                mLastY = event.getY();
                isScrolling = false;
                return true;
            case MotionEvent.ACTION_MOVE:

                //左划,应该为负的
                float xDiff = event.getX() - mLastX;
                float yDiff = event.getY() - mLastY;
                if (Math.abs(xDiff) > Math.abs(yDiff)) {
                    isScrolling = true;
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                mLastX = event.getX();
                LinearLayout.LayoutParams params = (LayoutParams) tv.getLayoutParams();
                params.leftMargin += xDiff;
                if (params.leftMargin > 0) {
                    params.leftMargin = 0;
                }
                tv.setLayoutParams(params);
                return true;
        }
        return super.onTouchEvent(event);
    }
非常简单就解决了滑动冲突,看下图,可以发现,不会有滑动冲突了,listview不会乱滚了

viewpager在listview内会不会滑动冲突

经常看到有人说viewpager放在listview里会产生滑动冲突,还有人给出了若干解决方案,我自己写了个试了一下,发现没有冲突啊?到底是他们错了,还是我错了呢?

原来低版本的时候的确会有冲突,高版本修复了这个问题

这是4.4_r1的Viewpager的onTouchEvent的处理move的代码,在supportv4包内,

    case MotionEvent.ACTION_MOVE:
         if (!mIsBeingDragged) {
             final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
             final float x = MotionEventCompat.getX(ev, pointerIndex);
             final float xDiff = Math.abs(x - mLastMotionX);
             final float y = MotionEventCompat.getY(ev, pointerIndex);
             final float yDiff = Math.abs(y - mLastMotionY);
             if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
             if (xDiff > mTouchSlop && xDiff > yDiff) {
                 if (DEBUG) Log.v(TAG, "Starting drag!");
                 mIsBeingDragged = true;
                 requestParentDisallowInterceptTouchEvent(true);
                 mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
                         mInitialMotionX - mTouchSlop;
                 mLastMotionY = y;
                 setScrollState(SCROLL_STATE_DRAGGING);
                 setScrollingCacheEnabled(true);


                 // Disallow Parent Intercept, just in case
                 ViewParent parent = getParent();
                 if (parent != null) {
                     parent.requestDisallowInterceptTouchEvent(true);
                 }
             }
         }
         // Not else! Note that mIsBeingDragged can be set above.
         if (mIsBeingDragged) {
             // Scroll to follow the motion event
             final int activePointerIndex = MotionEventCompat.findPointerIndex(
                     ev, mActivePointerId);
             final float x = MotionEventCompat.getX(ev, activePointerIndex);
             needsInvalidate |= performDrag(x);
         }
         break;

而上一个版本4.3.1_r1,
 case MotionEvent.ACTION_MOVE:
               if (!mIsBeingDragged) {
                   final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                   final float x = MotionEventCompat.getX(ev, pointerIndex);
                   final float xDiff = Math.abs(x - mLastMotionX);
                   final float y = MotionEventCompat.getY(ev, pointerIndex);
                   final float yDiff = Math.abs(y - mLastMotionY);
                   if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
                   if (xDiff > mTouchSlop && xDiff > yDiff) {
                       if (DEBUG) Log.v(TAG, "Starting drag!");
                       mIsBeingDragged = true;
                       mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
                               mInitialMotionX - mTouchSlop;
                       mLastMotionY = y;
                       setScrollState(SCROLL_STATE_DRAGGING);
                       setScrollingCacheEnabled(true);
                   }
               }
               // Not else! Note that mIsBeingDragged can be set above.
               if (mIsBeingDragged) {
                   // Scroll to follow the motion event
                   final int activePointerIndex = MotionEventCompat.findPointerIndex(
                           ev, mActivePointerId);
                   final float x = MotionEventCompat.getX(ev, activePointerIndex);
                   needsInvalidate |= performDrag(x);
               }
               break;

可以明显看到,高版本调用了  requestParentDisallowInterceptTouchEvent(true);,而requestParentDisallowInterceptTouchEvent代码如下,就是掉parent的requestDisallowInterceptTouchEvent,不许父亲吃我的事件
    private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
        final ViewParent parent = getParent();
        if (parent != null) {
            parent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

所以我们使用高版本的v4包或者v7包,就不用考虑滑动冲突的问题,ohlala。

原生控件如何解决滑动冲突

我再看了下ScrollView

     if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    mIsBeingDragged = true;
                    if (deltaY > 0) {
                        deltaY -= mTouchSlop;
                    } else {
                        deltaY += mTouchSlop;
                    }
                }
 
 再看下HorizontalScrollView 
               if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    mIsBeingDragged = true;
                    if (deltaX > 0) {
                        deltaX -= mTouchSlop;
                    } else {
                        deltaX += mTouchSlop;
                    }
                }
 
 


看来原生控件都是类似的,在onTouchEvent的move里判断是否是我的滑动事件,如果是,那就拦截,条件一般就是Math.abs(deltaY) > mTouchSlop

所以大部分原生控件其实都已经解决了滑动冲突的问题,但是我们自己写的控件,或者说github上的一些控件经常有滑动冲突的问题需要解决,如何解决呢?完全可以参考原生控件的方法。


总结

原生控件基本上都解决了滑动冲突问题,但是我们自己写的控件或者github上的控件很多都没解决,需要我们自己解决,解决方法可就是 在onTouchEvent里面处理


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值