Existe-t-il un exemple d’utilisation d’un TouchDelegate dans Android pour augmenter la taille de la cible de clic d’une vue?

D’après ce que je comprends, lorsque vous avez une vue trop petite pour pouvoir la toucher facilement, vous êtes censé utiliser un TouchDelegate pour augmenter la zone cliquable de cette vue.

Cependant, la recherche d’exemples d’utilisation sur Google amène plusieurs personnes à poser la question, mais peu de réponses.

Est-ce que quelqu’un sait comment configurer un délégué tactile pour pouvoir, par exemple, augmenter sa zone cliquable de 4 pixels dans toutes les directions?

J’ai demandé à un ami chez Google et ils ont pu m’aider à comprendre comment utiliser TouchDelegate. Voici ce que nous avons imaginé:

final View parent = (View) delegate.getParent(); parent.post( new Runnable() { // Post in the parent's message queue to make sure the parent // lays out its children before we call getHitRect() public void run() { final Rect r = new Rect(); delegate.getHitRect(r); r.top -= 4; r.bottom += 4; parent.setTouchDelegate( new TouchDelegate( r , delegate)); } }); 

J’ai été en mesure d’accomplir cela avec plusieurs vues (cases à cocher) sur un écran en grande partie à partir de cet article de blog . Fondamentalement, vous prenez la solution d’ambiance et l’appliquez à chaque bouton et à son parent individuellement.

 public static void expandTouchArea(final View bigView, final View smallView, final int extraPadding) { bigView.post(new Runnable() { @Override public void run() { Rect rect = new Rect(); smallView.getHitRect(rect); rect.top -= extraPadding; rect.left -= extraPadding; rect.right += extraPadding; rect.bottom += extraPadding; bigView.setTouchDelegate(new TouchDelegate(rect, smallView)); } }); } 

Dans mon cas, j’avais une grid de vues avec des cases à cocher superposées et appelait la méthode comme suit:

 CheckBox mCheckBox = (CheckBox) convertView.findViewById(R.id.checkBox1); final ImageView imageView = (ImageView) convertView.findViewById(R.id.imageView1); // Increase checkbox clickable area expandTouchArea(imageView, mCheckBox, 100); 

Travailler pour moi

Cette solution a été publiée par @BrendanWeinstein dans les commentaires.

Au lieu d’envoyer un TouchDelegate vous pouvez remplacer la getHitRect(Rect) de votre View (au cas où vous en étendez un).

 public class MyView extends View { //NOTE: any other View can be used here /* a lot of required methods */ @Override public void getHitRect(Rect r) { super.getHitRect(r); //get hit Rect of current View if(r == null) { return; } /* Manipulate with rect as you wish */ r.top -= 10; } } 

L’approche d’ambiance n’a pas fonctionné pour moi mais après quelques changements, elle a fonctionné:

 private void initApplyButtonOnClick() { mApplyButton.setOnClickListener(onApplyClickListener); final View parent = (View)mApplyButton.getParent(); parent.post(new Runnable() { @Override public void run() { final Rect hitRect = new Rect(); parent.getHitRect(hitRect); hitRect.right = hitRect.right - hitRect.left; hitRect.bottom = hitRect.bottom - hitRect.top; hitRect.top = 0; hitRect.left = 0; parent.setTouchDelegate(new TouchDelegate(hitRect , mApplyButton)); } }); } 

Peut-être que cela peut sauver le temps de quelqu’un

N’est-ce pas la meilleure idée de donner du rembourrage à ce composant particulier (bouton).

Un peu tard pour la fête, mais après beaucoup de recherches, j’utilise maintenant:

 /** * Expand the given child View's touchable area by the given padding, by * setting a TouchDelegate on the given ancestor View whenever its layout * changes. */*emphasized text* public static void expandTouchArea(final View ancestorView, final View childView, final Rect padding) { ancestorView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { TouchDelegate delegate = null; if (childView.isShown()) { // Get hitRect in parent's coordinates Rect hitRect = new Rect(); childView.getHitRect(hitRect); // Translate to ancestor's coordinates int ancestorLoc[] = new int[2]; ancestorView.getLocationInWindow(ancestorLoc); int parentLoc[] = new int[2]; ((View)childView.getParent()).getLocationInWindow( parentLoc); int xOffset = parentLoc[0] - ancestorLoc[0]; hitRect.left += xOffset; hitRect.right += xOffset; int yOffset = parentLoc[1] - ancestorLoc[1]; hitRect.top += yOffset; hitRect.bottom += yOffset; // Add padding hitRect.top -= padding.top; hitRect.bottom += padding.bottom; hitRect.left -= padding.left; hitRect.right += padding.right; delegate = new TouchDelegate(hitRect, childView); } ancestorView.setTouchDelegate(delegate); } }); } 

C’est mieux que la solution acceptée car elle permet également de définir un TouchDelegate sur n’importe quelle vue des ancêtres, pas seulement sur la vue parente.

Contrairement à la solution acceptée, elle met également à jour le TouchDelegate chaque fois que la disposition de la vue ancêtre change.

Si vous ne voulez pas le faire par programmation, créez simplement une zone transparente autour de l’image, si vous utilisez l’image comme arrière-plan du bouton (vue).

La zone grise peut être transparente pour augmenter la zone tactile.

entrer la description de l'image ici

Dans la plupart des cas, vous pouvez encapsuler la vue nécessitant une zone tactile plus grande dans une autre vue sans tête (vue transparente artificielle) et append un remplissage / marge à la vue encapsuleuse et joindre le clic / toucher à la vue encapsulante doit avoir une plus grande surface tactile.

Selon le commentaire de @Mason Lee, cela a résolu mon problème. Mon projet avait une disposition relative et un bouton. Donc, le parent est -> mise en page et enfant est -> un bouton.

Voici un exemple de google link google code

En cas de suppression de sa réponse très précieuse, je mets sa réponse ici.

On m’a récemment demandé comment utiliser un TouchDelegate. J’étais un peu rouillé à ce sujet et je ne trouvais pas de bonne documentation à ce sujet. Voici le code que j’ai écrit après quelques essais et erreurs. touch_delegate_view est un RelativeLayout simple avec l’identifiant touch_delegate_root. J’ai défini avec un seul enfant de la mise en page, le bouton delegated_button. Dans cet exemple, je développe la zone cliquable du bouton à 200 pixels au-dessus du haut de mon bouton.

la classe publique TouchDelegateSample étend l’activité {

Bouton mButton; @Override protégé void onCreate (Bundle savedInstanceState) {super.onCreate (savedInstanceState); setContentView (R.layout.touch_delegate_view); mButton = (Button) findViewById (R.id.delegated_button); Voir parent = findViewById (R.id.touch_delegate_root);

 // post a runnable to the parent view's message queue so its run after // the view is drawn parent.post(new Runnable() { @Override public void run() { Rect delegateArea = new Rect(); Button delegate = TouchDelegateSample.this.mButton; delegate.getHitRect(delegateArea); delegateArea.top -= 200; TouchDelegate expandedArea = new TouchDelegate(delegateArea, delegate); // give the delegate to an ancestor of the view we're delegating the // area to if (View.class.isInstance(delegate.getParent())) { ((View)delegate.getParent()).setTouchDelegate(expandedArea); } } }); } } 

Cheers, Justin Android Team @ Google

Quelques mots de moi: si vous voulez développer le côté gauche, vous donnez une valeur avec moins, et si vous voulez développer le côté droit de l’object, vous donnez une valeur avec plus. Cela fonctionne de la même manière avec le haut et le bas.

Pour développer la zone tactile de manière générique avec quelques ressortingctions, utilisez le code suivant.

Il vous permet d’étendre la zone tactile de la view donnée dans la vue ancestor donnée par l’ expansion donnée en pixels. Vous pouvez choisir n’importe quel ancêtre tant que la vue donnée est dans l’arbre de configuration des ancêtres.

  public static void expandTouchArea(final View view, final ViewGroup ancestor, final int expansion) { ancestor.post(new Runnable() { public void run() { Rect bounds = getRelativeBounds(view, ancestor); Rect expandedBounds = expand(bounds, expansion); // LOG.debug("Expanding touch area of {} within {} from {} by {}px to {}", view, ancestor, bounds, expansion, expandedBounds); ancestor.setTouchDelegate(new TouchDelegate(expandedBounds, view)); } private Rect getRelativeBounds(View view, ViewGroup ancestor) { Point relativeLocation = getRelativeLocation(view, ancestor); return new Rect(relativeLocation.x, relativeLocation.y, relativeLocation.x + view.getWidth(), relativeLocation.y + view.getHeight()); } private Point getRelativeLocation(View view, ViewGroup ancestor) { Point absoluteAncestorLocation = getAbsoluteLocation(ancestor); Point absoluteViewLocation = getAbsoluteLocation(view); return new Point(absoluteViewLocation.x - absoluteAncestorLocation.x, absoluteViewLocation.y - absoluteAncestorLocation.y); } private Point getAbsoluteLocation(View view) { int[] absoluteLocation = new int[2]; view.getLocationOnScreen(absoluteLocation); return new Point(absoluteLocation[0], absoluteLocation[1]); } private Rect expand(Rect rect, int by) { Rect expandedRect = new Rect(rect); expandedRect.left -= by; expandedRect.top -= by; expandedRect.right += by; expandedRect.bottom += by; return expandedRect; } }); } 

Ressortingctions applicables:

  • La zone tactile ne peut pas dépasser les limites de l’ancêtre de la vue, car l’ancêtre doit être capable d’attraper l’événement tactile afin de le transmettre à la vue.
  • Un seul TouchDelegate peut être défini sur un ViewGroup . Si vous souhaitez travailler avec plusieurs delegates tactiles, choisissez des ancêtres différents ou utilisez un délégué tactile de composition comme expliqué dans Comment utiliser plusieurs TouchDelegate .

Parce que je n’aimais pas l’idée d’attendre la mise en page juste pour obtenir la nouvelle taille du rectangle du TouchDelegate, j’ai opté pour une solution différente:

 public class TouchSizeIncreaser extends FrameLayout { public TouchSizeIncreaser(@NonNull Context context, @Nullable AtsortingbuteSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return true; } @Override public boolean onTouchEvent(MotionEvent event) { final View child = getChildAt(0); if(child != null) { child.onTouchEvent(event); } return true; } } 

Et puis, dans une mise en page:

    

L’idée est que TouchSizeIncreaser FrameLayout encapsule la vue Spinner (peut être n’importe quelle vue enfant) et transfère tous les événements tactiles capturés dans sa vue rectifiée à la vue enfant. Cela fonctionne pour les clics, le compteur s’ouvre même s’il est cliqué en dehors de ses limites, ne sachant pas quelles sont les implications pour d’autres cas plus complexes.