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?
Odpowiedzi:
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)); } });
źródło
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.
źródło
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; } }
źródło
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
źródło
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ź.
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.
źródło
Czy nie jest to lepszy pomysł na nadanie Padding temu konkretnemu komponentowi (Button).
źródło
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.
źródło
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.
źródło
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.
źródło
Aby ogólnie rozszerzyć obszar dotykowy z kilkoma ograniczeniami, użyj następującego kodu.
Pozwala rozszerzyć obszar dotykowy danego
view
w danymancestor
widoku o podanąexpansion
w 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:
TouchDelegate
może być ustawiony naViewGroup
. 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 .źródło
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.
źródło