Czy istnieje przykład użycia TouchDelegate w systemie Android, aby zwiększyć rozmiar celu kliknięcia widoku?

82

Rozumiem, że jeśli masz widok, który jest zbyt mały, aby można go było łatwo dotknąć, powinieneś użyć TouchDelegate, aby zwiększyć klikalny obszar dla tego widoku.

Jednak wyszukiwanie przykładów użycia w Google powoduje, że wiele osób zadaje pytanie, ale niewiele odpowiedzi.

Czy ktoś zna właściwy sposób konfigurowania delegata dotykowego, aby widok, powiedzmy, zwiększył obszar klikalności o 4 piksele w każdym kierunku?

emmby
źródło
3
Oto dodatkowy dobry post na temat korzystania z TouchDelegate: groups.google.com/group/android-developers/browse_thread/thread/…
Mason Lee
Dobry poradnik: Powiększone obszary dotykowe
Thierry-Dimitri Roy

Odpowiedzi:

100

Zapytałem znajomego z Google, który pomógł mi dowiedzieć się, jak korzystać z TouchDelegate. Oto, co wymyśliliśmy:

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
źródło
16
A jeśli na jednym ekranie znajdują się dwa przyciski, do których chcesz to zrobić? Jeśli ponownie ustawisz opcjęTouchDelegate, spowoduje to usunięcie poprzedniego delegata dotykowego.
cottonBallPaws
2
Ten nie zadziałał dla mnie ... Zakładam, że delegat jest widokiem, do którego próbuję dodać TouchDelegate.
Kevin Parker
20
@AmokraneChentir Miałem ten sam problem. Zamiast używać TouchDelegates, mam klasę, która rozszerza przycisk. W tej klasie nadpisuję getHitRect. Możesz rozszerzyć prostokąt trafienia do rozmiaru widoku nadrzędnego. public void getHitRect (Rect outRect) {outRect.set (getLeft () - mPadding, getTop () - mPadding, getRight () + mPadding, getTop () + mPadding); }
Brendan Weinstein,
5
@Amokrane: Okazuje się, że zaproponowane przeze mnie rozwiązanie nie działa w ICS. Napisałem post na blogu na temat dwóch różnych strategii Touch Delegate brendanweinstein.me/2012/06/26/ ... i możesz pobrać przykładowy kod dla obu strategii na github.com/brendanw/Touch-Delegates/blob/master/src/com/ brendan /…
Brendan Weinstein
8
Według @ZsoltSafrany, bezpieczniejszym sposobem na to jest użycie OnGlobalLayoutListener i przypisanie delegata onGlobalLayout ().
greg7gkb
20

Udało mi się to osiągnąć dzięki wielu widokom (polom wyboru) na jednym ekranie, rysując w dużej mierze z tego posta na blogu . Zasadniczo bierzesz rozwiązanie emmby i stosujesz je indywidualnie do każdego przycisku i jego rodzica.

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

W moim przypadku miałem siatkę widoków obrazów z polami wyboru nałożonymi na górę i nazwałem metodę w następujący sposób:

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

Pracuje świetnie dla mnie.

Kyle Clegg
źródło
11

To rozwiązanie zostało opublikowane przez @BrendanWeinstein w komentarzach.

Zamiast wysyłać TouchDelegate, możesz zmienić getHitRect(Rect)swoją metodę View(w przypadku, gdy ją przedłużasz).

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
źródło
Ciekawy pomysł. Ale czy nie oznaczałoby to, że widoku nie można utworzyć z układu? Nie, czekaj - jeśli zaprojektuję nową klasę widoku i użyję jej w układzie. To może zadziałać. Warte spróbowania.
Martin,
10
@Martin to dobry pomysł. Niestety, getHitRect nie jest już wywoływany od ICS przez dispatchTouchEvent grepcode.com/file/repository.grepcode.com/java/ext/… Zastępowanie getHitRect będzie działać tylko dla piernika i poniżej.
Brendan Weinstein,
6

Podejście emmby nie zadziałało, ale po drobnych zmianach zadziałało:

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

Może to pozwoli zaoszczędzić komuś czas

wirus
źródło
to działa dla mnie. w rzeczywistości sprawia, że ​​cały rodzic wysyła zdarzenia kliknięć do dziecka. nie musi to być nawet bezpośredni rodzic (może to być rodzic rodzica).
programista Androida,
To prawdopodobnie działa, jeśli masz jeden widok do delegowania. co, jeśli masz, powiedzmy, 50 widoków, aby ulepszyć obszar dotykowy? - Jak na tym zrzucie ekranu: plus.google.com/u/0/b/112127498414857833804/ ... - Czy zatem TouchDelegate jest nadal odpowiednim rozwiązaniem?
Martin
2

Według komentarza @Mason Lee rozwiązało to mój problem. Mój projekt miał względny układ i jeden przycisk. Więc rodzic to -> układ, a dziecko to -> przycisk.

Oto przykładowy kod google link google

W przypadku skreślenia jego bardzo cennej odpowiedzi zamieszczam tutaj jego odpowiedź.

Niedawno zapytano mnie, jak używać TouchDelegate. Sam byłem trochę zardzewiały i nie mogłem znaleźć na ten temat dobrej dokumentacji. Oto kod, który napisałem po krótkiej próbie i błędach. touch_delegate_view jest prostym RelativeLayout o identyfikatorze touch_delegate_root. Zdefiniowałem z jednym dzieckiem układu, przyciskiem delegated_button. W tym przykładzie rozszerzam klikalny obszar przycisku do 200 pikseli powyżej górnej części mojego przycisku.

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

Pozdrawiam, Justin Android Team @ Google

Kilka słów ode mnie: jeśli chcesz rozszerzyć lewą stronę, podajesz wartość z minusem, a jeśli chcesz rozszerzyć prawą stronę obiektu, podajesz wartość z plusem. Działa to tak samo z górą i dołem.

martwa ryba
źródło
Magiczna wskazówka wydaje się być jednym przyciskiem - jakoś mam wrażenie, że TouchDelegate nie nadaje się do użytku w przypadku wielu przycisków. Czy możesz to potwierdzić?
Martin
1

Czy nie jest to lepszy pomysł na nadanie Padding temu konkretnemu komponentowi (Button).

user2454519
źródło
1
To sprawiłoby, że widok byłby większy. Co się stanie, jeśli nad przyciskiem znajduje się tekst nieklikalny i chcesz, aby obszar tego tekstu był używany do poprawy dokładności kliknięcia przycisku. - Jak na tym zrzucie
Martin
1

Trochę spóźniony na imprezę, ale po wielu badaniach używam teraz:

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

Jest to lepsze niż przyjęte rozwiązanie, ponieważ umożliwia również ustawienie TouchDelegate w dowolnym widoku nadrzędnym, a nie tylko w widoku nadrzędnym.

W przeciwieństwie do zaakceptowanego rozwiązania aktualizuje również TouchDelegate za każdym razem, gdy nastąpi zmiana w układzie widoku przodka.

Stephen Talley
źródło
0

Jeśli nie chcesz robić tego programowo, po prostu utwórz przezroczysty obszar wokół obrazu. Jeśli używasz obrazu jako tła przycisku (widoku).

Szary obszar może być przezroczysty, aby zwiększyć obszar dotyku.

wprowadź opis obrazu tutaj

Vinayak Bevinakatti
źródło
1
Czy nie stworzyłoby to wokół widoku pustej przestrzeni, której nie można wykorzystać do niczego innego? A co, jeśli chcesz wyświetlić tekst informacyjny, zamknij przycisk i użyj obszaru tekstowego, aby ulepszyć obszar kliknięcia przycisku?
Martin,
Nie mówię, że to najlepsze i ostateczne podejście. Ale będzie pasować do wielu scenariuszy, w których chcesz wyświetlić obraz małego przycisku, ale obszar dotykowy ma zostać rozszerzony. W twoim przypadku trudno jest nadać pierwszeństwo przyciskowi, gdy klikniesz inne widoki (widok tekstu), wszystko razem jest innym wyzwaniem.
Vinayak Bevinakatti
0

W większości przypadków można zawinąć widok, który wymaga większego obszaru dotykowego, w innym widoku bez głowy (sztuczny przezroczysty widok) i dodać dopełnienie / margines do widoku otoki i dołączyć kliknięcie / dotknięcie nawet do widoku otoki zamiast oryginalnego widoku, który musi mieć większy obszar dotyku.

inteista
źródło
Czy nie stworzyłoby to wokół widoku pustej przestrzeni, której nie można wykorzystać do niczego innego? A co, jeśli chcesz wyświetlić tekst informacyjny, zamknij przycisk i użyj obszaru tekstowego, aby ulepszyć obszar kliknięcia przycisku?
Martin,
@ Martin, to zależy od tego, jak to zrobisz. I powinno być łatwe do zrobienia, co chcesz - po prostu zawiń, co chcesz (dowolną liczbę widoków) w przezroczystym widoku na wszystkie te widoki i przypisz kliknięcie nawet do tego obszaru.
inteist
0

Aby ogólnie rozszerzyć obszar dotykowy z kilkoma ograniczeniami, użyj następującego kodu.

Pozwala rozszerzyć obszar dotykowy danego vieww danym ancestorwidoku o podaną expansionw pikselach. Możesz wybrać dowolnego przodka, o ile dany widok znajduje się w drzewie układów przodków.

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

Obowiązujące ograniczenia:

  • Obszar dotykowy nie może przekraczać granic przodka widoku, ponieważ przodek musi być w stanie złapać zdarzenie dotyku, aby przekazać je dalej do widoku.
  • Tylko jeden TouchDelegatemoże być ustawiony na ViewGroup. Jeśli chcesz pracować z wieloma delegatami dotykowymi, wybierz różnych przodków lub użyj delegata dotykowego komponującego, jak wyjaśniono w Jak korzystać z funkcji Multiple TouchDelegate .
Björn Kahlert
źródło
0

Ponieważ nie podobał mi się pomysł czekania na przejście układu tylko po to, aby uzyskać nowy rozmiar prostokąta TouchDelegate, wybrałem inne rozwiązanie:

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

A potem w układzie:

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

Chodzi o to, że TouchSizeIncreaser FrameLayout zawinie Spinner (może to być dowolny widok podrzędny) i przekaże wszystkie zdarzenia dotyku przechwycone w jego trafieniu prosto do widoku podrzędnego. Działa na kliknięcia, pokrętło otwiera się nawet po kliknięciu poza jego granicami, nie jestem pewien, jakie są konsekwencje dla innych bardziej złożonych przypadków.

Adi B
źródło