实现工具栏菜单项点击波纹效果
Achieve toolbar menu item click ripple effect
我尝试使用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
点击效果不再显示为正圆。
我尝试用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>
现在,
- 点击区域放大
selectableItemBackgroundBorderless
圆形效果是一个完美的圆形。
但是,它似乎有一些奇怪的行为。当我点击ImageButton
的实际区域时,没有显示圈压效果
请问为什么会这样,如何克服呢?我使用 API 26.
进行了测试
请注意,我尽量避免使用 TouchDelegate
技术,除非我被迫这样做,因为它会使我们的代码更加复杂。
附加信息
以下是 Toolbar
按钮所展示的正确行为。
点击区域在按钮外时出现波纹效果
点击区域在按钮内时出现波纹效果
但是,我不知道他们是如何实现这种行为的。
The ripples on Android 5.0 start in the center of each view. According
to the Material Design rules, the ripples should start where the touch
event occurs, so they seem to flow outward from the finger. This was
addressed in Android 5.1, but was a bug in Android 5.0
To fix this, you need to use the setHotspot()
method, added to
Drawable in API Level 21. setHotspot()
provides to the drawable a
“hot spot”, and RippleDrawable
apparently uses this as the emanation point for the ripple effect. setHotspot()
takes a pair of
float values, presumably with an eye towards using setHotspot()
inside of an OnTouchListener
, as the MotionEvent
reports X/Y
positions of the touch event with float values.
感谢 The Busy Coder's Guide 这本书
您可以使用此代码来纠正涟漪错误:
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);
}
});
}
其他解决方案:
How to achieve ripple animation using support library?
另一个解决方案:
第一次尝试:
I try to use padding to increase the touch area of a button.
您可以使用此代码将纹波中心置于 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
时可能是另一种解决方案:
正如你所说:
However, it seems to have some weird behavior. When I click on the
actual area of the ImageButton, the circle press effect is not shown.
解决此问题的方法可能是在 ImageButton
的 onClick()
方法中添加 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);
我怀疑你的问题在于你有一个 ImageButton
而不仅仅是一个 ImageView
。 ImageButton
的默认样式是:
<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
,或手动将项目设置为不可点击和可聚焦。在容器上设置点击侦听器也很重要, 而不是 button/image 本身,因为它将变得可点击。
这是一种变体:
<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"
/>
这是视觉证明:
这是在模拟器 运行 Android 5.0.2 上捕获的。我在我的 8.0 phone 上试过这个,效果也很好。
如果您使用 android:duplicateParentState="true"
,只需将 android:clickable="false"
设置为您的 ImageButton
即可使您的 Button
在单击它及其父项时突出显示
<ImageButton
...
android:duplicateParentState="true"
android:clickable="false"
/>
说明
来自document
When duplication is enabled, this view gets its drawable state from its parent rather than from its own internal properties
=> 查看基于父状态的更改状态。
ImageButton
的默认clickable
为true =>当你点击ImageButton
时,parent的状态不会改变=>ImageButton
不会高亮
=>设置clickable=false
将解决问题
一些注意事项
1) 不要忘记在其父级
上设置 clickable=true
2)对于TextView
、ImageView
(或者一些默认clickable=false的视图),不需要设置clickable=false
3) 使用时要小心 setOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
当使用 setOnClickListener
时,它也会 setClickable(true)
所以在这种情况下你 必须只 设置 setOnClickListener
为其父布局
花了一些时间后,我终于找到了 "toolbar mystery" 的工作原理。显示在工具栏上的是 ActionMenuItemView
。你可以看到,在 xml file it has style="?attr/actionButtonStyle"
applied to it. ?attr/actionButtonStyle
corresponds to 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" />
您将收到此输出:
打开 "Show layout bounds" 以便 ImageButton
的实际边界可见
如果你好奇?attr/actionBarItemBackground
到底代表什么,here's it:
<?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
。
我尝试使用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
点击效果不再显示为正圆。
我尝试用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>
现在,
- 点击区域放大
selectableItemBackgroundBorderless
圆形效果是一个完美的圆形。
但是,它似乎有一些奇怪的行为。当我点击ImageButton
的实际区域时,没有显示圈压效果
请问为什么会这样,如何克服呢?我使用 API 26.
进行了测试请注意,我尽量避免使用 TouchDelegate
技术,除非我被迫这样做,因为它会使我们的代码更加复杂。
附加信息
以下是 Toolbar
按钮所展示的正确行为。
点击区域在按钮外时出现波纹效果
点击区域在按钮内时出现波纹效果
但是,我不知道他们是如何实现这种行为的。
The ripples on Android 5.0 start in the center of each view. According to the Material Design rules, the ripples should start where the touch event occurs, so they seem to flow outward from the finger. This was addressed in Android 5.1, but was a bug in Android 5.0
To fix this, you need to use the
setHotspot()
method, added to Drawable in API Level 21.setHotspot()
provides to the drawable a “hot spot”, andRippleDrawable
apparently uses this as the emanation point for the ripple effect.setHotspot()
takes a pair of float values, presumably with an eye towards usingsetHotspot()
inside of anOnTouchListener
, as theMotionEvent
reportsX/Y
positions of the touch event with float values.
感谢 The Busy Coder's Guide 这本书
您可以使用此代码来纠正涟漪错误:
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);
}
});
}
其他解决方案:
How to achieve ripple animation using support library?
另一个解决方案:
第一次尝试:
I try to use padding to increase the touch area of a button.
您可以使用此代码将纹波中心置于 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
时可能是另一种解决方案:
正如你所说:
However, it seems to have some weird behavior. When I click on the actual area of the ImageButton, the circle press effect is not shown.
解决此问题的方法可能是在 ImageButton
onClick()
方法中添加 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);
我怀疑你的问题在于你有一个 ImageButton
而不仅仅是一个 ImageView
。 ImageButton
的默认样式是:
<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
,或手动将项目设置为不可点击和可聚焦。在容器上设置点击侦听器也很重要, 而不是 button/image 本身,因为它将变得可点击。
这是一种变体:
<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"
/>
这是视觉证明:
这是在模拟器 运行 Android 5.0.2 上捕获的。我在我的 8.0 phone 上试过这个,效果也很好。
如果您使用 android:duplicateParentState="true"
,只需将 android:clickable="false"
设置为您的 ImageButton
即可使您的 Button
在单击它及其父项时突出显示
<ImageButton
...
android:duplicateParentState="true"
android:clickable="false"
/>
说明
来自document
When duplication is enabled, this view gets its drawable state from its parent rather than from its own internal properties
=> 查看基于父状态的更改状态。
ImageButton
的默认clickable
为true =>当你点击ImageButton
时,parent的状态不会改变=>ImageButton
不会高亮
=>设置clickable=false
将解决问题
一些注意事项
1) 不要忘记在其父级
上设置 clickable=true
2)对于TextView
、ImageView
(或者一些默认clickable=false的视图),不需要设置clickable=false
3) 使用时要小心 setOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
当使用 setOnClickListener
时,它也会 setClickable(true)
所以在这种情况下你 必须只 设置 setOnClickListener
为其父布局
花了一些时间后,我终于找到了 "toolbar mystery" 的工作原理。显示在工具栏上的是 ActionMenuItemView
。你可以看到,在 xml file it has style="?attr/actionButtonStyle"
applied to it. ?attr/actionButtonStyle
corresponds to 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" />
您将收到此输出:
打开 "Show layout bounds" 以便 ImageButton
的实际边界可见
如果你好奇?attr/actionBarItemBackground
到底代表什么,here's it:
<?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
。