2017-09-05 4 views
0

Y a-t-il un moyen d'intercepter/décorer l'événement tactile de la vue sans étendre View ou l'entourer de ViewGroup (qui peut intercepter des événements enfants)? Supposons que j'ai ExpandableListView qui gère les événements de clic sur un élément. Si j'ai défini OnClickListener ou OnTouchListener sur une vue d'élément gonflée renvoyée par l'adaptateur, ExpandableListView n'effectue pas d'expansion de groupe d'actions correspondante car l'événement a été consommé par l'écouteur d'élément.Intercepter un événement click/touch sans surcharger les méthodes ViewGroup ou View

La raison pour laquelle je ne veux pas utiliser ExpandableListView#setOnItemClickListener est que je veux décorer l'événement click dans l'adaptateur sans utiliser la dépendance ExpandableListView.

Répondre

0

J'ai trouvé une solution de travail pour ce problème. Solution: Collecter des clones d'événements dans OnTouchListener et les envoyer ensuite à la vue parente.

private final Queue<MotionEntry> consumedEvents = new LinkedList<>(); 
private final AtomicBoolean isDispatching = new AtomicBoolean(false); 
... 
    groupView.setOnTouchListener(new OnTouchListener() { 
     @Override 
     public boolean onTouch(View v, MotionEvent e) { 
      // we don't want to handle re-dispatched event... 
      if (isDispatching.get()) { 
       return false; 
      } 
      // create clone as event might be changed by parent 
      MotionEvent clone = MotionEvent.obtain(e); 
      MotionEntry entry = new MotionEntry(v, clone); 
      consumedEvents.add(entry); 

      // consume ACTION_DOWN in order to receive subsequent motion events 
      // like ACTION_MOVE, ACTION_CANCEL/ACTION_UP... 
      if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 
       return true; 
      } 
      // we do not want to handle canceled motion... 
      if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 
       consumedEvents.clear(); 
       return false; 
      } 
      // at this moment we have intercepted whole motion 
      // = re-dispatch to parent in order to apply default handling... 
      if (event.getActionMasked() == MotionEvent.ACTION_UP) { 
       dispatchEvents(); 
      } 
      return true; 
     } 
    }); 
... 

méthode d'expédition:

private void dispatchEvents() { 
    isDispatching.set(true); 
    while (!consumedEvents.isEmpty()) { 
     MotionEntry entry = consumedEvents.poll(); 

     ViewGroup parent = (ViewGroup) entry.view.getParent(); 
     if (parent == null || entry.view.getVisibility() != View.VISIBLE) { 
      continue; // skip dispatching to detached/invisible view 
     } 
     // make position relative to parent... 
     entry.event.offsetLocation(entry.view.getLeft(), entry.view.getTop()); 
     entry.event.setSource(PARENT_DISPATCHER); 
     parent.dispatchTouchEvent(entry.event); 

     if (event.getActionMasked() == MotionEvent.ACTION_UP) { 
      clickListener.onClick(entry.view); 
     } 
    } 
    isDispatching.set(false); 
} 

Classe d'assistance

private class MotionEntry { 
    private final View view; 
    private final MotionEvent event; 

    public MotionEntry(View view, MotionEvent event) { 
     this.view = view; 
     this.event = event; 
    } 
}