Skip to content

Instantly share code, notes, and snippets.

@eluleci
Created September 25, 2014 16:15
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eluleci/6e0d02c766b27f6a5253 to your computer and use it in GitHub Desktop.
Save eluleci/6e0d02c766b27f6a5253 to your computer and use it in GitHub Desktop.
Ease and ripple touch effects (Android L Like Touch Effects) on Android views
<com.cengalabs.flatui.sample.RippleLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:layout_marginBottom="15dp"
android:background="@android:color/transparent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="16dp"
android:text="@string/touch_effect_description"
android:textColor="#333333"/>
</com.cengalabs.flatui.sample.RippleLinearLayout>
package com.cengalabs.flatui.sample;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import com.cengalabs.flatui.TouchEffectAnimator;
/**
* User: eluleci
* Date: 25.09.2014
* Time: 17:23
*/
public class RippleLinearLayout extends LinearLayout {
private TouchEffectAnimator touchEffectAnimator;
public RippleLinearLayout(Context context) {
super(context);
init();
}
public RippleLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// 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
touchEffectAnimator = new TouchEffectAnimator(this);
// enabling ripple effect. it only performs ease effect without enabling ripple effect
touchEffectAnimator.setHasRippleEffect(true);
// setting the effect color
touchEffectAnimator.setEffectColor(Color.LTGRAY);
// setting the duration
touchEffectAnimator.setAnimDuration(1000);
// setting radius to clip the effect. use it if you have a rounded background
touchEffectAnimator.setClipRadius(20);
// the view must contain an onClickListener to receive UP touch events. touchEffectAnimator
// 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
touchEffectAnimator.onTouchEvent(event);
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
// let animator show the animation by applying changes to view's canvas
touchEffectAnimator.onDraw(canvas);
super.onDraw(canvas);
}
}
package com.cengalabs.flatui;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
/**
* User: eluleci
* Date: 25.09.2014
* Time: 00:34
*
* This class adds touch effects to the given View. The effect animation is triggered by onTouchEvent
* of the View and this class is injected into the onDraw function of the View to perform animation.
*
*/
public class TouchEffectAnimator {
private final int EASE_ANIM_DURATION = 200;
private final int RIPPLE_ANIM_DURATION = 300;
private final int MAX_RIPPLE_ALPHA = 255;
private View mView;
private int mClipRadius;
private boolean hasRippleEffect = false;
private int animDuration = EASE_ANIM_DURATION;
private int requiredRadius;
private float mDownX;
private float mDownY;
private float mRadius;
private int mCircleAlpha = MAX_RIPPLE_ALPHA;
private int mRectAlpha = 0;
private Paint mCirclePaint = new Paint();
private Paint mRectPaint = new Paint();
private Path mCirclePath = new Path();
private Path mRectPath = new Path();
private boolean isTouchReleased = false;
private boolean isAnimatingFadeIn = false;
private Animation.AnimationListener animationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
isAnimatingFadeIn = true;
}
@Override
public void onAnimationEnd(Animation animation) {
isAnimatingFadeIn = false;
if (isTouchReleased) fadeOutEffect();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
public TouchEffectAnimator(View mView) {
this.mView = mView;
}
public void setHasRippleEffect(boolean hasRippleEffect) {
this.hasRippleEffect = hasRippleEffect;
if (hasRippleEffect) animDuration = RIPPLE_ANIM_DURATION;
}
public void setAnimDuration(int animDuration) {
this.animDuration = animDuration;
}
public void setEffectColor(int effectColor) {
mCirclePaint.setColor(effectColor);
mCirclePaint.setAlpha(mCircleAlpha);
mRectPaint.setColor(effectColor);
mRectPaint.setAlpha(mRectAlpha);
}
public void setClipRadius(int mClipRadius) {
this.mClipRadius = mClipRadius;
}
public void onTouchEvent(final MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
isTouchReleased = true;
if (!isAnimatingFadeIn) {
fadeOutEffect();
}
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
isTouchReleased = true;
if (!isAnimatingFadeIn) {
fadeOutEffect();
}
} else if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// gets the bigger value (width or height) to fit the circle
requiredRadius = mView.getWidth() > mView.getHeight() ? mView.getWidth() : mView.getHeight();
// increasing radius for circle to reach from corner to other corner
requiredRadius *= 1.2;
isTouchReleased = false;
mDownX = event.getX();
mDownY = event.getY();
mCircleAlpha = MAX_RIPPLE_ALPHA;
mRectAlpha = 0;
ValueGeneratorAnim valueGeneratorAnim = new ValueGeneratorAnim(new InterpolatedTimeCallback() {
@Override
public void onTimeUpdate(float interpolatedTime) {
if (hasRippleEffect)
mRadius = requiredRadius * interpolatedTime;
mRectAlpha = (int) (interpolatedTime * MAX_RIPPLE_ALPHA);
mView.invalidate();
}
});
valueGeneratorAnim.setInterpolator(new DecelerateInterpolator());
valueGeneratorAnim.setDuration(animDuration);
valueGeneratorAnim.setAnimationListener(animationListener);
mView.startAnimation(valueGeneratorAnim);
}
}
public void onDraw(final Canvas canvas) {
if (hasRippleEffect) {
mCirclePath.reset();
mCirclePaint.setAlpha(mCircleAlpha);
mCirclePath.addRoundRect(new RectF(0, 0, mView.getWidth(), mView.getHeight()),
mClipRadius, mClipRadius, Path.Direction.CW);
canvas.clipPath(mCirclePath);
canvas.drawCircle(mDownX, mDownY, mRadius, mCirclePaint);
}
mRectPath.reset();
if (hasRippleEffect && mCircleAlpha != 255) mRectAlpha = mCircleAlpha / 2;
mRectPaint.setAlpha(mRectAlpha);
canvas.drawRoundRect(new RectF(0, 0, mView.getWidth(), mView.getHeight()), mClipRadius,
mClipRadius, mRectPaint);
}
private void fadeOutEffect() {
ValueGeneratorAnim valueGeneratorAnim = new ValueGeneratorAnim(new InterpolatedTimeCallback() {
@Override
public void onTimeUpdate(float interpolatedTime) {
mCircleAlpha = (int) (MAX_RIPPLE_ALPHA - (MAX_RIPPLE_ALPHA * interpolatedTime));
mRectAlpha = mCircleAlpha;
mView.invalidate();
}
});
valueGeneratorAnim.setDuration(animDuration);
mView.startAnimation(valueGeneratorAnim);
}
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);
}
}
@stickylabdev
Copy link

Please make as project

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