当前位置: 首页 > 知识库问答 >
问题:

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

弓明亮
2023-03-14

我试图添加一个按钮点击涟漪动画。我确实喜欢下面的内容,但它需要从版本到21。

涟漪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>

按钮

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

我想让它向下兼容设计库。

如何做到这一点?

共有3个答案

燕宏胜
2023-03-14

我制作了一个简单的类来制作涟漪按钮,我最终从不需要它,所以它不是最好的,但它是:

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

编辑

因为很多人都在寻找类似的东西,我制作了一个类,可以让其他视图产生连锁反应:

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);
    }
}
司空默
2023-03-14

我以前投票决定不讨论这个问题,但实际上我改变了主意,因为这是一个非常好的视觉效果,不幸的是,它还不是支持库的一部分。它很可能会出现在未来的更新中,但目前还没有公布时间框架。

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

  • https://github.com/traex/RippleEffect
  • https://github.com/balysv/material-ripple
  • https://github.com/siriscac/RippleView
  • https://github.com/ozodrukh/RippleDrawable

包括与旧版本Android兼容的以材料为主题的小部件集:

  • https://github.com/keithellis/MaterialWidget

所以你可以尝试其中一个或者谷歌其他“材料小部件”等等...

董高逸
2023-03-14

基本纹波设置

>

超出视图边界的纹波:
android: background="?selectableItemBackground无边界"

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

支持库

  • 使用?代码(或代码)速记)而不是?android: attr引用了支持库,因此可以返回API 7。

带有图像/背景的涟漪

  • 要有一个图像或背景并覆盖纹波,最简单的解决方案是将View包装在FrameLayout中,纹波设置为setForeground()setbackground()

老实说,没有一个干净的方法可以做到这一点。

 类似资料:
  • 到底什么是宏呢? 我们把那些能自动执行某种操作的命令统称为“宏”。   宏也是一种操作命令,它和菜单操作命令都是一样的,只是它们对数据库施加作用的时间有所不同,作用时的条件也有所不同。菜单命令一般用在数据库的设计过程中,而宏命令则用在数据库的执行过程中。菜单命令必须由使用者来施加这个操作,而宏命令则可以在数据库中自动执行。   在OFFICE中,有很多种基本宏操作,这些基本操作还可以组合成很多其他

  • 我已经达到了magic dex的极限,因为我的应用程序使用了很多JAR(驱动API、greendao、文本到pdf、支持…)。 我目前的解决方案是,我只为google drive创建了第二个apk,我从主apk调用了它。但现在我发现android终于通过这个库支持了这一点。我的问题是我不知道如何实现它(最好没有gradle)。我找不到任何好的教程。 好吧,我正在失去理智,试图实现这个...我找到了

  • 所以现在Android O可以通过OTA更新使用,我想将compileSdkVersion移动到26,buildToolsVersion移动到26.0.1等。 然而,当我将我的支持库引用移到26.0.1时,会出现“无法解决”错误。 有几个现有的答案指出需要将“https://maven.google.com”添加到 build.gradle 文件的 Maven 存储库列表中。我已经这样做了——无济

  • 我需要在Android 2中实现actionbar。我已经尝试了这篇文章,并按照这篇官方文章中提到的设置了支持库。 以下是我使用appcompat支持库实现ActionBar的步骤: 首先,我使用以下配置创建项目(我不创建图标和默认活动): 最低要求SDK: API 10: Android 2.3.3(姜饼) 目标SDK: API 19: Android 4.4 使用编译: API 10: And

  • 2)React VR构建在React原生框架上。这是否意味着React VR可以用来实现我的目标(AR导航)? 3)有人能分享他们在React Native和AR中的经验吗?然后我就有了一个清晰的开始方法。

  • 我在Unity中有项目,使用Mapbox, Mapbox包含以下支持库:support-v4-25.1.0.aar 此库以.aar文件的形式提供,而不是作为gradle命令 尝试构建项目时,我收到此错误: 无法确定任务的依赖项:启动程序:lintVitalRelease。< br >无法解析配置的所有项目:launcher:debuguntimeclass path。< br >无法转换支持-v4