How to increase hit area of Android button without scaling background?
AndroidAndroid 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.