Created
September 25, 2014 16:15
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Please make as project