实现工具栏菜单项点击水波纹效果

23

我尝试使用padding来增加按钮的触摸区域。我使用了{{padding}}。

<ImageButton
    android:paddingRight="32dp"
    android:paddingEnd="32dp"
    android:id="@+id/confirm_image_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    android:background="?selectableItemBackgroundBorderless"
    android:src="?attr/confirmIcon" />

点击区域变大了。但是,selectableItemBackgroundBorderless 的点击效果不再呈现为完美的圆形。

enter image description here


我尝试使用{{duplicateParentState}}技术来解决。
<FrameLayout
    android:clickable="true"
    android:paddingRight="32dp"
    android:paddingEnd="32dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true">
    <ImageButton
        android:duplicateParentState="true"
        android:id="@+id/confirm_image_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?selectableItemBackgroundBorderless"
        android:src="?attr/confirmIcon" />
</FrameLayout>

enter image description here

现在,

  1. 点击区域被放大。
  2. selectableItemBackgroundBorderless圆形效果是完美的圆形。

然而,它似乎有一些奇怪的行为。当我点击ImageButton的实际区域时,圆形按压效果不会显示。

enter image description here

我能知道为什么会这样,以及如何克服吗?我使用API 26进行了测试。
请注意,我尽量避免使用TouchDelegate技术,除非被迫这样做,因为它会使我们的代码更加复杂。

附加信息

以下是Toolbar按钮的正确行为。

当点击区域在按钮外部时,会显示涟漪效果

enter image description here

当点击区域在按钮内部时,会显示涟漪效果

enter image description here

然而,我不知道他们如何实现这样的行为。


我建议编写您的动画和点击行为。我认为这是最快的解决方案。 - Vyacheslav
4个回答

63

经过一些时间的探索,我终于找到了"工具栏奥秘"的实现方式。它是通过在工具栏上显示ActionMenuItemView实现的。而且您可以看到,在xml文件中,它应用了style="?attr/actionButtonStyle"?attr/actionButtonStyle对应于Widget.Material.ActionButton,并且在这种样式下,我们可以看到 <item name="background">?attr/actionBarItemBackground</item>

如果您想将相同的效果应用于您的ImageButton,那么您所要做的就是将 android:background="?attr/actionBarItemBackground" 应用于它。因此,以下是xml布局:

<ImageButton
    android:id="@+id/confirm_image_button"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:background="?attr/actionBarItemBackground"
    android:src="@drawable/ic_check_black_24dp" />
您将收到以下输出: 输入图像描述 打开“显示布局边界”,以便可以看到 ImageButton 的实际边界 如果您想知道 ?attr/actionBarItemBackground 实际上代表什么,这里是它的定义:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?attr/colorControlHighlight"
    android:radius="20dp" />

因此,您可以创建自己的可绘制对象,并将其应用为ImageButton的背景。


谢谢。这就是我要找的答案,没有任何绕路来实现这个目的。 - Cheok Yan Cheng
非常棒的答案,我已经寻找很久了。奖励当之无愧! - Suleyman

2
Android 5.0上的涟漪效果是从每个视图中心开始的。按照Material Design规则,涟漪效果应该从触摸事件发生的位置开始,因此它们看起来从手指处向外流动。这个问题在Android 5.1中得到了解决,但在Android 5.0中存在一个bug。
要解决这个问题,您需要使用API Level 21中添加到Drawable的setHotspot()方法。setHotspot()为drawable提供了一个“hot spot”,RippleDrawable显然将其用作涟漪效果的发射点。setHotspot()接受一对浮点值,可能考虑到在OnTouchListener内部使用setHotspot(),因为MotionEvent报告触摸事件的X/Y位置具有浮点值。
感谢The Busy Coder's Guide书籍。
您可以使用以下代码来修复涟漪效果的bug:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
       btn.setOnTouchListener(new View.OnTouchListener() {
          @TargetApi(Build.VERSION_CODES.LOLLIPOP)
          @Override
          public boolean onTouch(View v, MotionEvent event) {
              v.getBackground()
                .setHotspot(event.getX(), event.getY());
              return(false);
          }
     });
}

其他解决方案:

如何使用支持库实现涟漪动画?

RippleDrawable


另一个解决方案:

在您的第一次尝试中:

我尝试使用填充来增加按钮的触摸区域。

您可以使用此代码将涟漪的中心放置在ImageButton的中心::

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
           btn.setOnTouchListener(new View.OnTouchListener() {
              @TargetApi(Build.VERSION_CODES.LOLLIPOP)
              @Override
              public boolean onTouch(View v, MotionEvent event) {
                  v.getBackground()
                    .setHotspot(v.getWidth()/2f, v.getHeight()/2f);//setting hot spot at center of ImageButton
                  return(false);
              }
         });
    }

FrameLayout 包含 ImageButton 时,也许有另一种解决方案:

如你所说:

但是,似乎存在一些奇怪的行为。当我点击 ImageButton 的实际区域时,圆形按压效果不会显示。

解决此问题的方法之一可能是在 ImageButtononClick() 方法中添加 frameLayout.performClick()

或者,当点击 ImageButton 时,您可以使用以下代码模拟对 frameLayout 的触摸:

// Obtain MotionEvent object
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis() + 100;
float x = 0.0f;// or x = frameLayout.getWidth()/2f
float y = 0.0f;// or y = frameLayout.getHeight()/2f
// List of meta states found here: developer.android.com/reference/android/view/KeyEvent.html#getMetaState()
int metaState = 0;
MotionEvent motionEvent = MotionEvent.obtain(
    downTime, 
    eventTime, 
    MotionEvent.ACTION_UP, 
    x, 
    y, 
    metaState
);

// Dispatch touch event to view
frameLayout.dispatchTouchEvent(motionEvent);

请问没有这个解决方法它为什么不能正常工作? - Cheok Yan Cheng
你有关于这个 bug 的参考来源吗? - Cheok Yan Cheng
我使用API 26进行了测试。我可以确认您提出的解决方案不起作用。OnTouchListener被触发,但圆形按压效果未显示。 - Cheok Yan Cheng
我已经阅读了那些答案,但它们并没有解答我的疑问。我知道如何实现涟漪效果。(正如您可以看到,我的涟漪效果是使用 https://dev59.com/c18d5IYBdhLWcg3woDYZ#28715666 中的技术实现的)但我的问题是不同的,这在我的问题中已经清楚地说明了。 - Cheok Yan Cheng
在你的第一次尝试中,你只有一个设置了padding的ImageButton。你可以使用v.getBackground().setHotspot(v.getWidth()/2f, v.getHeight()/2f);将水波纹的中心位置设置在图像按钮的中心位置。请查看我的编辑答案。 - ygngy

1
我猜测你的问题在于你使用了 ImageButton 而不是仅仅使用 ImageViewImageButton 的默认样式为:
<style name="Widget.ImageButton">
    <item name="android:focusable">true</item>
    <item name="android:clickable">true</item>
    <item name="android:scaleType">center</item>
    <item name="android:background">@android:drawable/btn_default</item>
</style>

背景已被替换,但您仍然可以使项目可点击和可聚焦。在您的示例中,真正的活动项目是容器,因此您可以使用常规的ImageView,或手动设置项目不可点击和不可聚焦。还要重要的是在容器上设置单击侦听器,而不是按钮/图像本身,因为它会变得可点击。
这是一种变体:
<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingEnd="48dp"
    android:focusable="true"
    android:clickable="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    >

    <ImageButton
        android:duplicateParentState="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?selectableItemBackgroundBorderless"
        android:duplicateParentState="true"
        android:src="@drawable/ic_check_black_24dp"
        android:clickable="false"
        android:focusable="false"
        />
</FrameLayout>

另一个与之相同,只不过 FrameLayout 中的视图如下:
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="?selectableItemBackgroundBorderless"
    android:duplicateParentState="true"
    android:src="@drawable/ic_check_black_24dp"
    />

这是视觉证明:

screen capture

这是在运行Android 5.0.2的模拟器上进行捕获的。我也在我的8.0手机上尝试过,同样有效。

我尝试用ImageView替换ImageButton。它仍然表现出与ImageButton相同的问题行为,就像我从ImageButton中描述的那样。如果按下区域在ImageView上,则不会显示圆形按压效果。 - Cheok Yan Cheng
@CheokYanCheng 手动将可聚焦和可点击设置为 false 如何?此外,您是在图像上还是容器上设置了 onClickListener - Malcolm
是的,我试过 https://gist.github.com/yccheok/1657ad18cc5084eb37f7a667ddb3a4b7 ,并尝试在容器和/或按钮上使用 onClickListener(各种组合)。但它不起作用。你有在你那边试过吗?它对你有效吗? - Cheok Yan Cheng
@CheokYanCheng 完美的。我已经更新了我的答案,附上了实际的代码。也许是你的设备出了问题? - Malcolm

1
如果你使用 android:duplicateParentState="true",只需将 android:clickable="false" 设置到你的 ImageButton 上,当你点击它和它的父元素时,它的父元素也会高亮显示。
<ImageButton
       ...
       android:duplicateParentState="true"
       android:clickable="false"
  />

解释

来自文档

当启用复制时,此视图的可绘制状态来自其父级而不是其自身内部属性

=> 视图基于父状态更改状态。 ImageButton 的默认 clickable 为 true => 当您单击 ImageButton 时,其父状态不会更改 => ImageButton 不会高亮显示

=> 设置 clickable=false 将解决问题

一些注意事项

1) 不要忘记在其父级上设置 clickable=true
2) 对于 TextViewImageView(或某些默认为 clickable=false 的视图),您不需要设置 clickable=false
3) 使用 setOnClickListener 时要小心

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

当使用 setOnClickListener 时,它也会调用 setClickable(true),因此在这种情况下,您只需要为其父布局设置 setOnClickListener

你自己试过这个解决方案吗?因为我试过了,对我来说没有起作用。 - Cheok Yan Cheng
@CheokYanCheng 当然可以,我已经使用这种方法很多次了。 - Linh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接