2015-06-24 3 views
6

У меня есть проблема с попыткой контролировать распространение событий касания в моем RecycleView. Таким образом, у меня есть RecyclerView, заполняющий набор CardViews, имитирующих стек карточек (поэтому они перекрывают друг друга с определенным перемещением, хотя они имеют различное значение высоты). Моя текущая проблема заключается в том, что на каждой карте есть кнопка, и поскольку относительное смещение карты меньше высоты кнопки, это приводит к тому, что кнопки перекрываются, и всякий раз, когда отправляется событие касания, он начинает распространяться с нижней иерархии представлений (от детей с наивысшим числом детей).Управление распространением событий касания в случае перекрывающихся представлений

В соответствии со статьями, которые я прочитал (this, this и также this video) распространение прикосновения зависит от порядка взглядов в родительском зрении, поэтому прикосновение сначала будет доставлено ребенку с высоким индексом, в то время как я хочу сенсорное событие обрабатываться только прикосновением верхнего вида и RecyclerView (он также должен обрабатывать жесты перетаскивания и движения). В настоящее время я использую резервную копию с картами, которые знают о своей позиции в иерархии представлений родителей, чтобы предотвратить неправильное обращение с детьми, но это действительно уродливый способ сделать это. Мое предположение заключается в том, что я должен переопределить метод RecyclerView и правильно отправить событие касания только до самого верхнего ребенка. Однако, когда я попробовал этот способ сделать это (который также является своего рода неуклюжая):

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

только вниз событие было доставлено на кнопку в карточке (не событие щелчка не срабатывает). Я буду признателен за любые рекомендации о том, как обратить вспять порядок распространения сенсорных событий или делегирование события касания к определенному представлению. Заранее большое спасибо.

EDIT: Это скриншот того, как стек пример карта выглядят как 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; 
} 
} 

EDIT 2: Я забыл упомянуть, эта проблема присутствует только на предварительно Lolipop устройств, кажется, что, начиная с Lolipop, ViewGroups также принять во внимание высоту, а диспетчеризация события касания

EDIT 3: Вот мой текущий порядок ребенок рисунок:

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

EDIT 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); 
} 
+0

вы можете разместить свой xml и адаптер –

+0

@war_Hero Я добавил код для тестового приложения, которое имеет такую ​​же проблему, как описано в вопросе – Chaosit

ответ

2

CardView использует API возвышения на L и до L, он изменяет размер тени. dispatchTouchEvent в L уважает Z упорядоченность при переборе над детьми, которые не происходят в заранее L.

Глядя на исходный код:

Pre леденец

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]; 

... 

Lollipop

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 значений, установленных на Views.

Возможно, вы захотите проверить custom child drawing order in a ViewGroup, но я думаю, что ваше текущее исправление проблемы достаточно.

+0

Спасибо за ответ , да, я уже переопределил порядок рисования детей (вы можете увидеть код в моем последнем редактировании), поскольку в устройствах pre-Lolipop у меня была проблема с неправильным размещением детей. Поэтому, к сожалению, я больше не могу изменить порядок рисования чертежей, иначе у меня появятся некоторые проблемы с рендерингом, поэтому я искал решение для переопределения dispatchTouchEvent и управления приказом диспетчера прикосновений. Мне не нравится мое текущее решение, потому что, на мой взгляд, карты не должны знать о своей позиции, я предпочел бы иметь все позиции, связанные с классами LayoutManager и RecyclerView. – Chaosit

+0

Это верно, но версии с предварительным леллипопом не уважают z-порядок, поэтому не думайте, что вы можете много обойтись, кроме переопределения поведения по умолчанию с вашей пользовательской реализацией, которую вы уже делаете. – random

+0

Я думаю, что должен быть способ от переопределения 'dispatchTouchEvent', который будет ограничивать распространение касания строго до первого представления в иерархии взглядов, к сожалению, мои собственные усилия не принесли слишком большой прибыли в этой области, я пробовал самый тупой способ сделать это, как вызов диспетчерского события касания на самый верхний ребенок, но я потерял чувствительность к касанию RecyclerView, поэтому я попытался отправить события касания только к касательным в самом верхнем ребенке, но у меня возникла эта странная проблема, когда не все сенсорные события были доставлены на кнопку (описанная в вопрос) – Chaosit

0

Вы пытались установить свой верхний вид как интерактивный?

button.setClickable(true); 

Если этот атрибут не установлен (по умолчанию) в представлении XML, он распространяет событие click вверх.

Но если вы установите его на верхний вид как истинный, он не должен распространять какое-либо событие на любом другом представлении.

+0

Всякий раз, когда вы устанавливаете OnClickListener в разрешаемом флаге, устанавливается автоматически. Я попытался сделать это вручную - это не помогло. Это может помочь, если я устанавливаю clickable в false для каждого представления, кроме самого верхнего, но в основном это будет почти то же самое решение, которое я имею прямо сейчас, в то время как я ищу правильный способ управления распространением событий касания – Chaosit