web-dev-qa-db-fra.com

Comment réaliser une animation à l'aide de la bibliothèque de support?

J'essaie d'ajouter une animation d'ondulation sur un clic de bouton. J'ai aimé ci-dessous mais il faut 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>

Bouton

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

Je veux le rendre compatible avec la bibliothèque de conception.

Comment cela peut être fait?

155
N Sharma

Configuration d'ondulation de base

  • Ondulations contenues dans la vue.
    Android:background="?selectableItemBackground"

  • Ondulations qui dépassent les limites de la vue:
    Android:background="?selectableItemBackgroundBorderless"

    Consultez ici pour résoudre les références ?(attr) xml en code Java.

Bibliothèque de support

  • L'utilisation de ?attr: (ou du ? en abrégé) au lieu de ?android:attr fait référence à support library , de sorte que l'API 7 est disponible à nouveau.

Ondulations avec images/arrière-plans

  • Pour avoir une image ou un fond et une ondulation superposée, la solution la plus simple consiste à envelopper la View dans une FrameLayout avec l'ondulation définie avec setForeground() ou setBackground().

Honnêtement, il n’existe pas de moyen propre d’agir autrement, même si Nick Butcher a posté ceci sur le sujet de ImageViews avec des ondulations.

338
Ben De La Haye

Auparavant, j'avais voté en faveur de la fermeture de cette question, mais en fait, j'ai changé d'avis car c'est un très bel effet visuel qui, malheureusement, ne fait pas encore partie de la bibliothèque de support. Il apparaîtra probablement dans une mise à jour future, mais aucun délai n’a été annoncé. 

Heureusement, peu d'implémentations personnalisées sont déjà disponibles:

comprenant des ensembles de widgets sur le thème Materlial compatibles avec les anciennes versions d’Android:

afin que vous puissiez essayer un de ceux-ci ou google pour d'autres "widgets matériels" ou alors ...

54
Marcin Orlowski

J'ai fait un cours simple qui fabrique des boutons avec des ondulations, je n'en ai jamais eu besoin au final donc ce n'est pas le meilleur, mais le voici:

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

MODIFIER

Comme beaucoup de gens recherchent quelque chose comme ça, j'ai créé un cours qui permet de faire en sorte que d'autres vues aient un effet d'entraînement:

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

Parfois, vous avez un arrière-plan personnalisé, dans ce cas, une meilleure solution consiste à utiliser Android:foreground="?selectableItemBackground"

4
Kenny Orellana

C'est très simple ;-)

Tout d’abord, vous devez créer deux fichiers dessiables, l’un pour l’ancienne version de l’API et l’autre pour la version la plus récente, bien sûr! si vous créez le fichier extractible pour la dernière version de l’API, Android Studio vous suggère de créer l’ancien automatiquement. et finalement définissez ceci drawable à votre vue d’arrière-plan.

Exemple dessinable pour la nouvelle version de l'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>

Exemple dessinable pour l'ancienne version de l'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>

Pour plus d'informations sur Ripple Drawable, visitez le site: https://developer.Android.com/reference/Android/graphics/drawable/RippleDrawable.html

4
Amintabar

parfois, cette ligne sera utilisable sur n’importe quelle disposition ou composant.

 Android:background="?attr/selectableItemBackground"

Comme en.

 <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