Gibt es ein Beispiel für die Verwendung eines TouchDelegate in Android, um das Klickziel einer Ansicht zu vergrößern?

82

Mein Verständnis ist, dass Sie, wenn Sie eine Ansicht haben, die zu klein ist, um sie leicht zu berühren, ein TouchDelegate verwenden sollten, um den anklickbaren Bereich für diese Ansicht zu vergrößern.

Bei der Suche nach Verwendungsbeispielen bei Google werden jedoch mehrere Personen gestellt, die die Frage stellen, aber nur wenige Antworten.

Kennt jemand die richtige Methode, um einen Touch-Delegaten für eine Ansicht einzurichten, um beispielsweise den anklickbaren Bereich in alle Richtungen um 4 Pixel zu vergrößern?

emmby
quelle
3
Hier ist ein weiterer guter Beitrag zur Verwendung von TouchDelegate: groups.google.com/group/android-developers/browse_thread/thread/…
Mason Lee
Gutes Tutorial: Vergrößerte berührbare Bereiche
Thierry-Dimitri Roy

Antworten:

100

Ich habe einen Freund bei Google gefragt, der mir helfen konnte, die Verwendung von TouchDelegate herauszufinden. Folgendes haben wir uns ausgedacht:

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
quelle
16
Was ist, wenn auf einem Bildschirm zwei Schaltflächen vorhanden sind, mit denen Sie dies tun möchten? Wenn Sie TouchDelegate erneut festlegen, wird der vorherige Touch-Delegat gelöscht.
CottonBallPaws
2
Dieser hat bei mir nicht funktioniert ... Ich gehe davon aus, dass der Delegat die Ansicht ist, zu der ich das TouchDelegate hinzufügen möchte.
Kevin Parker
20
@ AmokraneChentir Ich hatte das gleiche Problem. Anstatt TouchDelegates zu verwenden, habe ich eine Klasse, die die Schaltfläche erweitert. In dieser Klasse überschreibe ich getHitRect. Sie können das Trefferrechteck auf die Größe der übergeordneten Ansicht erweitern. public void getHitRect (Rect outRect) {outRect.set (getLeft () - mPadding, getTop () - mPadding, getRight () + mPadding, getTop () + mPadding); }
Brendan Weinstein
5
@ Amokrane: Es stellt sich heraus, dass die von mir angebotene Lösung in ICS nicht funktioniert. Ich habe einen Blog-Beitrag über zwei verschiedene Touch Delegate-Strategien geschrieben. Brendanweinstein.me/2012/06/26/… und Sie können Beispielcode für beide Strategien unter github.com/brendanw/Touch-Delegates/blob/master/src/com/ erhalten. brendan /…
Brendan Weinstein
8
Laut @ZsoltSafrany ist dies sicherer über einen OnGlobalLayoutListener und die Zuweisung des Delegaten onGlobalLayout ().
Greg7gkb
20

Ich konnte dies mit mehreren Ansichten (Kontrollkästchen) auf einem Bildschirm erreichen, die größtenteils aus diesem Blog-Beitrag stammen . Grundsätzlich nehmen Sie die Lösung von emmby und wenden sie einzeln auf jede Schaltfläche und deren Eltern an.

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

In meinem Fall hatte ich eine Rasteransicht von Bildansichten mit überlagerten Kontrollkästchen und rief die Methode wie folgt auf:

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

Arbeitet großartig für mich.

Kyle Clegg
quelle
11

Diese Lösung wurde von @BrendanWeinstein in Kommentaren gepostet.

Anstatt eine zu senden TouchDelegate, können Sie getHitRect(Rect)Ihre Methode überschreiben View(falls Sie eine erweitern).

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
quelle
Interessante Idee. Aber würde das nicht bedeuten, dass die Ansicht nicht aus einem Layout erstellt werden kann? Nein, warte - wenn ich eine neue Ansichtsklasse entwerfe und diese im Layout verwende. Das könnte funktionieren. Einen Versuch wert.
Martin
10
@ Martin das ist die richtige Idee. Leider wird getHitRect ab ICS nicht mehr von dispatchTouchEvent aufgerufen. Grepcode.com/file/repository.grepcode.com/java/ext/… Das Überschreiben von getHitRect funktioniert nur für Lebkuchen und darunter.
Brendan Weinstein
6

emmbys Ansatz hat bei mir nicht funktioniert, aber nach ein paar Änderungen hat es funktioniert:

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

Vielleicht kann es jemandem Zeit sparen

Virus
quelle
Das hat bei mir funktioniert. Tatsächlich sendet das gesamte übergeordnete Element Klickereignisse an das untergeordnete Element. Es muss nicht einmal das direkte Elternteil sein (kann ein Elternteil eines Elternteils sein).
Android-Entwickler
Das funktioniert wahrscheinlich, wenn Sie eine Ansicht delegieren müssen. Was ist, wenn Sie beispielsweise 50 Ansichten haben, um den Berührungsbereich zu verbessern? - Wie in diesem Screenshot: plus.google.com/u/0/b/112127498414857833804/… - Ist ein TouchDelegate dann noch eine geeignete Lösung?
Martin
2

Laut dem Kommentar von @Mason Lee hat dies mein Problem gelöst. Mein Projekt hatte ein relatives Layout und eine Schaltfläche. Eltern ist also -> Layout und Kind ist -> eine Schaltfläche.

Hier ist ein Google Link Beispiel Google Code

Im Falle des Löschens seiner sehr wertvollen Antwort habe ich hier seine Antwort eingefügt.

Ich wurde kürzlich gefragt, wie man ein TouchDelegate benutzt. Ich war selbst ein bisschen verrostet und konnte keine gute Dokumentation dazu finden. Hier ist der Code, den ich nach ein wenig Versuch und Irrtum geschrieben habe. touch_delegate_view ist ein einfaches RelativeLayout mit der ID touch_delegate_root. Ich habe mit einem einzigen untergeordneten Element des Layouts die Schaltfläche delegated_button definiert. In diesem Beispiel erweitere ich den anklickbaren Bereich der Schaltfläche auf 200 Pixel über dem oberen Rand meiner Schaltfläche.

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

Prost, Justin Android Team @ Google

Einige Worte von mir: Wenn Sie die linke Seite erweitern möchten, geben Sie einen Wert mit Minus an, und wenn Sie die rechte Seite des Objekts erweitern möchten, geben Sie einen Wert mit Plus an. Dies funktioniert genauso mit oben und unten.

toter Fisch
quelle
Der magische Hinweis hier scheint eine Schaltfläche zu sein - Irgendwie habe ich das Gefühl, dass TouchDelegate für mehrere Schaltflächen unbrauchbar ist. Können Sie das bestätigen?
Martin
1

Ist es nicht die bessere Idee, dieser bestimmten Komponente (Button) Padding zu geben?

user2454519
quelle
1
Das würde die Ansicht vergrößern. Was ist, wenn Sie einen nicht klickbaren Text über einer Schaltfläche haben und möchten, dass der Bereich dieses Textes verwendet wird, um die Klickgenauigkeit für die Schaltfläche zu verbessern. - Wie in diesem Screenshot: plus.google.com/u/0/b/112127498414857833804/…
Martin
1

Ein bisschen spät zur Party, aber nach viel Recherche benutze ich jetzt:

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

Dies ist besser als die akzeptierte Lösung, da damit auch ein TouchDelegate für jede Vorgängeransicht festgelegt werden kann, nicht nur für die übergeordnete Ansicht.

Im Gegensatz zur akzeptierten Lösung wird das TouchDelegate auch aktualisiert, wenn sich das Layout der Vorgängeransicht ändert.

Stephen Talley
quelle
0

Wenn Sie dies nicht programmgesteuert tun möchten, erstellen Sie einfach einen transparenten Bereich um das Bild. Wenn Sie das Bild als Hintergrund für die Schaltfläche (Ansicht) verwenden.

Der graue Bereich kann transparent sein, um den Berührungsbereich zu vergrößern.

Geben Sie hier die Bildbeschreibung ein

Vinayak Bevinakatti
quelle
1
Würde das nicht einen leeren Raum um die Ansicht schaffen, der für nichts anderes verwendet werden kann? Was ist, wenn Sie Informationstext anzeigen möchten, schließen Sie eine Schaltfläche und verwenden Sie den Textbereich, um den Klickbereich für Schaltflächen zu erweitern?
Martin
Ich sage nicht, dass es der beste und letzte Ansatz ist. Es passt jedoch in viele Szenarien, in denen Sie ein winziges Schaltflächenbild anzeigen möchten, aber den zu erweiternden Berührungsbereich. In Ihrem Fall ist es schwierig, der Schaltfläche Vorrang einzuräumen, wenn Sie auf andere Ansichten (Textansicht) klicken. Alles zusammen ist eine andere Herausforderung.
Vinayak Bevinakatti
0

In den meisten Fällen können Sie die Ansicht, für die ein größerer Berührungsbereich erforderlich ist, in eine andere kopflose Ansicht (künstliche transparente Ansicht) einbinden, der Wrapper-Ansicht Polster / Rand hinzufügen und das Klicken / Berühren sogar an die Wrapper-Ansicht anhängen, anstatt an die ursprüngliche Ansicht muss einen größeren Berührungsbereich haben.

Inteist
quelle
Würde das nicht einen leeren Raum um die Ansicht schaffen, der für nichts anderes verwendet werden kann? Was ist, wenn Sie Informationstext anzeigen möchten, schließen Sie eine Schaltfläche und verwenden Sie den Textbereich, um den Klickbereich für Schaltflächen zu erweitern?
Martin
@ Martin, es kommt darauf an, wie du es machst. Und es sollte einfach sein, alles zu tun, was Sie wollen - wickeln Sie einfach alles, was Sie wollen (wie viele Ansichten Sie wollen) in eine transparente Ansicht über all diese Ansichten und weisen Sie diesem Bereich sogar einen Klick zu.
Inteist
0

Verwenden Sie den folgenden Code, um den Touch-Bereich mit wenigen Einschränkungen generisch zu erweitern.

Damit können Sie den Berührungsbereich des Gegebenen viewinnerhalb der gegebenen ancestorAnsicht um die gegebenen expansionin Pixel erweitern. Sie können einen beliebigen Vorfahren auswählen, solange sich die angegebene Ansicht im Layoutbaum der Vorfahren befindet.

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

Einschränkungen, die gelten:

  • Der Berührungsbereich darf die Grenzen des Vorfahren der Ansicht nicht überschreiten, da der Vorfahr das Berührungsereignis abfangen muss, um es an die Ansicht weiterzuleiten.
  • Es kann nur einer TouchDelegateauf a gesetzt werden ViewGroup. Wenn Sie mit mehreren Touch-Delegaten arbeiten möchten, wählen Sie verschiedene Vorfahren aus oder verwenden Sie einen komponierenden Touch-Delegaten, wie unter Verwenden mehrerer TouchDelegate erläutert .
Björn Kahlert
quelle
0

Da mir die Idee, auf den Layoutdurchlauf zu warten, um die neue Größe des TouchDelegate-Rechtecks ​​zu erhalten, nicht gefallen hat, habe ich mich für eine andere Lösung entschieden:

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

Und dann in einem Layout:

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

Die Idee ist, dass TouchSizeIncreaser FrameLayout den Spinner (kann eine beliebige untergeordnete Ansicht sein) umschließt und alle Berührungsereignisse, die in seinem Treffer erfasst wurden, direkt an die untergeordnete Ansicht weiterleitet. Es funktioniert für Klicks. Der Spinner öffnet sich auch dann, wenn er außerhalb seiner Grenzen geklickt wird. Er ist sich nicht sicher, welche Auswirkungen dies auf andere komplexere Fälle hat.

Adi B.
quelle