Skip to content

Instantly share code, notes, and snippets.

@CasperPas
Created November 10, 2014 19:00
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save CasperPas/c094c45814cae9b58e81 to your computer and use it in GitHub Desktop.
Save CasperPas/c094c45814cae9b58e81 to your computer and use it in GitHub Desktop.
Helper class for creating Ripple effect Like Paper-Ripple from Polymer or in Material Design
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<vn.casperpas.bbnext.entities.PaperRippleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/label"
android:textSize="16sp"
android:singleLine="true"
android:textColor="#333333"
android:text="Hello world!"
android:padding="10sp" />
</vn.casperpas.bbnext.entities.PaperRippleLayout>
</LinearLayout>
package vn.casperpas.bbnext.entities;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.v4.view.MotionEventCompat;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Created by casper on 11/8/14.
* This is where the magic happens :D
*/
public class PaperRipple {
private final int EASE_ANIM_DURATION = 200;
private final int MAX_RIPPLE_ALPHA = 255;
private final int MAX_RIPPLE_RADIUS = 300;
private View mView;
private RectF mDrawRect;
private int mClipRadius;
private Path mCirclePath = new Path();
private boolean hasRippleEffect = false;
private int mEffectColor = Color.LTGRAY;
private int mMaxRadius = MAX_RIPPLE_RADIUS;
private int mMaxAlpha = MAX_RIPPLE_ALPHA;
private int animDuration = EASE_ANIM_DURATION;
private AnimationSet mAnimationSet = null;
private ArrayList<RippleWave> mWavesList = new ArrayList<RippleWave>();
private RippleWave mCurrentWave = null;
public PaperRipple(View mView) {
this.mView = mView;
this.mDrawRect = new RectF(0, 0, mView.getWidth(), mView.getHeight());
this.mAnimationSet = new AnimationSet(true);
}
public void setHasRippleEffect(boolean hasRippleEffect) {
this.hasRippleEffect = hasRippleEffect;
}
public void setAnimDuration(int animDuration) {
this.animDuration = animDuration;
}
public void setEffectColor(int effectColor) {
mEffectColor = effectColor;
}
public void setClipRadius(int mClipRadius) {
this.mClipRadius = mClipRadius;
}
public void setRippleSize(int radius) {
mMaxRadius = radius;
}
public void setMaxAlpha(int alpha) {
mMaxAlpha = alpha;
}
public void onTouchEvent(final MotionEvent event) {
if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_CANCEL) {
mCurrentWave.fadeOutWave();
} else if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) {
mCurrentWave.fadeOutWave();
} else if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
mCurrentWave = new RippleWave(mView, mAnimationSet, animDuration, mEffectColor);
mCurrentWave.setMaxAlpha(mMaxAlpha);
mCurrentWave.setWaveMaxRadius(mMaxRadius);
mCurrentWave.startWave(event.getX(), event.getY());
mWavesList.add(mCurrentWave);
}
}
public void onDraw(final Canvas canvas) {
if (hasRippleEffect && mWavesList.size() > 0) {
mDrawRect.set(0, 0, mView.getWidth(), mView.getHeight());
mCirclePath.reset();
mCirclePath.addRoundRect(this.mDrawRect,
mClipRadius, mClipRadius, Path.Direction.CW);
canvas.clipPath(mCirclePath);
// Draw ripples
for (Iterator<RippleWave> iterator = mWavesList.iterator(); iterator.hasNext();) {
RippleWave wave = iterator.next();
if (wave.isFinished()) {
iterator.remove();
} else {
wave.onDraw(canvas);
}
}
}
}
class ValueGeneratorAnim extends Animation {
private InterpolatedTimeCallback interpolatedTimeCallback;
ValueGeneratorAnim(InterpolatedTimeCallback interpolatedTimeCallback) {
this.interpolatedTimeCallback = interpolatedTimeCallback;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
this.interpolatedTimeCallback.onTimeUpdate(interpolatedTime);
}
}
interface InterpolatedTimeCallback {
public void onTimeUpdate(float interpolatedTime);
}
private class RippleWave {
private final float opacityDecayVelocity = 1.75f;
private int waveMaxRadius = MAX_RIPPLE_RADIUS;
private boolean waveFinished = false;
private AnimationSet mAnimationSet = new AnimationSet(true);
private View mView;
private int animDuration = EASE_ANIM_DURATION;
private int mCircleAlpha = MAX_RIPPLE_ALPHA;
private int mMaxAlpha = MAX_RIPPLE_ALPHA;
private Paint mCirclePaint = new Paint();
private float mDownX;
private float mDownY;
private float mRadius;
private Animation.AnimationListener radiusAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
private Animation.AnimationListener opacityAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
waveFinished = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
public RippleWave(View view, AnimationSet animationSet, int animDuration, int effectColor) {
this.mView = view;
this.mAnimationSet = animationSet;
this.animDuration = animDuration;
this.mCirclePaint.setColor(effectColor);
}
private int waveOpacity(float interpolatedTime) {
int opacity = (int)Math.max(0, mMaxAlpha - (mMaxAlpha * interpolatedTime * this.opacityDecayVelocity));
return opacity;
}
private float waveRadius(float interpolatedTime) {
float radius = waveMaxRadius * 1.1f + 5;
float size = radius * (1 - (float)Math.pow(80, -interpolatedTime));
return size;
}
public boolean isFinished() {
return waveFinished;
}
public void startWave(float x, float y) {
mDownX = x;
mDownY = y;
mCircleAlpha = mMaxAlpha;
ValueGeneratorAnim valueGeneratorAnim = new ValueGeneratorAnim(new InterpolatedTimeCallback() {
@Override
public void onTimeUpdate(float interpolatedTime) {
mRadius = RippleWave.this.waveRadius(interpolatedTime);
mView.invalidate();
}
});
valueGeneratorAnim.setInterpolator(new DecelerateInterpolator());
valueGeneratorAnim.setDuration(animDuration);
valueGeneratorAnim.setAnimationListener(radiusAnimationListener);
mAnimationSet.addAnimation(valueGeneratorAnim);
if (!mAnimationSet.hasStarted() || mAnimationSet.hasEnded()) {
mView.startAnimation(mAnimationSet);
}
}
public void fadeOutWave() {
ValueGeneratorAnim valueGeneratorAnim = new ValueGeneratorAnim(new InterpolatedTimeCallback() {
@Override
public void onTimeUpdate(float interpolatedTime) {
mCircleAlpha = RippleWave.this.waveOpacity(interpolatedTime);
mView.invalidate();
}
});
valueGeneratorAnim.setDuration(animDuration);
valueGeneratorAnim.setAnimationListener(opacityAnimationListener);
// If all animations stopped
if (mAnimationSet.hasEnded()) {
mAnimationSet.getAnimations().clear();
mAnimationSet.addAnimation(valueGeneratorAnim);
mView.startAnimation(mAnimationSet);
} else {
// Add new animation to current set
mAnimationSet.addAnimation(valueGeneratorAnim);
}
}
public void onDraw(final Canvas canvas) {
mCirclePaint.setAlpha(mCircleAlpha);
canvas.drawCircle(mDownX, mDownY, mRadius, mCirclePaint);
}
public void setWaveMaxRadius(int radius) {
waveMaxRadius = radius;
}
public void setMaxAlpha(int alpha) {
mMaxAlpha = alpha;
}
}
}
package vn.casperpas.bbnext.entities;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import vn.casperpas.bbnext.R;
/**
* User: casper
* Date: 8.11.2014
* Time: 17:23
*/
// Using RelativeLayout is just an example. You can use PaperRipple with any View object
public class PaperRippleLayout extends RelativeLayout {
private PaperRipple paperRipple;
public PaperRippleLayout(Context context) {
super(context);
init(context, null);
}
public PaperRippleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// you should set a background to view for effect to be visible. in this sample, this
// linear layout contains a transparent background which is set inside the XML
// giving the view to animate on
paperRipple = new PaperRipple(this);
// enabling ripple effect. it only performs ease effect without enabling ripple effect
paperRipple.setHasRippleEffect(true);
int rippleColor = Color.LTGRAY;
int animationDuration = 1000;
int clipRadius = 0;
// Remove this if you don't use custom style attrs via XML
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PaperRippleLayout);
rippleColor = a.getColor(R.styleable.PaperRippleLayout_ripple_color, rippleColor);
animationDuration = a.getInt(R.styleable.PaperRippleLayout_duration, animationDuration);
clipRadius = a.getInt(R.styleable.PaperRippleLayout_clip_radius, clipRadius);
}
// setting the effect color
paperRipple.setEffectColor(rippleColor);
// setting the duration
paperRipple.setAnimDuration(animationDuration);
// setting radius to clip the effect. use it if you have a rounded background
paperRipple.setClipRadius(clipRadius);
// the view must contain an onClickListener to receive UP touch events. paperRipple
// doesn't return any value in onTouchEvent for flexibility. so it is developers
// responsibility to add a listener
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
}
});
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
// send the touch event to animator
paperRipple.onTouchEvent(event);
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
// let animator show the animation by applying changes to view's canvas
paperRipple.onDraw(canvas);
super.onDraw(canvas);
}
}
@DeveloperPaul123
Copy link

This is great!! Is this code open source?

Copy link

ghost commented Oct 13, 2015

It is not working :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment