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?
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
?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
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 ImageView
s avec des ondulations.
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 ...
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);
}
}
Parfois, vous avez un arrière-plan personnalisé, dans ce cas, une meilleure solution consiste à utiliser Android:foreground="?selectableItemBackground"
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
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">