Jak zwiększyć obszar trafienia przycisku Androida bez skalowania tła?

83

Mam przycisk z niestandardowym rysunkiem.

<Button
    android:layout_width="22dip"
    android:layout_height="23dip"
    android:background="@drawable/triangle" />

Element do rysowania to trójkąt z przezroczystym tłem.

|\
| \
|__\

Trudno mi dotknąć tego przycisku. Po pierwsze, jest stosunkowo mały. Po drugie, przezroczyste piksele nie są dostępne. Chciałbym zachować ten sam rozmiar do rysowania, ale nadać obszarowi uderzenia kwadratowy kształt dwa razy większy niż trójkąt.

_____________
|            |
|    |\      |
|    | \     |
|    |__\    |
|____________|
JoJo
źródło
1
Margines również nie zwiększył obszaru trafienia.
JoJo
Spróbuj użyć dopełnienia. To na pewno zadziała.
Arjun Vyas

Odpowiedzi:

68

możesz użyć TouchDelegate API.

final View parent = (View) button.getParent();  // button: the view you want to enlarge hit area
parent.post( new Runnable() {
    public void run() { 
        final Rect rect = new Rect(); 
        button.getHitRect(rect); 
        rect.top -= 100;    // increase top hit area
        rect.left -= 100;   // increase left hit area
        rect.bottom += 100; // increase bottom hit area           
        rect.right += 100;  // increase right hit area
        parent.setTouchDelegate( new TouchDelegate( rect , button)); 
    } 
}); 
Morris Lin
źródło
1
W jaki sposób wartość jest kiedykolwiek ustawiona na prostą tutaj? button.getHitRect (rect) nie zwraca żadnej wartości!
amitavk
@ amitav13 rectjest przekazywany jako parametr.
not_again_stackoverflow
hej, wartość 100, której używasz, czy powinna to być wartość dp, którą otrzymam z folderu wymiarów? czy rodzaj wartości zmieni się w zależności od gęstości, prawda?
j2emanue
@ j2emanue Zgadza się. Możesz również obliczyć to programowo stackoverflow.com/a/8399445/2523667
Nicolas
45

Chcesz "dopełnienia". Spowoduje to umieszczenie przestrzeni wewnątrz widoku. Margines wyprowadzi przestrzeń na zewnątrz, co nie zwiększy obszaru trafienia.

 <Button
    android:layout_width="22dip"
    android:layout_height="23dip"
    android:background="@drawable/triangle"
    android:padding="10dp" />
kwahn
źródło
testowałem tutaj na urządzeniu i emulatorze, nie działa. przetestowałeś to?
LiangWang
18
Dopełnienie jest dodawane wewnątrz layout_width / layout_height, a nie na zewnątrz. Tak więc ten przycisk nadal ma rozmiar 22x23. Więc ten nie pomaga.
Simon Warta
1
To działa świetnie. Dopełnienie rozszerzyło obszar, na który można było kliknąć, zgodnie z życzeniem i pozostawiło mój obraz tego samego rozmiaru, o co poprosił JoJo (oryginalny autor).
Alyoshak
Dlaczego ta odpowiedź spotkała się z tak wieloma pozytywnymi opiniami, jasne jest, że wypełnienie treści wewnątrz granic, a nie na zewnątrz ?!
Farid
1
W związku z moim poprzednim komentarzem, myślę, że to również powiększy tło. Jeśli tak, być może możesz umieścić tło wewnątrz „wstawki” do rysowania. PS. Wolę ten niż TouchDelegate, ponieważ TouchDelegate musi zadzierać z przodkiem i staje się bardziej skomplikowane, jeśli widok dziecka się porusza ...
Henry
24

Musisz użyć klasy TouchDelegate, która jest zdefiniowana w dokumentach interfejsu API jako „Klasa pomocnicza do obsługi sytuacji, w których chcesz, aby widok miał większy obszar dotykowy niż jego rzeczywiste granice widoku”

http://developer.android.com/reference/android/view/TouchDelegate.html

LuxuryMode
źródło
@mxcl, nie widzę w Twoim komentarzu niczego, co spowodowałoby, że ta odpowiedź byłaby nieprawidłowa lub „zła”.
dbm
7
Wyjaśnienie TouchDelegatei fragment kodu: developer.android.com/training/gestures/viewgroup.html#delegate
Brais Gabin
16

Wolę takie rozwiązanie:

Po prostu dodaj dodatnie dopełnienie i ujemne marginesy wewnątrz pliku zasobów układu:

<some.View
  ..
  android:padding="12dp"
  android:margin="-12dp"
  .. />

Jest to bardzo mała zmiana w porównaniu z wykorzystaniem TouchDelegates. Nie chcę też uruchamiać Runnable do dodawania klikalnego obszaru wewnątrz mojego kodu Java.

Jedna rzecz, którą możesz rozważyć, jeśli widok powinien być doskonały w pikselach: wypełnienie nie zawsze może być równe marginesowi . Wyściółka powinien zawsze być tam dokładnie określone. Marża zależy od okolicznych widoków i zostaną przesunięte z marginesu wartości innych poglądów.

Timo Bähr
źródło
pokochałem ten: D
Ayman Salah
Nie mogłem zmusić tego do pracy. Mam dwa teksty obok siebie w LinearLayout i chcę, aby obszar po prawej stronie miał nieco większy obszar dotykowy niż sam tekst ... ale użycie tych 2 dodatkowych linii sprawia, że ​​mój tekst jest przesunięty, co nie jest moim wyglądem idę
Kibi
@Kibi Wstawianie pojedynczego widoku (tutaj TextView) do pliku LinearLayoutnie powinno być potrzebne. Niemniej jednak, czy próbowałeś dodać te dwie linie do swojego LinearLayout? Jak stwierdzono wcześniej, margines zależy od otaczających widoków.
Timo Bähr,
Dzięki @ TimoBähr, ale (początkowo) nie chciałem, aby cały LinearLayout był klikalny, ponieważ tekst po lewej nie miał być klikalny ... w końcu ja podniosłem i oszalałem cały LinearLayout Clickable i oczywiście to Pracuje.
Kibi,
9

Na podstawie odpowiedzi utworzyłem funkcję rozszerzenia kotlin, aby pomóc w tym.

/**
 * Increase the click area of this view
 */
fun View.increaseHitArea(dp: Float) {
    // increase the hit area
    val increasedArea = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().displayMetrics).toInt()
    val parent = parent as View
    parent.post {
        val rect = Rect()
        getHitRect(rect)
        rect.top -= increasedArea
        rect.left -= increasedArea
        rect.bottom += increasedArea
        rect.right += increasedArea
        parent.touchDelegate = TouchDelegate(rect, this)
    }
}

William Reed
źródło
6

po prostu dodając wypełnienie do przycisku

 <Button
android:layout_width="wrap_contant"
android:layout_height="wrap_contant"
android:background="@drawable/triangle"
android:padding="20dp" />

W ten sposób ... twój przycisk weźmie wysokość i szerokość obrazu trójkąta i doda wypełnienie o 20 dp do przycisku bez rozciągania samego obrazu. a jeśli chcesz mieć minimalną szerokość lub minimalną wysokość, możesz użyć tagów minWidthiminHeight

Nitesh
źródło
6

W przypadku korzystania z powiązania danych. Możesz użyć adaptera do wiązania.

@BindingAdapter("increaseTouch")
fun increaseTouch(view: View, value: Float) {
    val parent = view.parent
    (parent as View).post({
        val rect = Rect()
        view.getHitRect(rect)
        val intValue = value.toInt()
        rect.top -= intValue    // increase top hit area
        rect.left -= intValue   // increase left hit area
        rect.bottom += intValue // increase bottom hit area
        rect.right += intValue  // increase right hit area
        parent.setTouchDelegate(TouchDelegate(rect, view));
    });
}

a następnie możesz użyć atrybutu w widokach takich jak ten

increaseTouch="@{8dp}"
logcat
źródło
2

Nie wiem na pewno, co masz na myśli, mówiąc „bez zwiększania tła do rysowania”. Jeśli masz na myśli, że po prostu nie chcesz, aby twoje rysowanie się rozciągało, jedną z dostępnych opcji jest wzięcie tła png i dodanie do niego dodatkowej przezroczystej ramki pikselowej. To zwiększyłoby twoją strefę uderzenia, ale nie rozciągnęłoby twojego obrazu.

Jeśli jednak masz na myśli, że nie chcesz w ogóle zmieniać tego do rysowania, myślę, że jedyną opcją jest użycie większych, zakodowanych na stałe wartości wysokości i szerokości.

FoamyGuy
źródło
Chodzi mi o to, że potrzebuję grafiki przycisku, aby pojawiała się X na Y, ale obszar trafienia powinien wynosić X + 10 na Y + 10. Nie sądzę, aby przezroczyste piksele dodawały się do obszaru uderzenia. Moja grafika ma trójkątny kształt. Kiedy dotykam przezroczystych obszarów, onClick nie uruchamia się.
JoJo
Wiem na pewno, że przezroczyste piksele w tle img dodadzą się do strefy trafienia. Użyłem go już wcześniej do stworzenia niewidzialnego przycisku. Być może nie chodzi o dodawanie rozmiaru, ponieważ na stałe zakodowałeś wysokość i szerokość? Może spróbuj dla nich wrap_content.
FoamyGuy
To dziwne. Wysadziłem przycisk, aby wypełnić cały ekran tylko do testów. Kiedy dotykam nieprzezroczystych pikseli, LogCat drukuje mój dziennik onClick. Kiedy dotykam przezroczystych obszarów, nie rejestruje się.
JoJo
Ponadto, jeśli mocno naciskasz na ekran, tak że powierzchnia Twojego palca jest większa niż przycisk, onClick nie zostanie wyzwolony. Muszę ostrożnie dotknąć przycisku, aby wywołać kliknięcie. Czy jest jakaś właściwość, która reguluje ten „syndrom grubego palca”?
JoJo
2

spróbuj z tym

  <Button
        android:id="@+id/btn_profile"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:background="@android:color/transparent"
        android:drawableBottom="@drawable/moreoptionicon"
        android:onClick="click"
        android:paddingBottom="15dp"
        android:visibility="visible" />
shailesh
źródło
Podobał mi się ten pomysł… czysty… prosty… możesz pracować z wyściółką i rysować, aby zmieścił się w środku… fajnie…
Guilherme Oliveira
1

To zadziałało dla mnie:

public void getHitRect(Rect outRect) {
    outRect.set(getLeft(), getTop(), getRight(), getBottom() + 30);
}

Chociaż naprawdę powinieneś przekonwertować 30, aby zanurzyć lub zrobić procent wysokości przycisku.

mxcl
źródło
Potrzebuje wyjaśnienia. OP wysłał fragment XML układu dla przycisku (który byłby częścią XML układu niektórych widoków). Ale zakładam, że powyższa metoda musi być skojarzona z Button, a nie z klasą widoku zawierającą przycisk? Jak skojarzysz powyższą metodę z tym przyciskiem?
ToolmakerSteve
1

Potrzebowałem tylko tego samego: klikalny obszar większy niż obraz przycisku. Zawijałem go do FrameLayout o rozmiarze większym niż tło do rysowania i zmieniłem wewnętrzny znacznik Button na TextView. Następnie skierowałem moduł obsługi kliknięć do pracy z tym FrameLayout, a nie z wewnętrznym TextView. Wszystko działa dobrze!

Dmitri Novikov
źródło
Mimo że jest okropne, jest to najlepsze rozwiązanie, jakie oferuje Android.
i_am_jorf
1

Możesz użyć przezroczystego przycisku w ten sposób ...

<Button
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="@android:color/transparent" /> 

możesz zwiększyć lub zmniejszyć rozmiar przycisku zgodnie z wymaganiami i użyć enter code hereImageView w górę przycisku ....

<ImageView
    android:layout_width="22dip"
    android:layout_height="23dip"
    android:background="@drawable/triangle" />
Yatendra Sen
źródło
1

Utworzyłem w tym celu funkcję najwyższego poziomu w Kotlin:

fun increaseTouchArea(view: View, increaseBy: Int) {
    val rect = Rect()
    view.getHitRect(rect)
    rect.top -= increaseBy    // increase top hit area
    rect.left -= increaseBy   // increase left hit area
    rect.bottom += increaseBy // increase bottom hit area
    rect.right += increaseBy  // increase right hit area
    view.touchDelegate = TouchDelegate(rect, view)
}
Igor Konyukhov
źródło
2
Można utworzyć funkcję rozszerzenia dla Viewzamiast
Vadim Kotov
0

Do tego celu użyłem ImageButton. W definicji XML zobaczysz te dwa atrybuty:

  • android: background - rysowany do użycia jako tło.
  • android: scr - ustawia drawable jako zawartość tego ImageView.

Mamy więc dwie rysunki: tło i źródło. W twoim przykładzie źródłem będzie triagle (drawable / trianlge):

|\
| \
|__\

Tło jest kwadratowe (do rysowania / kwadratowe):

_____________
|            |
|            |
|            |
|            |
|____________|

Oto przykład ImageButton xml:

<ImageButton
   ...
   android:src="@drawable/triangle"
   android:background="@drawable/square">

Wynik:

_____________
|            |
|    |\      |
|    | \     |
|    |__\    |
|____________|

Również do rysowania jako kwadrat, może mieć kilka różnych stanów (wciśnięty, zogniskowany). I możesz zwiększyć rozmiar do rysowania tła za pomocą wyściółek.

Na wszelki wypadek, oto przykład tła, które można wyciągnąć z paska akcji Android Compat:

<?xml version="1.0" encoding="utf-8"?>    
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
    <item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/abc_list_selector_disabled_holo_dark" />
    <item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/abc_list_selector_disabled_holo_dark" />
    <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/abc_list_selector_background_transition_holo_dark" />
    <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/abc_list_selector_background_transition_holo_dark" />
    <item android:state_focused="true" android:drawable="@drawable/abc_list_focused_holo" />
    <item android:drawable="@android:color/transparent" />
 </selector>
molokoka
źródło
Z jakiegoś powodu mój src nie jest używany, nawet jeśli tło jest tylko przezroczyste.
MLProgrammer-CiM
0

Możesz ustawić dopełnienie dla widoku, czyli swojego przycisku.

<Button
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@android:color/transparent"
        android:padding="15dp" />

Mam nadzieję, że to działa dla Ciebie.

Mayank Bhatnagar
źródło
0

Więc właściwa odpowiedź, dodaj wypełnienie do obszaru dotykowego, powiększ go ORAZ zmień obraz z tła na srcCompact .

<Button
    android:layout_width="22dip"
    android:layout_height="23dip"
    android:padding="6dp"
    app:srcCompat="@drawable/triangle"/>

Aby użyć app: scrCompat, musisz dodać xmlns:app="http://schemas.android.com/apk/res-auto"do głównego / nadrzędnego elementu w xml.

przykład kompletny:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/height_btn_bottom_touch_area"
android:background="@color/bg_white"
android:clipToPadding="false"
android:elevation="7dp">

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:stateListAnimator="@animator/selected_raise"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" >

    <ImageView
        android:id="@+id/bottom_button_bg"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:contentDescription=
            "@string/service_request_background_contentDescription"
        android:elevation="1dp"
        android:padding="6dp"
        app:srcCompat="@drawable/btn_round_blue"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" >
    </ImageView>

    <TextView
        android:id="@+id/bottom_button_text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:elevation="1dp"
        android:gravity="center"
        android:text="@string/str_continue_save"
        android:textColor="@color/text_white"
        android:textSize="@dimen/size_text_title"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
Canato
źródło
0

Samo dodanie dopełnienia powodowało, że moje pole wyboru nie było wyśrodkowane. Poniższe działały dla mnie, jednak określiłem stały rozmiar pola wyboru (zgodnie ze specyfikacją mojego interfejsu użytkownika). Pomysł polegał na dodaniu wypełnienia po lewej, a nie po prawej stronie. Dodałem również wyściółkę na górze i na dole, zwiększając cel dotykowy i nadal utrzymując pole wyboru wyśrodkowane.

<CheckBox
        android:id="@+id/checkbox"
        android:layout_width="30dp"
        android:layout_height="45dp"
        android:paddingLeft="15dp"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:button="@drawable/checkbox"
        android:checked="false"
        android:gravity="center"
       />
RPM
źródło
0

Myślę, że poprawną odpowiedzią powinno być użycie ImageButton zamiast Button. Ustaw trójkąt do rysowania jako „src” ImageButton i ustaw rozmiar (layout_width i layout_height), jak chcesz, aby ułatwić dotyk, i ustaw ScaleType na środek, aby zachować rozmiar trójkąta.

Henz
źródło
0

Szczerze myślę, że najłatwiej jest to zrobić: -

Powiedzmy, że rozmiar obrazu to 26dp * 26dp i chcesz, aby obszar działania wynosił 30dp * 30dp, po prostu dodaj wypełnienie 2dp do swojego ImageButton / Button ORAZ ustaw wymiar na 30dp * 30dp


Oryginalny

<ImageButton
    android:layout_width="26dp"
    android:layout_height="26dp"
    android:src="@drawable/icon"/>

Większy obszar trafienia

<ImageButton
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:padding="2dp"
    android:src="@drawable/icon_transcript"/>
daisura99
źródło
0

Najprostszym rozwiązaniem byłoby zwiększenie width and heightpozycji (np. Obrazu, przycisku itp.) I dodanie padding; Więc klikalny obszar jest większy, ale wygląda w oryginalnym rozmiarze.

Przykład:

<Button
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:padding="8dp"
    android:background="@drawable/triangle" />

Gratulacje, udało Ci się osiągnąć ten sam rozmiar przycisku, ale dwukrotnie większy, który można kliknąć!

_____________
|            |
|    |\      |
|    | \     |
|    |__\    |
|____________|
unobatbayar
źródło
-2
<Button
    android:layout_width="32dp"
    android:layout_height="36dp"
    android:layout_margin="5dp"
    android:background="@drawable/foo" 
/>

Rozmiar tła to nadal 22x26dp. Po prostu dodaj margines.

Terence Lui
źródło