2015-06-24 7 views
6

Ho qualche problema nel tentativo di controllare la propagazione dell'evento di tocco all'interno del mio RecycleView. Così ho una RecyclerView che popola una serie di CardViews che imitano una pila di carte (quindi si sovrappongono a vicenda con un certo spostamento, sebbene abbiano un diverso valore di elevazione). Il mio problema attuale è che ogni carta ha un pulsante e poiché lo spostamento relativo della carta è inferiore all'altezza del pulsante, si verifica la situazione in cui i pulsanti si sovrappongono e ogni volta che viene inviato un evento tattile inizia a propagarsi dalla gerarchia inferiore della vista (dai bambini con il numero bambino più alto).Controllo della propagazione dell'evento tattile in caso di visualizzazioni sovrapposte

Secondo articoli che ho letto (this, this e anche this video) tocco di propagazione dipende da l'ordine di opinioni in vista primaria, in modo da toccare per prima consegnato al bambino con l'indice più alto, mentre io voglio l'evento di tocco per essere elaborati solo da touchables della vista più in alto e RecyclerView (deve anche elaborare i gesti di trascinamento e rilascio). Attualmente sto usando un fallback con le carte che sono consapevoli della loro posizione all'interno della gerarchia delle viste dei genitori per impedire ai bambini sbagliati di elaborare tocchi, ma questo è davvero un modo brutto per farlo. La mia ipotesi è che devo scavalcare il metodo dispatchTouchEvent di RecyclerView e inviare correttamente un evento touch solo al bambino più in alto. Tuttavia, quando ho provato questo modo di fare che (che è anche un po 'goffo):

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

L'unico evento è stato consegnato al pulsante all'interno di una scheda (nessun evento click attivato). Apprezzerò qualsiasi consiglio sul modo di invertire l'ordine di propagazione degli eventi tattili o sulla delega di un evento tattile a una specifica vista. Grazie mille in anticipo.

EDIT: Questa è la schermata di come la pila esempio carta sta cercando come Screen

esempio di codice adattatore:

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

    } 
} 

e un esempio di layout carta:

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

E ecco il codice che sto usando ora per risolvere il problema (questo approccio non mi piace, voglio trovare il modo migliore)

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: ho dimenticato di menzionare, questo problema è presente solo su dispositivi pre-Lolipop, sembra che a partire dal Lolipop, ViewGroups anche prendere in considerazione l'elevazione mentre dispacciamento eventi touch

EDIT 3: Ecco il mio attuale ordine di disegno infantile:

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

EDIT 4: Finalmente sono riuscito a risolvere il problema grazie a utenti casuali, e this answer, la soluzione era estremamente semplice:

01.235.164,106 mila
@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

puoi pubblicare il tuo xml e l'adattatore –

+0

@war_Hero Ho aggiunto il codice per un'app di prova che ha lo stesso problema descritto in una domanda – Chaosit

risposta

2

CardView utilizza l'API di elevazione su L e prima di L, modifica la dimensione dell'ombra. dispatchTouchEvent a L rispetta Z ordinamento quando l'iterazione di bambini che non avviene in pre L.

Guardando il codice sorgente:

Pre Lollipop

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

L'ordine di disegno del bambino possono essere sovrascritti con custom child drawing order in a ViewGroup, e con setZ(float) valori Z personalizzati impostati su Visualizzazioni.

Si potrebbe voler controllare custom child drawing order in a ViewGroup ma penso che la tua correzione corrente per il problema è abbastanza buono.

+0

Grazie per la risposta , sì, ho già sovrascritto l'ordine di disegno dei bambini (puoi vedere il codice nella mia ultima modifica), dal momento che nei dispositivi pre-Lolipop ho avuto un problema con il posizionamento secondario errato. Quindi, sfortunatamente, non posso più cambiare l'ordine di disegno dei bambini, altrimenti avrò dei problemi di rendering, quindi stavo cercando una soluzione per sovrascrivere il dispatchTouchEvent e per controllare l'ordine di invio dei touch. Non mi piace la mia attuale soluzione, perché a mio avviso le carte non dovrebbero essere consapevoli della loro posizione in vista, preferirei avere tutto relativo alla posizione nelle classi LayoutManager e RecyclerView. – Chaosit

+0

è corretto, ma le versioni pre-lollipop non rispettano lo z-ordering quindi non pensate di poter fare molto a parte a parte il comportamento predefinito di override con la vostra implementazione personalizzata che state già facendo – random

+0

Penso che ci sia un modo di sovrascrivere il 'dispatchTouchEvent' che limiterà la propagazione del tocco rigorosamente alla prima vista nella gerarchia della vista, sfortunatamente i miei sforzi non hanno portato troppi profitti in questo campo, ho provato il modo più stupido di farlo come chiamare l'evento di invio dispatch su direttamente il bambino più in alto, ma ho perso la reattività tattile di RecyclerView, quindi ho provato a inviare gli eventi tattili solo ai touchables nel bambino più in alto, ma ho avuto questo strano problema quando non tutti gli eventi tattili sono stati consegnati al pulsante (quello descritto nel domanda) – Chaosit

0

Hai provato a impostare la visualizzazione in alto come selezionabile?

button.setClickable(true); 

Se questo attributo non è impostato (per default) in vista XML propagare l'evento click verso l'alto.

Ma se lo si imposta nella vista più in alto come vero, non dovrebbe propagare alcun evento su nessun'altra vista.

+0

Ogni volta che si imposta un OnClickListener sulla vista, il flag selezionabile viene impostato automaticamente. Ho provato a farlo manualmente - non ha aiutato. Questo potrebbe essere utile se imposto il clic su falso per ogni vista tranne che per il più alto, ma fondamentalmente sarà quasi la stessa soluzione che ho adesso, mentre sto cercando un modo corretto per controllare la propagazione degli eventi tattili – Chaosit

Problemi correlati