简体   繁体   English

如何在不缩放背景的情况下增加 Android 按钮的点击区域?

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

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.我想保持 drawable 的大小相同,但使点击区域成为三角形大小的两倍的正方形。

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

you can use TouchDelegate API.您可以使用 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)); 
    } 
}); 

You want "padding" It will put the space inside the view.你想要“填充”它将把空间放在视图内。 Margin will put the space outside, which will not increase the hit area. Margin 会将空间放在外面,不会增加命中区域。

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

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"您需要使用TouchDelegate ,它在 API 文档中定义为“帮助类来处理您希望视图具有比其实际视图边界更大的触摸区域的情况”

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

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.与 TouchDelegates 的用法相比,这是一个非常小的变化。 I also don't want to run a Runnable for adding the clickable area inside my Java code.我也不想运行 Runnable 来在我的 Java 代码中添加可点击区域。

One thing you might consider if the view should be pixel-perfect: padding cannot always equal to margin .如果视图应该是像素完美的,您可能会考虑的一件事是: padding不能总是等于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.边距取决于周围的视图,并将与其他视图的边距值偏移。

Based on answer I created a kotlin extension function to help with it.根据答案,我创建了一个 kotlin 扩展函数来帮助它。

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

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

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 .因此,通过这样做.. 您的按钮将采用三角形图像的高度和宽度,并将 20dp 填充添加到按钮而不拉伸图像本身。 and if you want some minimum width or some hinimum height you can use the minWidth and minHeight tags如果你想要一些最小宽度或一些最小高度,你可以使用minWidthminHeight标签

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

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.如果你的意思是你只是不希望你的 drawable 被拉伸,那么你有一个选择是拿你的背景 png 并在上面添加一个额外的透明像素边框。 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.但是,如果您的意思是根本不想更改可绘制对象,那么我认为您唯一的选择是对高度和宽度使用更大的硬编码值。

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.我将它包装成大于背景可绘制大小的 FrameLayout 并将内部 Button 标记更改为 TextView。 Than I directed a click handler to work with this FrameLayout, not with the inner TextView.比我指示单击处理程序使用此 FrameLayout,而不是使用内部 TextView。 All works well!一切正常!

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 a enter code here ImageView up the button....您可以根据需要增加或减少按钮大小,并enter code here使用enter code here ImageView up the button....

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

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

I honestly think the easiest way is this:-老实说,我认为最简单的方法是:-

Say your image size is 26dp*26dp and you want a hit area of 30dp*30dp, just add a padding of 2dp to your ImageButton/Button AND set the dimension to 30dp*30dp假设您的图像大小为 26dp*26dp,并且您想要 30dp*30dp 的命中区域,只需在 ImageButton/Button 上添加 2dp 的填充并将尺寸设置为 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"/>

Simplest solution would be increase the width and height of your item (eg Image, Button etc) and add padding ;最简单的解决方案是增加项目的width and height (例如图像、按钮等)并添加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!恭喜,您实现了相同的按钮大小,但可点击大小增加了一倍!

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

I added a fairly simple solution to SO;我为 SO 添加了一个相当简单的解决方案; https://stackoverflow.com/a/67904934/13215135 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;这种设计可用于一个布局上的多个Views ——它还允许您在每个View上明确设计触摸区域,而不是在用户设备上对像素进行猜测;

See 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.我很快就会把它应用到我自己的代码中。

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.尽管实际上您应该将 30 转换为倾角或按按钮高度的百分比计算。

For this purposes I was using ImageButton.为此,我使用了 ImageButton。 In xml defenition you will see these two attributes:在 xml defenition 中,您将看到这两个属性:

  • android:background - A drawable to use as the background. android:background - 用作背景的可绘制对象。
  • android:scr - Sets a drawable as the content of this ImageView. android:scr - 设置一个 drawable 作为这个 ImageView 的内容。

So we have two drawables: background one and source.所以我们有两个可绘制对象:背景一个和源。 In you example source will be triagle(drawable/trianlge):在您的示例中,源将是 triagle(drawable/trianlge):

|\
| \
|__\

And background is square(drawable/square):背景是正方形(可绘制/正方形):

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

Here is example of ImageButton xml:这是 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:以防万一,这里是可从 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>

You can set the padding to the view ie your button.您可以将填充设置为视图,即您的按钮。

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

Hope this works for you.希望这对你有用。

So the right answer, add padding to the touchable area get bigger AND change your picture from background to srcCompact .所以正确的答案是,将填充添加到可触摸区域变大并将图片从背景更改为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.要使用 app:scrCompat,您需要将xmlns:app="http://schemas.android.com/apk/res-auto"到 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>

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).以下对我有用,但是我为复选框指定了固定大小(根据我的 UI 规范)。 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"
       />

I think the correct answer should be using ImageButton instead of Button.我认为正确的答案应该是使用 ImageButton 而不是 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.将三角形 drawable 设置为 ImageButton 的“src”,并根据需要设置大小(layout_width & layout_height)以便于触摸,并将 ScaleType 设置为 center 以保留三角形大小。

Well, I used custom Framelayout and it works perfectly for me as I assigned the touch for the first child.好吧,我使用了自定义 Framelayout,它非常适合我,因为我为第一个孩子分配了触摸。 I have a view like this:我有这样的看法:

<CustomFrameLayout>
<View>
</CustomFrameLayout>

for customFrameLayout:对于自定义框架布局:

class CustomFrameLayout(context: Context, attrs: AttributeSet?) :
    FrameLayout(context, attrs) {
    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        return true
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val child = getChildAt(0)
        child.onTouchEvent(event)
        return true
    }

}

and in XML I put my view like this:在 XML 中,我的观点是这样的:

 <com.CustomFrameLayout
        android:layout_width="wrap_content"
        android:layout_height="@dimen/buttonsize"
        android:layout_weight="1">

        <com.views.Digit
            android:id="@+id/Digit4"
            android:background="@drawable/four_normal"
            style="@style/numKeypadStyle"
            android:text="4"
            />
 </com.CustomFrameLayout>
<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.背景的大小仍然是 22x26dp。 Just add margin.只需添加保证金。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM