How to Achieve Ripple Animation Using Support Library

How to achieve ripple animation using support library?

Basic ripple setup

  • Ripples contained within the view.

    android:background="?selectableItemBackground"

  • Ripples that extend beyond the view's bounds:

    android:background="?selectableItemBackgroundBorderless"

    Have a look here for resolving ?(attr) xml references in Java code.

Support Library

  • Using ?attr: (or the ? shorthand) instead of ?android:attr references the support library, so is available back to API 7.

Ripples with images/backgrounds

  • To have a image or background and overlaying ripple the easiest solution is to wrap the View in a FrameLayout with the ripple set with setForeground() or setBackground().

Honestly there is no clean way of doing this otherwise.

Continuous ripple animation - Android

Please check below code,

public class RippleBackground extends RelativeLayout{

private static final int DEFAULT_RIPPLE_COUNT=6;
private static final int DEFAULT_DURATION_TIME=3000;
private static final float DEFAULT_SCALE=6.0f;
private static final int DEFAULT_FILL_TYPE=0;

private int rippleColor;
private float rippleStrokeWidth;
private float rippleRadius;
private int rippleDurationTime;
private int rippleAmount;
private int rippleDelay;
private float rippleScale;
private int rippleType;
private Paint paint;
private boolean animationRunning=false;
private AnimatorSet animatorSet;
private ArrayList<Animator> animatorList;
private LayoutParams rippleParams;
private ArrayList<RippleView> rippleViewList=new ArrayList<RippleView>();

public RippleBackground(Context context) {
super(context);
}

public RippleBackground(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

public RippleBackground(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

private void init(final Context context, final AttributeSet attrs) {
if (isInEditMode())
return;

if (null == attrs) {
throw new IllegalArgumentException("Attributes should be provided to this view,");
}

final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleBackground);
rippleColor=typedArray.getColor(R.styleable.RippleBackground_rb_color, getResources().getColor(R.color.rippelColor));
rippleStrokeWidth=typedArray.getDimension(R.styleable.RippleBackground_rb_strokeWidth, getResources().getDimension(R.dimen.rippleStrokeWidth));
rippleRadius=typedArray.getDimension(R.styleable.RippleBackground_rb_radius,getResources().getDimension(R.dimen.rippleRadius));
rippleDurationTime=typedArray.getInt(R.styleable.RippleBackground_rb_duration,DEFAULT_DURATION_TIME);
rippleAmount=typedArray.getInt(R.styleable.RippleBackground_rb_rippleAmount,DEFAULT_RIPPLE_COUNT);
rippleScale=typedArray.getFloat(R.styleable.RippleBackground_rb_scale,DEFAULT_SCALE);
rippleType=typedArray.getInt(R.styleable.RippleBackground_rb_type,DEFAULT_FILL_TYPE);
typedArray.recycle();

rippleDelay=rippleDurationTime/rippleAmount;

paint = new Paint();
paint.setAntiAlias(true);
if(rippleType==DEFAULT_FILL_TYPE){
rippleStrokeWidth=0;
paint.setStyle(Paint.Style.FILL);
}else
paint.setStyle(Paint.Style.STROKE);
paint.setColor(rippleColor);

rippleParams=new LayoutParams((int)(2*(rippleRadius+rippleStrokeWidth)),(int)(2*(rippleRadius+rippleStrokeWidth)));
rippleParams.addRule(CENTER_IN_PARENT, TRUE);

animatorSet = new AnimatorSet();
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorList=new ArrayList<Animator>();

for(int i=0;i<rippleAmount;i++){
RippleView rippleView=new RippleView(getContext());
addView(rippleView,rippleParams);
rippleViewList.add(rippleView);
final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(rippleView, "ScaleX", 1.0f, rippleScale);
scaleXAnimator.setRepeatCount(ObjectAnimator.INFINITE);
scaleXAnimator.setRepeatMode(ObjectAnimator.RESTART);
scaleXAnimator.setStartDelay(i * rippleDelay);
scaleXAnimator.setDuration(rippleDurationTime);
animatorList.add(scaleXAnimator);
final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(rippleView, "ScaleY", 1.0f, rippleScale);
scaleYAnimator.setRepeatCount(ObjectAnimator.INFINITE);
scaleYAnimator.setRepeatMode(ObjectAnimator.RESTART);
scaleYAnimator.setStartDelay(i * rippleDelay);
scaleYAnimator.setDuration(rippleDurationTime);
animatorList.add(scaleYAnimator);
final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(rippleView, "Alpha", 1.0f, 0f);
alphaAnimator.setRepeatCount(ObjectAnimator.INFINITE);
alphaAnimator.setRepeatMode(ObjectAnimator.RESTART);
alphaAnimator.setStartDelay(i * rippleDelay);
alphaAnimator.setDuration(rippleDurationTime);
animatorList.add(alphaAnimator);
}

animatorSet.playTogether(animatorList);
}

private class RippleView extends View{

public RippleView(Context context) {
super(context);
this.setVisibility(View.INVISIBLE);
}

@Override
protected void onDraw(Canvas canvas) {
int radius=(Math.min(getWidth(),getHeight()))/2;
canvas.drawCircle(radius,radius,radius-rippleStrokeWidth,paint);
}
}

public void startRippleAnimation(){
if(!isRippleAnimationRunning()){
for(RippleView rippleView:rippleViewList){
rippleView.setVisibility(VISIBLE);
}
animatorSet.start();
animationRunning=true;
}
}

public void stopRippleAnimation(){
if(isRippleAnimationRunning()){
animatorSet.end();
animationRunning=false;
}
}

public boolean isRippleAnimationRunning(){
return animationRunning;
}
}

in attrs.xml file add this ,

<declare-styleable name="RippleBackground">
<attr name="rb_color" format="color" />
<attr name="rb_strokeWidth" format="dimension"/>
<attr name="rb_radius" format="dimension"/>
<attr name="rb_duration" format="integer"/>
<attr name="rb_rippleAmount" format="integer"/>
<attr name="rb_scale" format="float"/>
<attr name="rb_type" format="enum">
<enum name="fillRipple" value="0"/>
<enum name="strokeRipple" value="1"/>
</attr>
</declare-styleable>

add color in color.xml file

<color name="rippelColor">#0099CC</color>

add dimension in dimen.xml file

<dimen name="rippleStrokeWidth">2dp</dimen>
<dimen name="rippleRadius">64dp</dimen>

After adding above code just start animation like this in your activity

rippleBackground.startRippleAnimation();

Play ripple animation without user interaction

Unfortunately no accessible method is available to play ripple animation. But I could think of two possible ways to achieve this

#1 Using reflection

public static void simulateButtonPress(final View view)
{
Drawable drawable = view.getBackground();
if( drawable instanceof RippleDrawable )
{
try
{
final RippleDrawable rd = ((RippleDrawable)drawable);
final Method setRippleActive = rd.getClass().getDeclaredMethod("setRippleActive", boolean.class);
setRippleActive.setAccessible(true);
setRippleActive.invoke(rd, true); //setRippleActive(true)

//exit ripple effect after 250 milliseconds
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
try
{
setRippleActive.invoke(rd, false); //setRippleActive(false)
}
catch (Exception e)
{
e.printStackTrace();
}
}
}, 250);

}
catch (Exception e)
{
e.printStackTrace();
}
}
}

#2 By simulating Motion Events ACTION_DOWN and ACTION_CANCEL

public static void simulateButtonPress(final View view)
{
final long now = SystemClock.uptimeMillis();
final MotionEvent pressEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, view.getWidth()/2, view.getHeight()/2, 0);
view.dispatchTouchEvent(pressEvent);

new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
final long now = SystemClock.uptimeMillis();
final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, view.getWidth()/2, view.getHeight()/2, 0);
view.dispatchTouchEvent(cancelEvent);
}
}, 250);
}

Ripple animation over image button difficulty

You set the ripple as the background attribute.

That means it is drawn behind the image src.

Instead, set it as the foreground of this view or it's parent:

android:foreground="@drawable/ripple_animation"


Related Topics



Leave a reply



Submit