web-dev-qa-db-ja.com

サポートライブラリを使用してリップルアニメーションを実現する方法

ボタンクリックでリップルアニメーションを追加しようとしています。私は以下のようにしましたが、それは21にminSdKVersionを必要とします。

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>

ボタン

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

デザインライブラリとの下位互換性を保ちたいのです。

どのようにこれを行うことができますか?

161
N Sharma

基本波紋設定

  • ビュー内に含まれる波紋。
    Android:background="?selectableItemBackground"

  • ビューの境界を超えて広がる波紋:
    Android:background="?selectableItemBackgroundBorderless"

    Javaコード内の?(attr) xml参照を解決するためには、 をご覧ください

サポートライブラリ

  • ?attr:の代わりに?(または?android:attrの省略形)を使用すると、 サポートライブラリ が参照されるため、API 7に戻ることができます。

画像/背景の波紋

  • 画像や背景を重ねて波紋を重ねるには、最も簡単な解決策は、setForeground()またはsetBackground()で設定した波紋でViewFrameLayoutにラップすることです。

Nick Butcherが波紋の付いたImageViewsを題材にして this を投稿しましたが、正直なところ、これ以外の方法でこれを行う明確な方法はありません。

349
Ben De La Haye

私は以前、この質問をトピック外としてクローズすることを投票しましたが、残念ながらまだサポートライブラリには含まれていないため、実際には気が変わりました。それはおそらく将来のアップデートで現れるでしょうが、発表される時間枠はありません。

幸いなことに、すでにいくつかのカスタム実装が利用可能です。

androidの旧バージョンと互換性のあるMaterlialをテーマにしたウィジェットセットを含みます。

それで、あなたはこれらのうちの1つを試みることができるか、または他の「物質的なウィジェット」またはグーグルのためにグーグル...

54
Marcin Orlowski

私は波紋ボタンを作る簡単なクラスを作りました、私は最後にそれを必要としなかったので、それは最高ではありません、しかし、ここでそれは以下のとおりです

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

EDIT

多くの人がこのようなものを探しているので、他のビューに波及効果を持たせることができるクラスを作りました。

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);
    }
}
27
Nicolas Tyler

時々あなたはカスタム背景を持っています、その場合より良い解決策はAndroid:foreground="?selectableItemBackground"を使うことです

8
Kenny Orellana

とても簡単です;-)

最初に、古いAPIバージョン用と新しいバージョン用に2つのドロアブルファイルを作成する必要があります。もちろん!あなたが最新のapiバージョンのAndroidスタジオ用のドロアブルファイルを作成するなら、あなたは自動的に古いものを作成することを勧めます。そして最後にこの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>

旧バージョンの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>

リップルドロアブルの詳細については、こちらをご覧ください。 https://developer.Android.com/reference/Android/graphics/drawable/RippleDrawable.html

7
Amintabar

時にはこの行をレイアウトやコンポーネントに使用することができます。

 Android:background="?attr/selectableItemBackground"

のように。

 <RelativeLayout
                Android:id="@+id/relative_ticket_checkin"
                Android:layout_width="match_parent"
                Android:layout_height="match_parent"
                Android:layout_weight="1"
                Android:background="?attr/selectableItemBackground">
0
Jatin Mandanka