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?
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
?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
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 ImageView
s mit Wellen geschrieben hat.
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 ...
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);
}
}
Manchmal haben Sie einen benutzerdefinierten Hintergrund. In diesem Fall ist Android:foreground="?selectableItemBackground"
eine bessere Lösung.
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
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">