How to Create Material Ripple Effect in Sketchware
On MoreBlock:
}
public static class MaterialRippleLayout extends FrameLayout {
private static final int DEFAULT_DURATION = 350;
private static final int DEFAULT_FADE_DURATION = 75;
private static final float DEFAULT_DIAMETER_DP = 35;
private static final float DEFAULT_ALPHA = 0.2f;
private static final int DEFAULT_COLOR = Color.BLACK;
private static final int DEFAULT_BACKGROUND = Color.TRANSPARENT;
private static final boolean DEFAULT_HOVER = true;
private static final boolean DEFAULT_DELAY_CLICK = true;
private static final boolean DEFAULT_PERSISTENT = false;
private static final boolean DEFAULT_SEARCH_ADAPTER = false;
private static final boolean DEFAULT_RIPPLE_OVERLAY = false;
private static final int DEFAULT_ROUNDED_CORNERS = 0;
private static final int FADE_EXTRA_DELAY = 50;
private static final long HOVER_DURATION = 2500;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect bounds = new Rect();
private int rippleColor;
private boolean rippleOverlay;
private boolean rippleHover;
private int rippleDiameter;
private int rippleDuration;
private int rippleAlpha;
private boolean rippleDelayClick;
private int rippleFadeDuration;
private boolean ripplePersistent;
private android.graphics.drawable.Drawable rippleBackground;
private boolean rippleInAdapter;
private float rippleRoundedCorners;
private float radius;
private AdapterView parentAdapter;
private View childView;
private AnimatorSet rippleAnimator;
private ObjectAnimator hoverAnimator;
private Point currentCoords = new Point();
private Point previousCoords = new Point();
private int layerType;
private boolean eventCancelled;
private boolean prepressed;
private int positionInAdapter;
private GestureDetector gestureDetector;
private PerformClickEvent pendingClickEvent;
private PressedEvent pendingPressEvent;
public static RippleBuilder on(View view) {
return new RippleBuilder(view);
}
public MaterialRippleLayout(Context context) {
this(context, null, 0);
}
public MaterialRippleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaterialRippleLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setWillNotDraw(false);
gestureDetector = new GestureDetector(context, longClickListener);
rippleColor = DEFAULT_COLOR;
rippleDiameter = (int) dpToPx(getResources(), DEFAULT_DIAMETER_DP);
rippleOverlay = DEFAULT_RIPPLE_OVERLAY;
rippleHover = DEFAULT_HOVER;
rippleDuration = DEFAULT_DURATION;
rippleAlpha = (int) (255 * DEFAULT_ALPHA);
rippleDelayClick = DEFAULT_DELAY_CLICK;
rippleFadeDuration = DEFAULT_FADE_DURATION;
rippleBackground = new android.graphics.drawable.ColorDrawable(DEFAULT_BACKGROUND);
ripplePersistent = DEFAULT_PERSISTENT;
rippleInAdapter = DEFAULT_SEARCH_ADAPTER;
rippleRoundedCorners = DEFAULT_ROUNDED_CORNERS;
paint.setColor(rippleColor);
paint.setAlpha(rippleAlpha);
enableClipPathSupportIfNecessary();
}
@SuppressWarnings("unchecked")
public <T extends View> T getChildView() {
return (T) childView;
}
@Override
public final void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("MaterialRippleLayout can host only one child");
}
childView = child;
super.addView(child, index, params);
}
@Override
public void setOnClickListener(OnClickListener onClickListener) {
if (childView == null) {
throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
}
childView.setOnClickListener(onClickListener);
}
@Override
public void setOnLongClickListener(OnLongClickListener onClickListener) {
if (childView == null) {
throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
}
childView.setOnLongClickListener(onClickListener);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return !findClickableViewInChild(childView, (int) event.getX(), (int) event.getY());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean superOnTouchEvent = super.onTouchEvent(event);
if (!isEnabled() || !childView.isEnabled()) return superOnTouchEvent;
boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());
if (isEventInBounds) {
previousCoords.set(currentCoords.x, currentCoords.y);
currentCoords.set((int) event.getX(), (int) event.getY());
}
boolean gestureResult = gestureDetector.onTouchEvent(event);
if (gestureResult || hasPerformedLongPress) {
return true;
} else {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_UP:
pendingClickEvent = new PerformClickEvent();
if (prepressed) {
childView.setPressed(true);
postDelayed(
new Runnable() {
@Override public void run() {
childView.setPressed(false);
}
}, ViewConfiguration.getPressedStateDuration());
}
if (isEventInBounds) {
startRipple(pendingClickEvent);
} else if (!rippleHover) {
setRadius(0);
}
if (!rippleDelayClick && isEventInBounds) {
pendingClickEvent.run();
}
cancelPressedEvent();
break;
case MotionEvent.ACTION_DOWN:
setPositionInAdapter();
eventCancelled = false;
pendingPressEvent = new PressedEvent(event);
if (isInScrollingContainer()) {
cancelPressedEvent();
prepressed = true;
postDelayed(pendingPressEvent, ViewConfiguration.getTapTimeout());
} else {
pendingPressEvent.run();
}
break;
case MotionEvent.ACTION_CANCEL:
if (rippleInAdapter) {
currentCoords.set(previousCoords.x, previousCoords.y);
previousCoords = new Point();
}
childView.onTouchEvent(event);
if (rippleHover) {
if (!prepressed) {
startRipple(null);
}
} else {
childView.setPressed(false);
}
cancelPressedEvent();
break;
case MotionEvent.ACTION_MOVE:
if (rippleHover) {
if (isEventInBounds && !eventCancelled) {
invalidate();
} else if (!isEventInBounds) {
startRipple(null);
}
}
if (!isEventInBounds) {
cancelPressedEvent();
if (hoverAnimator != null) {
hoverAnimator.cancel();
}
childView.onTouchEvent(event);
eventCancelled = true;
}
break;
}
return true;
}
}
private void cancelPressedEvent() {
if (pendingPressEvent != null) {
removeCallbacks(pendingPressEvent);
prepressed = false;
}
}
private boolean hasPerformedLongPress;
private android.view.GestureDetector.SimpleOnGestureListener longClickListener = new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent e) {
hasPerformedLongPress = childView.performLongClick();
if (hasPerformedLongPress) {
if (rippleHover) {
startRipple(null);
}
cancelPressedEvent();
}
}
@Override
public boolean onDown(MotionEvent e) {
hasPerformedLongPress = false;
return super.onDown(e);
}
};
private void startHover() {
if (eventCancelled) return;
if (hoverAnimator != null) {
hoverAnimator.cancel();
}
final float radius = (float) (Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)) * 1.2f);
hoverAnimator = ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius)
.setDuration(HOVER_DURATION);
hoverAnimator.setInterpolator(new android.view.animation.LinearInterpolator());
hoverAnimator.start();
}
private void startRipple(final Runnable animationEndRunnable) {
if (eventCancelled) return;
float endRadius = getEndRadius();
cancelAnimations();
rippleAnimator = new AnimatorSet();
rippleAnimator.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
if (!ripplePersistent) {
setRadius(0);
setRippleAlpha(rippleAlpha);
}
if (animationEndRunnable != null && rippleDelayClick) {
animationEndRunnable.run();
}
childView.setPressed(false);
}
});
ObjectAnimator ripple = ObjectAnimator.ofFloat(this, radiusProperty, radius, endRadius);
ripple.setDuration(rippleDuration);
ripple.setInterpolator(new android.view.animation.DecelerateInterpolator());
ObjectAnimator fade = ObjectAnimator.ofInt(this, circleAlphaProperty, rippleAlpha, 0);
fade.setDuration(rippleFadeDuration);
fade.setInterpolator(new android.view.animation.AccelerateInterpolator());
fade.setStartDelay(rippleDuration - rippleFadeDuration - FADE_EXTRA_DELAY);
if (ripplePersistent) {
rippleAnimator.play(ripple);
} else if (getRadius() > endRadius) {
fade.setStartDelay(0);
rippleAnimator.play(fade);
} else {
rippleAnimator.playTogether(ripple, fade);
}
rippleAnimator.start();
}
private void cancelAnimations() {
if (rippleAnimator != null) {
rippleAnimator.cancel();
rippleAnimator.removeAllListeners();
}
if (hoverAnimator != null) {
hoverAnimator.cancel();
}
}
private float getEndRadius() {
final int width = getWidth();
final int height = getHeight();
final int halfWidth = width / 2;
final int halfHeight = height / 2;
final float radiusX = halfWidth > currentCoords.x ? width - currentCoords.x : currentCoords.x;
final float radiusY = halfHeight > currentCoords.y ? height - currentCoords.y : currentCoords.y;
return (float) Math.sqrt(Math.pow(radiusX, 2) + Math.pow(radiusY, 2)) * 1.2f;
}
private boolean isInScrollingContainer() {
ViewParent p = getParent();
while (p != null && p instanceof ViewGroup) {
if (((ViewGroup) p).shouldDelayChildPressedState()) {
return true;
}
p = p.getParent();
}
return false;
}
private AdapterView findParentAdapterView() {
if (parentAdapter != null) {
return parentAdapter;
}
ViewParent current = getParent();
while (true) {
if (current instanceof AdapterView) {
parentAdapter = (AdapterView) current;
return parentAdapter;
} else {
try {
current = current.getParent();
} catch (NullPointerException npe) {
throw new RuntimeException("Could not find a parent AdapterView");
}
}
}
}
private void setPositionInAdapter() {
if (rippleInAdapter) {
positionInAdapter = findParentAdapterView().getPositionForView(MaterialRippleLayout.this);
}
}
private boolean adapterPositionChanged() {
if (rippleInAdapter) {
int newPosition = findParentAdapterView().getPositionForView(MaterialRippleLayout.this);
final boolean changed = newPosition != positionInAdapter;
positionInAdapter = newPosition;
if (changed) {
cancelPressedEvent();
cancelAnimations();
childView.setPressed(false);
setRadius(0);
}
return changed;
}
return false;
}
private boolean findClickableViewInChild(View view, int x, int y) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
final Rect rect = new Rect();
child.getHitRect(rect);
final boolean contains = rect.contains(x, y);
if (contains) {
return findClickableViewInChild(child, x - rect.left, y - rect.top);
}
}
} else if (view != childView) {
return (view.isEnabled() && (view.isClickable() || view.isLongClickable() || view.isFocusableInTouchMode()));
}
return view.isFocusableInTouchMode();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bounds.set(0, 0, w, h);
rippleBackground.setBounds(bounds);
}
@Override
public boolean isInEditMode() {
return true;
}
@Override
public void draw(Canvas canvas) {
final boolean positionChanged = adapterPositionChanged();
if (rippleOverlay) {
if (!positionChanged) {
rippleBackground.draw(canvas);
}
super.draw(canvas);
if (!positionChanged) {
if (rippleRoundedCorners != 0) {
Path clipPath = new Path();
RectF rect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
clipPath.addRoundRect(rect, rippleRoundedCorners, rippleRoundedCorners, Path.Direction.CW);
canvas.clipPath(clipPath);
}
canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
}
} else {
if (!positionChanged) {
rippleBackground.draw(canvas);
canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
}
super.draw(canvas);
}
}
private Property<MaterialRippleLayout, Float> radiusProperty
= new Property<MaterialRippleLayout, Float>(Float.class, "radius") {
@Override
public Float get(MaterialRippleLayout object) {
return object.getRadius();
}
@Override
public void set(MaterialRippleLayout object, Float value) {
object.setRadius(value);
}
};
private float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}
private Property<MaterialRippleLayout, Integer> circleAlphaProperty
= new Property<MaterialRippleLayout, Integer>(Integer.class, "rippleAlpha") {
@Override
public Integer get(MaterialRippleLayout object) {
return object.getRippleAlpha();
}
@Override
public void set(MaterialRippleLayout object, Integer value) {
object.setRippleAlpha(value);
}
};
public int getRippleAlpha() {
return paint.getAlpha();
}
public void setRippleAlpha(Integer rippleAlpha) {
paint.setAlpha(rippleAlpha);
invalidate();
}
public void setRippleColor(int rippleColor) {
this.rippleColor = rippleColor;
paint.setColor(rippleColor);
paint.setAlpha(rippleAlpha);
invalidate();
}
public void setRippleOverlay(boolean rippleOverlay) {
this.rippleOverlay = rippleOverlay;
}
public void setRippleDiameter(int rippleDiameter) {
this.rippleDiameter = rippleDiameter;
}
public void setRippleDuration(int rippleDuration) {
this.rippleDuration = rippleDuration;
}
public void setRippleBackground(int color) {
rippleBackground = new android.graphics.drawable.ColorDrawable(color);
rippleBackground.setBounds(bounds);
invalidate();
}
public void setRippleHover(boolean rippleHover) {
this.rippleHover = rippleHover;
}
public void setRippleDelayClick(boolean rippleDelayClick) {
this.rippleDelayClick = rippleDelayClick;
}
public void setRippleFadeDuration(int rippleFadeDuration) {
this.rippleFadeDuration = rippleFadeDuration;
}
public void setRipplePersistent(boolean ripplePersistent) {
this.ripplePersistent = ripplePersistent;
}
public void setRippleInAdapter(boolean rippleInAdapter) {
this.rippleInAdapter = rippleInAdapter;
}
public void setRippleRoundedCorners(int rippleRoundedCorner) {
this.rippleRoundedCorners = rippleRoundedCorner;
enableClipPathSupportIfNecessary();
}
public void setDefaultRippleAlpha(float alpha) {
this.rippleAlpha = (int) (255 * alpha);
paint.setAlpha(rippleAlpha);
invalidate();
}
public void performRipple() {
currentCoords = new Point(getWidth() / 2, getHeight() / 2);
startRipple(null);
}
public void performRipple(Point anchor) {
currentCoords = new Point(anchor.x, anchor.y);
startRipple(null);
}
private void enableClipPathSupportIfNecessary() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (rippleRoundedCorners != 0) {
layerType = getLayerType();
setLayerType(LAYER_TYPE_SOFTWARE, null);
} else {
setLayerType(layerType, null);
}
}
}
private class PerformClickEvent implements Runnable {
@Override public void run() {
if (hasPerformedLongPress) return;
if (getParent() instanceof AdapterView) {
if (!childView.performClick())
clickAdapterView((AdapterView) getParent());
} else if (rippleInAdapter) {
clickAdapterView(findParentAdapterView());
} else {
childView.performClick();
}
}
private void clickAdapterView(AdapterView parent) {
final int position = parent.getPositionForView(MaterialRippleLayout.this);
final long itemId = parent.getAdapter() != null
? parent.getAdapter().getItemId(position)
: 0;
if (position != AdapterView.INVALID_POSITION) {
parent.performItemClick(MaterialRippleLayout.this, position, itemId);
}
}
}
private final class PressedEvent implements Runnable {
private final MotionEvent event;
public PressedEvent(MotionEvent event) {
this.event = event;
}
@Override
public void run() {
prepressed = false;
childView.setLongClickable(false);
childView.onTouchEvent(event);
childView.setPressed(true);
if (rippleHover) {
startHover();
}
}
}
static float dpToPx(android.content.res.Resources resources, float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
}
public static class RippleBuilder {
private final Context context;
private final View child;
private int rippleColor = DEFAULT_COLOR;
private boolean rippleOverlay = DEFAULT_RIPPLE_OVERLAY;
private boolean rippleHover = DEFAULT_HOVER;
private float rippleDiameter = DEFAULT_DIAMETER_DP;
private int rippleDuration = DEFAULT_DURATION;
private float rippleAlpha = DEFAULT_ALPHA;
private boolean rippleDelayClick = DEFAULT_DELAY_CLICK;
private int rippleFadeDuration = DEFAULT_FADE_DURATION;
private boolean ripplePersistent = DEFAULT_PERSISTENT;
private int rippleBackground = DEFAULT_BACKGROUND;
private boolean rippleSearchAdapter = DEFAULT_SEARCH_ADAPTER;
private float rippleRoundedCorner = DEFAULT_ROUNDED_CORNERS;
public RippleBuilder(View child) {
this.child = child;
this.context = child.getContext();
}
public RippleBuilder rippleColor(int color) {
this.rippleColor = color;
return this;
}
public RippleBuilder rippleOverlay(boolean overlay) {
this.rippleOverlay = overlay;
return this;
}
public RippleBuilder rippleHover(boolean hover) {
this.rippleHover = hover;
return this;
}
public RippleBuilder rippleDiameterDp(int diameterDp) {
this.rippleDiameter = diameterDp;
return this;
}
public RippleBuilder rippleDuration(int duration) {
this.rippleDuration = duration;
return this;
}
public RippleBuilder rippleAlpha(float alpha) {
this.rippleAlpha = alpha;
return this;
}
public RippleBuilder rippleDelayClick(boolean delayClick) {
this.rippleDelayClick = delayClick;
return this;
}
public RippleBuilder rippleFadeDuration(int fadeDuration) {
this.rippleFadeDuration = fadeDuration;
return this;
}
public RippleBuilder ripplePersistent(boolean persistent) {
this.ripplePersistent = persistent;
return this;
}
public RippleBuilder rippleBackground(int color) {
this.rippleBackground = color;
return this;
}
public RippleBuilder rippleInAdapter(boolean inAdapter) {
this.rippleSearchAdapter = inAdapter;
return this;
}
public RippleBuilder rippleRoundedCorners(int radiusDp) {
this.rippleRoundedCorner = radiusDp;
return this;
}
public MaterialRippleLayout create() {
MaterialRippleLayout layout = new MaterialRippleLayout(context);
layout.setRippleColor(rippleColor);
layout.setDefaultRippleAlpha(rippleAlpha);
layout.setRippleDelayClick(rippleDelayClick);
layout.setRippleDiameter((int) dpToPx(context.getResources(), rippleDiameter));
layout.setRippleDuration(rippleDuration);
layout.setRippleFadeDuration(rippleFadeDuration);
layout.setRippleHover(rippleHover);
layout.setRipplePersistent(ripplePersistent);
layout.setRippleOverlay(rippleOverlay);
layout.setRippleBackground(rippleBackground);
layout.setRippleInAdapter(rippleSearchAdapter);
layout.setRippleRoundedCorners((int) dpToPx(context.getResources(), rippleRoundedCorner));
ViewGroup.LayoutParams params = child.getLayoutParams();
ViewGroup parent = (ViewGroup) child.getParent();
int index = 0;
if (parent != null && parent instanceof MaterialRippleLayout) {
throw new IllegalStateException("MaterialRippleLayout could not be created: parent of the view already is a MaterialRippleLayout");
}
if (parent != null) {
index = parent.indexOfChild(child);
parent.removeView(child);
}
layout.addView(child, new ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.MATCH_PARENT));
if (parent != null) {
parent.addView(layout, index, params);
}
return layout;
}
}
}
{
OnCreate:
MaterialRippleLayout.on(imageview1)
.rippleColor(Color.RED)
.create();
Comments
Post a Comment