How to increase hit area of Android button without scaling background?

Android

Android Problem Overview


I have a button with a custom drawable.

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

The drawable is a triangle with transparent background.

|\
| \
|__\

I find this button hard to tap. First, it's relatively small. Second, the transparent pixels are not tappable. I would like to keep the drawable the same size, but make the hit area a square shape twice the size of the triangle.

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

Android Solutions


Solution 1 - Android

you can use 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)); 
    } 
}); 

Solution 2 - Android

You want "padding" It will put the space inside the view. Margin will put the space outside, which will not increase the hit area.

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

Solution 3 - Android

You need to use a TouchDelegate, which is defined in the API docs as "Helper class to handle situations where you want a view to have a larger touch area than its actual view bounds"

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

Solution 4 - Android

I prefer this solution:

Simply add a positive padding and a negative marging inside the layout resource file:

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

This is a very small change in comparison to the usage of TouchDelegates. I also don't want to run a Runnable for adding the clickable area inside my Java code.

One thing you might consider if the view should be pixel-perfect: padding cannot always equal to margin. The padding should always be there exactly as specified. The margin depends on the surrounding views and will be offset with the margin values of other views.

Solution 5 - Android

Based on answer I created a kotlin extension function to help with it.

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

Solution 6 - Android

In case you are using Data Binding. You can use binding adapter.

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

and then you can use attribute on views like this

increaseTouch="@{8dp}"

Solution 7 - Android

simply by adding padding to the button

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

So by doing this.. your button will take the height and width of the triangle image and will add the 20dp padding to the button without stretching the image itself . and if you want some minimum width or some hinimum height you can use the minWidth and minHeight tags

Solution 8 - Android

I don't know for sure what you mean by "without also increasing the background drawable." If you mean that you just don't want your drawable to get stretched then one option you have is take your background png and add an extra transparent pixel border on to it. That would increase your hit zone but wouldn't stretch your image.

If however you mean that you don't want to change that drawable at all then I think your only option is use larger hard coded values for height and width.

Solution 9 - Android

try with this

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

Solution 10 - Android

See https://stackoverflow.com/questions/2949407/can-i-increase-a-buttons-onclick-area-programmatically. This looks like the easiest way to me for sure. Even includes a bit on animating only the actual clickable object while allowing the background to receive clicks. I'll be applying this to my own code soon.

Solution 11 - Android

This worked for me:

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

Though really you should convert the 30 to dip or do a percentage of the button height.

Solution 12 - Android

I just needed the same: clickable area larger than button's image. I wrapped it into FrameLayout sized larger than background drawable and changed the inner Button tag to TextView. Than I directed a click handler to work with this FrameLayout, not with the inner TextView. All works well!

Solution 13 - Android

You can use transparent button like this...

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

you can increase or decrease the button size as your requirement and use aenter code here ImageView up the button....

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

Solution 14 - Android

I created the top-level function for this in 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)
}

Solution 15 - Android

I honestly think the easiest way is this:-

Say your image size is 26dp26dp and you want a hit area of 30dp30dp, just add a padding of 2dp to your ImageButton/Button AND set the dimension to 30dp*30dp


Original

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

Bigger hit area

<ImageButton
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:padding="2dp"
    android:src="@drawable/icon_transcript"/>

Solution 16 - Android

Simplest solution would be increase the width and height of your item (e.g Image, Button etc) and add padding; So the clickable area is larger but looks in the original size.

Example:

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

Congratulations, you achieved same button size but double the clickable size!

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

Solution 17 - Android

I added a fairly simple solution to SO; https://stackoverflow.com/a/67904934/13215135

This design can be used for multiple Views on one layout - it also allows you to explicitly design the touch area on each View, instead of guestimating the pixels on a users device;

Solution 18 - Android

For this purposes I was using ImageButton. In xml defenition you will see these two attributes:

  • android:background - A drawable to use as the background.
  • android:scr - Sets a drawable as the content of this ImageView.

So we have two drawables: background one and source. In you example source will be triagle(drawable/trianlge):

|\
| \
|__\

And background is square(drawable/square):

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

Here is example of ImageButton xml:

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

Result:

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

Also square drawable, could have several different states(pressed, focused). And you could expand backgroud drawable size using paddings.

Just in case, here is example for background drawable from Android Compat Actionbar:

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

Solution 19 - Android

You can set the padding to the view i.e. your button.

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

Hope this works for you.

Solution 20 - Android

So the right answer, add padding to the touchable area get bigger AND change your picture from background to srcCompact.

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

To use app:scrCompat you need add xmlns:app="http://schemas.android.com/apk/res-auto" to your main/parent element in the xml.

example complete:

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

Solution 21 - Android

Just adding padding was causing my checkbox to not be centered. The following worked for me, however I specified a fixed size for the checkbox (as per my UI spec). The idea was to add padding to the left and not to the right. I added padding to the top and bottom too making the touch target larger and still keeping the checkbox centered.

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

Solution 22 - Android

I think the correct answer should be using ImageButton instead of Button. Set the triangle drawable as the "src" of the ImageButton, and set the size (layout_width & layout_height) as you want for easier touch and set the ScaleType to center to retain the triangle size.

Solution 23 - Android

<Button
    android:layout_width="32dp"
    android:layout_height="36dp"
    android:layout_margin="5dp"
    android:background="@drawable/foo" 
/>

The size of the background is still 22x26dp. Just add margin.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoJoView Question on Stackoverflow
Solution 1 - AndroidMorris LinView Answer on Stackoverflow
Solution 2 - AndroidkwahnView Answer on Stackoverflow
Solution 3 - AndroidLuxuryModeView Answer on Stackoverflow
Solution 4 - AndroidTimo BährView Answer on Stackoverflow
Solution 5 - AndroidWilliam ReedView Answer on Stackoverflow
Solution 6 - AndroidlogcatView Answer on Stackoverflow
Solution 7 - AndroidNiteshView Answer on Stackoverflow
Solution 8 - AndroidFoamyGuyView Answer on Stackoverflow
Solution 9 - AndroidshaileshView Answer on Stackoverflow
Solution 10 - AndroidThomson ComerView Answer on Stackoverflow
Solution 11 - AndroidmxclView Answer on Stackoverflow
Solution 12 - AndroidDmitri NovikovView Answer on Stackoverflow
Solution 13 - AndroidYatendra SenView Answer on Stackoverflow
Solution 14 - AndroidIgor KonyukhovView Answer on Stackoverflow
Solution 15 - Androiddaisura99View Answer on Stackoverflow
Solution 16 - AndroidunobatbayarView Answer on Stackoverflow
Solution 17 - Androidtommytucker7182View Answer on Stackoverflow
Solution 18 - AndroidmolokokaView Answer on Stackoverflow
Solution 19 - AndroidMayank BhatnagarView Answer on Stackoverflow
Solution 20 - AndroidCanatoView Answer on Stackoverflow
Solution 21 - AndroidRPMView Answer on Stackoverflow
Solution 22 - AndroidHenryView Answer on Stackoverflow
Solution 23 - AndroidTerence LuiView Answer on Stackoverflow