web-dev-qa-db-ger.com

Wie erreicht man eine Ripple-Animation mithilfe der Support-Bibliothek?

Ich versuche, eine Ripple-Animation beim Klicken auf die Schaltfläche hinzuzufügen. Ich mochte unten, aber es erfordert minSdKVersion bis 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>

Taste

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

Ich möchte es abwärtskompatibel mit der Design-Bibliothek machen.

Wie kann das gemacht werden?

155
N Sharma

Grundlegende Welligkeitseinstellung

  • In der Ansicht enthaltene Wellen.
    Android:background="?selectableItemBackground"

  • Wellen, die über die Grenzen der Ansicht hinausgehen:
    Android:background="?selectableItemBackgroundBorderless"

    Schauen Sie sich hier an, um ?(attr) xml-Referenzen in Java-Code aufzulösen.

Unterstützungsbibliothek

  • Die Verwendung von ?attr: (oder der ?-Kurzschreibweise) anstelle von ?android:attr verweist auf die support-Bibliothek , ist also wieder in API 7 verfügbar.

Ripples mit Bildern/Hintergründen

  • Um ein Bild oder einen Hintergrund und eine überlagerte Welligkeit zu haben, ist die einfachste Lösung, die View in eine FrameLayout mit der mit setForeground() oder setBackground() eingestellten Welligkeit einzuwickeln.

Ehrlich gesagt gibt es keine saubere Möglichkeit, dies ansonsten zu tun, obwohl Nick Butcher this zum Thema ImageViews mit Wellen geschrieben hat.

338
Ben De La Haye

Ich habe früher dafür gestimmt, diese Frage als Off-Topic zu schließen, aber eigentlich habe ich meine Meinung geändert, da dies ein sehr schöner visueller Effekt ist, der leider noch nicht in der Support-Bibliothek enthalten ist. Es wird höchstwahrscheinlich in zukünftigen Updates angezeigt, aber es ist kein Zeitrahmen angekündigt. 

Glücklicherweise gibt es nur wenige benutzerdefinierte Implementierungen:

einschließlich Widget-Sets zum Thema Materlial, die mit älteren Android-Versionen kompatibel sind:

sie können also eines davon ausprobieren oder nach anderen "Material-Widgets" oder so suchen ...

54
Marcin Orlowski

Ich habe eine einfache Klasse gemacht, die Ripple Buttons macht, ich habe sie am Ende nie gebraucht, also ist es nicht die beste. Aber hier ist es:

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

Da viele Leute nach so etwas suchen, habe ich eine Klasse gemacht, die andere Ansichten dazu bringen kann, den Welleneffekt zu bewirken:

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

Manchmal haben Sie einen benutzerdefinierten Hintergrund. In diesem Fall ist Android:foreground="?selectableItemBackground" eine bessere Lösung.

4
Kenny Orellana

Es ist sehr einfach ;-)

Zuerst müssen Sie zwei gezeichnete Dateien erstellen, eine für die alte API-Version und eine für die neueste Version. Natürlich! Wenn Sie die Zeichnungsdatei für die neueste API-Version erstellen, schlagen Sie im Android Studio vor, die alte automatisch zu erstellen. und stellen Sie schließlich diese Zeichenweise auf Ihre Hintergrundansicht ein.

Beispiel für neue API-Version (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>

Muster für alte API-Version (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>

Weitere Informationen zu Ripple Drawable finden Sie hier: https://developer.Android.com/reference/Android/graphics/drawable/RippleDrawable.html

4
Amintabar

manchmal wird diese Zeile für Layout oder Komponenten verwendet.

 Android:background="?attr/selectableItemBackground"

Wie als.

 <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