如何使用支持库实现波纹动画?

N S*_*rma 165 android material-design

我想在按钮点击上添加一个涟漪动画.我确实喜欢下面但它需要minSdKVersion到21.

ripple.xml

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="?android:colorAccent" />
        </shape>
    </item>
</ripple>
Run Code Online (Sandbox Code Playgroud)

按键

<com.devspark.robototextview.widget.RobotoButton
    android:id="@+id/loginButton"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/ripple"
    android:text="@string/login_button" />
Run Code Online (Sandbox Code Playgroud)

我想让它向后兼容设计库.

怎么做到这一点?

Ben*_*aye 362

基本纹波设置

  • 视图中包含的涟漪.
    android:background="?selectableItemBackground"

  • 超出视图范围的涟漪:
    android:background="?selectableItemBackgroundBorderless"

    看看这里解决?(attr)Java代码中的xml引用.

支持库

  • 使用?attr:(或?简写)代替?android:attr引用支持库,因此可以返回到API 7.

涟漪与图像/背景

  • 有一个图像或背景和重叠纹波最简单的解决方案是将包裹View在一个FrameLayout与设置的波纹setForeground()setBackground().

老实说,除此之外没有干净的方式这样做,尽管尼克布彻在涟漪的主题上发布了这个android:background="?selectableItemBackground".

  • 这不会为21之前的版本添加涟漪支持. (37认同)
  • 它可能不会添加纹波支持,但此解决方案可以很好地降低.这实际上解决了我遇到的特殊问题.我想要对L的涟漪效果和对先前版本的android的简单选择. (20认同)
  • 如果我还想要背景色怎么办? (14认同)
  • 纹波效应不适用于API <21.纹波是材料设计的单击效果.谷歌设计团队的观点并未在棒棒糖前设备上展示.pre-lolipop有自己的点击效果(默认为浅蓝色封面).提供的答案建议使用系统的默认点击效果.如果你想自定义点击效果的颜色,你需要制作一个drawable并将它放在res/drawable-v21上以获得波纹点击效果(使用<ripple> drawable),并将res/drawable设置为非涟漪点击效果(通常带<selector> drawable) (8认同)
  • @AndroidDev,@ Dave Jensen:实际上,使用`?attr:`而不是`?android:attr`引用了v7支持库,假设你使用它,它可以向后兼容API 7.参见:http:/ /developer.android.com/tools/support-library/features.html#v7 (4认同)

Mar*_*ski 54

我之前投票决定将这个问题作为主题关闭但实际上我改变了主意,因为这是非常好的视觉效果,遗憾的是,它还不是支持库的一部分.它很可能会在未来的更新中出现,但没有宣布时间框架.

幸运的是,现有的自定义实现很少:

包括与旧版Android兼容的Materlial主题小部件集:

所以你可以尝试其中一个或谷歌的其他"材料小部件"左右...

  • 现在这是支持库的一部分,请参阅我的回答. (12认同)

Nic*_*ler 27

我做了一个简单的类来制作波纹按钮,我最后从不需要它,所以它不是最好的,但它在这里是:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

public class RippleView extends Button
{
    private float duration = 250;

    private float speed = 1;
    private float radius = 0;
    private Paint paint = new Paint();
    private float endRadius = 0;
    private float rippleX = 0;
    private float rippleY = 0;
    private int width = 0;
    private int height = 0;
    private OnClickListener clickListener = null;
    private Handler handler;
    private int touchAction;
    private RippleView thisRippleView = this;

    public RippleView(Context context)
    {
        this(context, null, 0);
    }

    public RippleView(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public RippleView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init()
    {
        if (isInEditMode())
            return;

        handler = new Handler();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas)
    {
        super.onDraw(canvas);

        if(radius > 0 && radius < endRadius)
        {
            canvas.drawCircle(rippleX, rippleY, radius, paint);
            if(touchAction == MotionEvent.ACTION_UP)
                invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event)
    {
        rippleX = event.getX();
        rippleY = event.getY();

        switch(event.getAction())
        {
            case MotionEvent.ACTION_UP:
            {
                getParent().requestDisallowInterceptTouchEvent(false);
                touchAction = MotionEvent.ACTION_UP;

                radius = 1;
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                speed = endRadius / duration * 10;
                handler.postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if(radius < endRadius)
                        {
                            radius += speed;
                            paint.setAlpha(90 - (int) (radius / endRadius * 90));
                            handler.postDelayed(this, 1);
                        }
                        else
                        {
                            clickListener.onClick(thisRippleView);
                        }
                    }
                }, 10);
                invalidate();
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            {
                getParent().requestDisallowInterceptTouchEvent(false);
                touchAction = MotionEvent.ACTION_CANCEL;
                radius = 0;
                invalidate();
                break;
            }
            case MotionEvent.ACTION_DOWN:
            {
                getParent().requestDisallowInterceptTouchEvent(true);
                touchAction = MotionEvent.ACTION_UP;
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                paint.setAlpha(90);
                radius = endRadius/4;
                invalidate();
                return true;
            }
            case MotionEvent.ACTION_MOVE:
            {
                if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height)
                {
                    getParent().requestDisallowInterceptTouchEvent(false);
                    touchAction = MotionEvent.ACTION_CANCEL;
                    radius = 0;
                    invalidate();
                    break;
                }
                else
                {
                    touchAction = MotionEvent.ACTION_MOVE;
                    invalidate();
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public void setOnClickListener(OnClickListener l)
    {
        clickListener = l;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

由于很多人都在寻找这样的东西,我创造了一个可以让其他观点产生连锁反应的课程:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

public class RippleViewCreator extends FrameLayout
{
    private float duration = 150;
    private int frameRate = 15;

    private float speed = 1;
    private float radius = 0;
    private Paint paint = new Paint();
    private float endRadius = 0;
    private float rippleX = 0;
    private float rippleY = 0;
    private int width = 0;
    private int height = 0;
    private Handler handler = new Handler();
    private int touchAction;

    public RippleViewCreator(Context context)
    {
        this(context, null, 0);
    }

    public RippleViewCreator(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public RippleViewCreator(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init()
    {
        if (isInEditMode())
            return;

        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.control_highlight_color));
        paint.setAntiAlias(true);

        setWillNotDraw(true);
        setDrawingCacheEnabled(true);
        setClickable(true);
    }

    public static void addRippleToView(View v)
    {
        ViewGroup parent = (ViewGroup)v.getParent();
        int index = -1;
        if(parent != null)
        {
            index = parent.indexOfChild(v);
            parent.removeView(v);
        }
        RippleViewCreator rippleViewCreator = new RippleViewCreator(v.getContext());
        rippleViewCreator.setLayoutParams(v.getLayoutParams());
        if(index == -1)
            parent.addView(rippleViewCreator, index);
        else
            parent.addView(rippleViewCreator);
        rippleViewCreator.addView(v);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    @Override
    protected void dispatchDraw(@NonNull Canvas canvas)
    {
        super.dispatchDraw(canvas);

        if(radius > 0 && radius < endRadius)
        {
            canvas.drawCircle(rippleX, rippleY, radius, paint);
            if(touchAction == MotionEvent.ACTION_UP)
                invalidate();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event)
    {
        return true;
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event)
    {
        rippleX = event.getX();
        rippleY = event.getY();

        touchAction = event.getAction();
        switch(event.getAction())
        {
            case MotionEvent.ACTION_UP:
            {
                getParent().requestDisallowInterceptTouchEvent(false);

                radius = 1;
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                speed = endRadius / duration * frameRate;
                handler.postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        if(radius < endRadius)
                        {
                            radius += speed;
                            paint.setAlpha(90 - (int) (radius / endRadius * 90));
                            handler.postDelayed(this, frameRate);
                        }
                        else if(getChildAt(0) != null)
                        {
                            getChildAt(0).performClick();
                        }
                    }
                }, frameRate);
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            {
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }
            case MotionEvent.ACTION_DOWN:
            {
                getParent().requestDisallowInterceptTouchEvent(true);
                endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY);
                paint.setAlpha(90);
                radius = endRadius/3;
                invalidate();
                return true;
            }
            case MotionEvent.ACTION_MOVE:
            {
                if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height)
                {
                    getParent().requestDisallowInterceptTouchEvent(false);
                    touchAction = MotionEvent.ACTION_CANCEL;
                    break;
                }
                else
                {
                    invalidate();
                    return true;
                }
            }
        }
        invalidate();
        return false;
    }

    @Override
    public final void addView(@NonNull View child, int index, ViewGroup.LayoutParams params)
    {
        //limit one view
        if (getChildCount() > 0)
        {
            throw new IllegalStateException(this.getClass().toString()+" can only have one child.");
        }
        super.addView(child, index, params);
    }
}
Run Code Online (Sandbox Code Playgroud)


Ami*_*bar 12

这很简单;-)

首先,您必须创建两个可绘制文件,一个用于旧api版本,另一个用于最新版本,当然!如果您为最新的api版本android studio创建drawable文件,建议您自动创建旧的。最后将此可绘制对象设置为背景视图。

适用于新api版本(res / drawable-v21 / ripple.xml)的示例可绘制对象:

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/colorPrimary" />
            <corners android:radius="@dimen/round_corner" />
        </shape>
    </item>
</ripple>
Run Code Online (Sandbox Code Playgroud)

适用于旧api版本的示例可绘制样本(res / drawable / ripple.xml)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/colorPrimary" />
    <corners android:radius="@dimen/round_corner" />
</shape>
Run Code Online (Sandbox Code Playgroud)

有关波纹可绘制的更多信息,请访问:https : //developer.android.com/reference/android/graphics/drawable/RippleDrawable.html


小智 11

有时您具有自定义背景,在这种情况下,使用更好的解决方案 android:foreground="?selectableItemBackground"

  • 是的,但它适用于 API &gt;= 23 或具有 21 API 的设备,但仅适用于 CardView 或 FrameLayout (2认同)