在视图重叠的情况下控制触摸事件传播

时间:2015-06-24 14:41:45

标签: android sdk touch

我在尝试控制RecycleView中的触摸事件传播时遇到了一些问题。所以我有一个RecyclerView填充一组模仿卡片堆栈的CardView(因此它们相互重叠并具有一定的位移,尽管它们具有不同的高程值)。我目前的问题是每张卡都有一个按钮,因为相对卡位移小于按钮的高度,导致按钮重叠的情况,每当调度触摸事件时,它开始从视图层次结构的底部传播(来自儿童)最高的孩子数)。

根据我阅读的文章(thisthis以及this video),触摸传播取决于父视图中的视图顺序,因此首先将触摸传递给孩子具有最高索引,而我希望触摸事件仅由最顶层视图和RecyclerView的触摸器处理(它还必须处理拖动和拖动手势)。目前,我正在使用卡片,这些卡片知道他们在父视图层次结构中的位置,以防止错误的孩子处理触摸,但这是非常难看的方式。我的假设是我必须覆盖RecyclerView的dispatchTouchEvent方法并正确地将触摸事件发送给最顶层的孩子。但是,当我尝试这种方式时(这也有点笨拙):

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    View topChild = getChildAt(0);
    for (View touchable : topChild.getTouchables()) {
        int[] location = new int[2];
        touchable.getLocationOnScreen(location);
        RectF touchableRect = new RectF(location[0],
                location[1],
                location[0] + touchable.getWidth(),
                location[1] + touchable.getHeight());
        if (touchableRect.contains(ev.getRawX(), ev.getRawY())) {
            return touchable.dispatchTouchEvent(ev);
        }
    }
    return onTouchEvent(ev);
}

只有DOWN事件被发送到卡内的按钮(没有触发点击事件)。我将非常感谢有关扭转触摸事件传播顺序或将触摸事件委托给特定视图的方法的任何建议。非常感谢你提前。

编辑:这是示例卡堆栈的样子截图 Screen

示例适配器代码:

public class TestAdapter extends RecyclerView.Adapter {

List<Integer> items;

public TestAdapter(List<Integer> items) {
    this.items = items;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.test_layout, parent, false);
    return new TestHolder(v);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    TestHolder currentHolder = (TestHolder) holder;
    currentHolder.number.setText(Integer.toString(position));
    currentHolder.tv.setTag(position);
    currentHolder.tv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int pos = (int) v.getTag();
            Toast.makeText(v.getContext(), Integer.toString(pos) + "clicked", Toast.LENGTH_SHORT).show();
        }
    });
}

@Override
public int getItemCount() {
    return items.size();
}

private class TestHolder extends RecyclerView.ViewHolder {

    private TextView tv;
    private TextView number;

    public TestHolder(View itemView) {
        super(itemView);
        tv = (TextView) itemView.findViewById(R.id.click);
        number = (TextView) itemView.findViewById(R.id.number);
    }

    }
}

和示例卡片布局:

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
<RelativeLayout android:layout_width="match_parent"
                android:layout_height="match_parent">

    <TextView android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerInParent="true"
              android:layout_margin="16dp"
              android:textSize="24sp"
              android:id="@+id/number"/>

    <TextView android:layout_width="wrap_content"
              android:layout_height="64dp"
              android:gravity="center"
              android:layout_alignParentBottom="true"
              android:layout_alignParentLeft="true"
              android:layout_margin="16dp"
              android:textSize="24sp"
              android:text="CLICK ME"
              android:id="@+id/click"/>
</RelativeLayout>

</android.support.v7.widget.CardView>

这是我现在用来解决问题的代码(这种方法我不喜欢,我想找到更好的方法)

public class PositionAwareCardView extends CardView {

private int mChildPosition;

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

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

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

public void setChildPosition(int pos) {
    mChildPosition = pos;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // if it's not the topmost view in view hierarchy then block touch events
    return mChildPosition != 0 || super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    return false;
}
}
编辑2:我忘了提一下,这个问题只存在于Lolipop之前的设备上,似乎从Lolipop开始,ViewGroups在调度触摸事件时也会考虑提升

编辑3:这是我当前的儿童绘画顺序:

@Override
protected int getChildDrawingOrder(int childCount, int i) {
    return childCount - i - 1;
}

编辑4:最后我能够通过用户随机解决问题,this answer,解决方案非常简单:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    if (!onInterceptTouchEvent(ev)) {
        if (getChildCount() > 0) {
            final float offsetX = -getChildAt(0).getLeft();
            final float offsetY = -getChildAt(0).getTop();
            ev.offsetLocation(offsetX, offsetY);
            if (getChildAt(0).dispatchTouchEvent(ev)) {
                // if touch event is consumed by the child - then just record it's coordinates
                x = ev.getRawX();
                y = ev.getRawY();
                return true;
            }
            ev.offsetLocation(-offsetX, -offsetY);
        }
    }
    return super.dispatchTouchEvent(ev);
}

2 个答案:

答案 0 :(得分:2)

CardView在L上使用高程API,在L之前,它会更改阴影大小。 L中的dispatchTouchEvent在迭代孩子时遵循Z顺序,这在前L中没有发生。

查看源代码:

前棒棒糖

ViewGroup#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = customOrder ?
    getChildDrawingOrder(childrenCount, i) : i;
    final View child = children[childIndex];

...

<强>棒棒糖

ViewGroup#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
// Check whether any clickable siblings cover the child
// view and if so keep track of the intersections. Also
// respect Z ordering when iterating over children.

ArrayList<View> orderedList = buildOrderedChildList();

final boolean useCustomOrder = orderedList == null
                 && isChildrenDrawingOrderEnabled();
final int childCount = mChildrenCount;
         for (int i = childCount - 1; i >= 0; i--) {
             final int childIndex = useCustomOrder
                         ? getChildDrawingOrder(childCount, i) : i;
             final View sibling = (orderedList == null)
                         ? mChildren[childIndex] : orderedList.get(childIndex);

             // We care only about siblings over the child  
             if (sibling == child) {
                 break;
             }
             ...

可以使用custom child drawing order in a ViewGroup覆盖子绘图顺序,并在视图上设置setZ(float)个自定义Z值。

您可能需要检查custom child drawing order in a ViewGroup,但我认为您目前对该问题的修复已经足够了。

答案 1 :(得分:0)

您是否尝试将最顶层的视图设置为可点击?

button.setClickable(true);

如果在View XML中未设置此属性(默认设置),则会向上传播click事件。

但是如果你把它放在最顶层的视图上为true,它就不应该在任何其他视图上传播任何事件。