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

Cha*_*sit 6 sdk android touch

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

根据我读过的文章(这个,这个这个视频),触摸传播取决于父视图中的视图顺序,因此触摸将首先传递给具有最高索引的子项,而我希望仅处理触摸事件通过最顶层视图和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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

示例适配器代码:

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

    }
}
Run Code Online (Sandbox Code Playgroud)

和一个示例卡布局:

<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>
Run Code Online (Sandbox Code Playgroud)

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

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;
}
}
Run Code Online (Sandbox Code Playgroud)

编辑2:我忘了提一下,这个问题只存在于Lolipop之前的设备上,似乎从Lolipop开始,ViewGroups在调度触摸事件时也会考虑提升

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

@Override
protected int getChildDrawingOrder(int childCount, int i) {
    return childCount - i - 1;
}
Run Code Online (Sandbox Code Playgroud)

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

@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);
}
Run Code Online (Sandbox Code Playgroud)

ran*_*dom 2

CardView在 L 上使用海拔 API,在 L 之前,它会更改阴影大小。dispatchTouchEventin L 在迭代子级时遵循 Z 排序,这在 pre 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];

...
Run Code Online (Sandbox Code Playgroud)

棒糖

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;
             }
             ...
Run Code Online (Sandbox Code Playgroud)

可以使用ViewGroup 中的自定义子绘制顺序以及在视图上设置的setZ(float)自定义 Z 值来覆盖子绘制顺序。

您可能想检查ViewGroup 中的自定义子绘图顺序,但我认为您当前对问题的修复已经足够好了。