26

这是一个简短的问题:

假设我有一个ViewasRippleDrawable背景。

有没有一种简单的方法可以在不触发任何触摸或点击事件的情况下从特定位置触发涟漪?

4

6 回答 6

34

就在这里!为了以编程方式触发涟漪,您必须设置RippleDrawablewith的状态setState()。调用setVisible()不起作用!_


解决方案

要显示波纹,您必须同时将状态设置为按下和启用:

rippleDrawable.setState(new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled });

只要设置了这些状态,就会显示波纹。当您想再次隐藏波纹时,将状态设置为空int[]

rippleDrawable.setState(new int[] {  });

您可以通过调用来设置涟漪发出的点setHotspot()


这个怎么运作

我调试了很多,研究了RippleDrawableup和down的源代码,直到我意识到波纹实际上是在onStateChange(). 调用setVisible()没有任何效果,也不会导致实际出现任何涟漪。

源代码的相关部分RippleDrawable是这样的:

@Override
protected boolean onStateChange(int[] stateSet) {
    final boolean changed = super.onStateChange(stateSet);

    boolean enabled = false;
    boolean pressed = false;
    boolean focused = false;

    for (int state : stateSet) {
        if (state == R.attr.state_enabled) {
            enabled = true;
        }
        if (state == R.attr.state_focused) {
            focused = true;
        }
        if (state == R.attr.state_pressed) {
            pressed = true;
        }
    }

    setRippleActive(enabled && pressed);
    setBackgroundActive(focused || (enabled && pressed));

    return changed;
}

如您所见,如果启用和按下属性都设置了,那么波纹和背景都将被激活并显示波纹。此外,只要您设置焦点状态,背景也会被激活。有了这个,你可以触发波纹并让背景独立改变颜色。

如果您有兴趣,可以查看RippleDrawable 此处的完整源代码。

于 2015-05-04T08:46:45.990 回答
24

我合并/合并了上面@Xaver Kapeller 和@Nikola Despotoski 的答案:

protected void forceRippleAnimation(View view)
{
    Drawable background = view.getBackground();

    if(Build.VERSION.SDK_INT >= 21 && background instanceof RippleDrawable)
    {
        final RippleDrawable rippleDrawable = (RippleDrawable) background;

        rippleDrawable.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});

        Handler handler = new Handler();

        handler.postDelayed(new Runnable()
        {
            @Override public void run()
            {
                rippleDrawable.setState(new int[]{});
            }
        }, 200);
    }
}

要以编程方式强制命令产生涟漪效果,只需调用 forceRippleAnimation(),将要产生涟漪的 View 作为参数传递。

于 2015-08-30T21:43:17.523 回答
13

这是 Nikola 的 setHotSpot() 和https://stackoverflow.com/a/25415471/1474113的组合

private void forceRipple(View view, int x, int y) {
    Drawable background = view.getBackground();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && background instanceof RippleDrawable) {
        background.setHotspot(x, y);
    }
    view.setPressed(true);
    // For a quick ripple, you can immediately set false.
    view.setPressed(false);
}
于 2016-07-07T09:35:31.527 回答
8

首先,您需要从视图中获取可绘制对象。

private void forceRippleAnimation(View v, float x, float y){
   Drawable background = v.getBackground();
   if(background instanceof RippleDrawable){
     RippleDrawable ripple = (RippleDrawable)background;
     ripple.setHotspot(x, y);
     ripple.setVisible (true, true);
   }

}

方法setHotspot(x,y);用于设置涟漪动画从哪里开始,否则,如果没有设置,RippleDrawable则将取其Rect所在的位置(即Rect设置为背景的 View 的位置)并从中心开始启动涟漪效果。

setVisible(true, true)将使可绘制可见,最后一个参数将强制动画,无论当前可绘制状态如何。

于 2014-12-01T19:35:51.723 回答
1

当从 RecyclerView 中刷出单元格时,我使用了以下 Luke 代码的 Kotlin 变体来手动显示和隐藏波纹:

fun View.showRipple() {
    if (Build.VERSION.SDK_INT >= 21 && background is RippleDrawable) {
        background.state = intArrayOf(android.R.attr.state_pressed, android.R.attr.state_enabled)
    }
}

fun View.hideRipple() {
    if (Build.VERSION.SDK_INT >= 21 && background is RippleDrawable) {
        background.state = intArrayOf()
    }
}
于 2019-05-26T13:46:44.767 回答
0

感谢你们!您的答案有助于解决我的问题,但我必须从多个答案中构建解决方案。

我的问题是透明RecyclerView项目(标题)和它背后的按钮。您可能会在图片上看到这种情况:

应用程序的外观

要点击关注者/喜欢/订阅按钮,我必须View.OnTouchListenerRecyclerView标题添加:

class ProfileHeaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private val clickView: View = itemView.findViewById(R.id.profile_header_view)

    fun bind(callback: View.OnTouchListener) {
        clickView.setOnTouchListener(callback)
    }

    fun unbind() {
        clickView.setOnTouchListener(null)
    }
}

并在里面Fragment模拟按钮点击和涟漪效果:

/**
 * [View.performClick] should call inside [onTouch].
 */
override fun onTouch(view: View?, ev: MotionEvent?): Boolean {
    if (ev == null) return true

    val followersContainer = followersContainer ?: return true
    val likeContainer = likeContainer ?: return true
    val subscriptionsContainer = subscriptionsContainer ?: return true
    
    when {
        ev.onView(followersContainer) -> if (followersContainer.isClick(ev)) {
            followersContainer.performClick()
        }
        ev.onView(likeContainer) -> if (likeContainer.isClick(ev)) {
            likeContainer.performClick()
        }
        ev.onView(subscriptionsContainer) -> if (subscriptionsContainer.isClick(ev)) {
            subscriptionsContainer.performClick()
        }
    }

    return true
}

/**
 * [MotionEvent.ACTION_UP] - user make click (remove ripple)
 * [MotionEvent.ACTION_DOWN] - user start click (show ripple)
 * [MotionEvent.ACTION_CANCEL] - user move touch outside of [View] (remove ripple)
 */
private fun View.isClick(ev: MotionEvent): Boolean {        
    when (ev.action) {
        MotionEvent.ACTION_UP -> {
            changeRippleState(ev, isTouch = false)
            return true
        }
        MotionEvent.ACTION_DOWN -> changeRippleState(ev, isTouch = true)
        MotionEvent.ACTION_CANCEL -> changeRippleState(ev, isTouch = false)
    }
    
    return false
}

/**
 * [this] show have android:background with ripple drawable (?attr/selectableItemBackground).
 */
private fun View.changeRippleState(ev: MotionEvent, isTouch: Boolean) {
    val rippleDrawable = background as? RippleDrawable ?: return

    /**
     * For emulate click position.
     */
    rippleDrawable.setHotspot(ev.rawX, ev.rawY)
    isPressed = isTouch
}

我检查了另一种设置按下状态的方法RippleDrawable,它也有效:

rippleDrawable.state = if (isTouch) {
    intArrayOf(android.R.attr.state_pressed, android.R.attr.state_enabled)
} else {
    intArrayOf()
}

检查的扩展MotionEvent发生在内部View或外部:

fun MotionEvent?.onView(view: View?): Boolean {
    if (view == null || this == null) return false

    return Rect().apply {
        view.getGlobalVisibleRect(this)
    }.contains(rawX.toInt(), rawY.toInt())
}
于 2021-05-12T14:34:32.337 回答