您的位置:首页 > 其它

滑动冲突

2016-06-28 23:35 183 查看
在读完了上一节 


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里面处理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: