¿Hay algún ejemplo de cómo usar TouchDelegate en Android para aumentar el tamaño del objetivo de clic de una vista?

82

Tengo entendido que cuando tiene una vista que es demasiado pequeña para tocarla fácilmente, se supone que debe usar un TouchDelegate para aumentar la región en la que se puede hacer clic para esa vista.

Sin embargo, al buscar ejemplos de uso en Google, aparecen varias personas que hacen la pregunta, pero pocas respuestas.

¿Alguien sabe la forma correcta de configurar un delegado táctil para una vista que, por ejemplo, aumente su región en la que se puede hacer clic en 4 píxeles en todas las direcciones?

emmby
fuente
3
Aquí hay una buena publicación adicional sobre cómo usar TouchDelegate: groups.google.com/group/android-developers/browse_thread/thread/…
Mason Lee
Buen tutorial: áreas táctiles ampliadas
Thierry-Dimitri Roy

Respuestas:

100

Le pregunté a un amigo de Google y pudieron ayudarme a descubrir cómo usar TouchDelegate. Esto es lo que se nos ocurrió:

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));
    }
});
emmby
fuente
16
¿Qué pasa si hay dos botones en una pantalla a los que le gustaría hacer esto? Si vuelve a configurar TouchDelegate, se borra el delegado táctil anterior.
cottonBallPaws
2
Este no funcionó para mí ... Supongo que el delegado es la vista a la que estoy tratando de agregar TouchDelegate.
Kevin Parker
20
@AmokraneChentir He tenido el mismo problema. En lugar de usar TouchDelegates, tengo una clase que amplía el botón. En esta clase anulo getHitRect. Puede extender el rectángulo de impacto al tamaño de la vista principal. public void getHitRect (Rect outRect) {outRect.set (getLeft () - mPadding, getTop () - mPadding, getRight () + mPadding, getTop () + mPadding); }
Brendan Weinstein
5
@Amokrane: Resulta que la solución que ofrecí no funciona en ICS. Escribí una publicación de blog sobre dos estrategias de Touch Delegate diferentes brendanweinstein.me/2012/06/26/… y puede obtener un código de muestra para ambas estrategias en github.com/brendanw/Touch-Delegates/blob/master/src/com/ brendan /…
Brendan Weinstein
8
Según @ZsoltSafrany, una forma más segura de hacer esto es a través de un OnGlobalLayoutListener y asignando el delegado onGlobalLayout ().
greg7gkb
20

Pude lograr esto con múltiples vistas (casillas de verificación) en una pantalla dibujando en gran parte de esta publicación de blog . Básicamente, toma la solución de emmby y la aplica a cada botón y a su padre individualmente.

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

En mi caso, tenía una vista de cuadrícula de vistas de imágenes con casillas de verificación superpuestas en la parte superior y llamé al método de la siguiente manera:

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

Funciona muy bien para mí.

Kyle Clegg
fuente
11

Esta solución fue publicada por @BrendanWeinstein en los comentarios.

En lugar de enviar un TouchDelegate, puede anular el getHitRect(Rect)método de su View(en caso de que esté ampliando uno).

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;
    }
}
Dmitry Zaytsev
fuente
Idea interesante. Pero, ¿no significaría que la vista no se puede crear a partir de un diseño? No, espere, si diseño una nueva clase de vista y la uso en el diseño. Eso podría funcionar. Vale la pena intentarlo.
Martin
10
@Martin, esa es la idea correcta. Desafortunadamente, a partir de ICS ya no se llama getHitRect por dispatchTouchEvent grepcode.com/file/repository.grepcode.com/java/ext/… La anulación de getHitRect solo funcionará para gingerbread e inferiores.
Brendan Weinstein
6

El enfoque de emmby no funcionó para mí, pero después de algunos cambios, sí lo hizo:

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

Tal vez pueda ahorrarle tiempo a alguien

virus
fuente
esto es lo que funcionó para mí. en realidad, hace que todo el padre envíe eventos de clic al hijo. ni siquiera tiene que ser el padre directo (puede ser padre de un padre).
desarrollador de Android
Eso probablemente funcione si tiene una vista para delegar. ¿Qué pasa si tiene, digamos, 50 vistas para mejorar el área táctil? - Como en esta captura de pantalla: plus.google.com/u/0/b/112127498414857833804/… - ¿TouchDelegate sigue siendo una solución adecuada?
Martin
2

Según el comentario de @Mason Lee, esto resolvió mi problema. Mi proyecto tenía un diseño relativo y un botón. Entonces el padre es -> diseño y el hijo es -> un botón.

Aquí hay un código de google de ejemplo de enlace de google

En caso de borrar su muy valiosa respuesta pongo aquí su respuesta.

Hace poco me preguntaron cómo usar TouchDelegate. Yo mismo estaba un poco oxidado con esto y no pude encontrar ninguna buena documentación al respecto. Aquí está el código que escribí después de un poco de prueba y error. touch_delegate_view es un RelativeLayout simple con el id touch_delegate_root. Definí con un único hijo del diseño, el botón delegated_button. En este ejemplo, amplío el área en la que se puede hacer clic del botón a 200 píxeles por encima de la parte superior de mi botón.

public class TouchDelegateSample extends Activity {

  Button mButton;   
  @Override   
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.touch_delegate_view);
    mButton = (Button)findViewById(R.id.delegated_button);
    View 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);
        }
      }
    });   
  } 
}

Saludos, Justin Equipo de Android @ Google

Algunas palabras mías: si quieres expandir el lado izquierdo, le das valor con menos, y si quieres expandir el lado derecho del objeto, le das valor con más. Esto funciona igual con la parte superior e inferior.

pez muerto
fuente
La pista mágica aquí parece ser un botón : de alguna manera tengo la sensación de que TouchDelegate no se puede utilizar para varios botones. ¿Puedes confirmar eso?
Martin
1

¿No es la mejor idea darle Padding a ese componente en particular (botón)?

usuario2454519
fuente
1
Eso haría ver más grande. ¿Qué sucede si tiene un texto que no se puede hacer clic encima de un botón y desea que el área de ese texto se use para mejorar la precisión del clic del botón? - Como en esta captura de
Martin
1

Un poco tarde para la fiesta, pero después de mucha investigación, ahora estoy usando:

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

Esto es mejor que la solución aceptada porque también permite que un TouchDelegate se configure en cualquier Vista anterior, no solo en la Vista principal.

A diferencia de la solución aceptada, también actualiza TouchDelegate cada vez que hay un cambio en el diseño de la vista anterior.

Stephen Talley
fuente
0

Si no quiere hacerlo programáticamente, simplemente cree un área transparente alrededor de la imagen, si está usando una imagen como fondo para el botón (ver).

El área gris puede ser transparente para aumentar el área táctil.

ingrese la descripción de la imagen aquí

Vinayak Bevinakatti
fuente
1
¿No crearía eso un espacio vacío alrededor de la vista que no se puede usar para nada más? ¿Qué sucede si desea mostrar texto informativo, cierre un botón y utilice el área de texto para mejorar el área de clic del botón?
Martin
No digo que sea el mejor y el enfoque final. Pero encajará en muchos escenarios en los que desea mostrar una imagen de botón pequeño pero el área táctil se ampliará. En su caso, es difícil dar prioridad al botón cuando hace clic en otras vistas (vista de texto), todo junto es un desafío diferente.
Vinayak Bevinakatti
0

En la mayoría de los casos, puede envolver la vista que requiere un área táctil más grande en otra vista sin cabeza (vista transparente artificial) y agregar relleno / margen a la vista de envoltura y adjuntar el clic / toque incluso a la vista de envoltura en lugar de la vista original que tiene que tener un área táctil más grande.

inteist
fuente
¿No crearía eso un espacio vacío alrededor de la vista que no se puede usar para nada más? ¿Qué sucede si desea mostrar texto informativo, cierre un botón y utilice el área de texto para mejorar el área de clic del botón?
Martin
@Martin, depende de cómo lo hagas. Y debería ser fácil hacer lo que quieras: simplemente envuelve lo que quieras (la cantidad de vistas que quieras) en una vista transparente encima de todas estas vistas y asigna un clic incluso a esa área.
inteist
0

Para expandir el área táctil genéricamente con muy pocas restricciones, use el siguiente código.

Le permite expandir el área táctil de lo dado viewdentro de la ancestorvista dada por lo dado expansionen píxeles. Puede elegir cualquier antepasado siempre que la vista dada esté en el árbol de diseño de antepasados.

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

Restricciones que aplican:

  • El área táctil no puede exceder los límites del ancestro de la vista, ya que el ancestro debe poder capturar el evento táctil para reenviarlo a la vista.
  • Solo uno TouchDelegatese puede establecer en a ViewGroup. Si desea trabajar con múltiples delegados táctiles, elija diferentes antepasados ​​o use un delegado táctil de composición como se explica en Cómo usar múltiples TouchDelegate .
Björn Kahlert
fuente
0

Como no me gustó la idea de esperar el pase de diseño solo para obtener el nuevo tamaño del rectángulo de TouchDelegate, busqué una solución diferente:

public class TouchSizeIncreaser extends FrameLayout {
    public TouchSizeIncreaser(@NonNull Context context, @Nullable AttributeSet 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;
    }
}

Y luego, en un diseño:

<ch.tutti.ui.util.TouchSizeIncreaser
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</ch.tutti.ui.util.TouchSizeIncreaser>

La idea es que TouchSizeIncreaser FrameLayout envolverá el Spinner (podría ser cualquier Vista secundaria) y reenviará todos los eventos táctiles capturados en su derecha a la Vista secundaria. Funciona para clics, la ruleta se abre incluso si se hace clic fuera de sus límites, no estoy seguro de cuáles son las implicaciones para otros casos más complejos.

Adi B
fuente