Skip to content

Instantly share code, notes, and snippets.

@murdly
Created March 24, 2018 18:04
Show Gist options
  • Save murdly/88a79c2d5a2f58bb9388d3a926b11539 to your computer and use it in GitHub Desktop.
Save murdly/88a79c2d5a2f58bb9388d3a926b11539 to your computer and use it in GitHub Desktop.
Shine effect
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewPropertyAnimator;
import static android.graphics.drawable.GradientDrawable.LINEAR_GRADIENT;
public class Shimmer extends View {
private static final float MILLISECONDS_PER_PX = 0.195f;
/**
* * START_END
* x |
* x V
* x
* x ---
* x ---
* x---
*
* -->
*
* END_START
* <--
*
* ---x
* --- x
* A --- x
* | x
* x
* x
*/
public enum Direction {
START_END,
END_START
}
/**
* * TOP_LEFT_BOTTOM_RIGHT
* x--
* -x-
* --x
*
* TOP_RIGHT_BOTTOM_LEFT
* --x
* -x-
* x--
*/
public enum Angle {
TOP_LEFT_BOTTOM_RIGHT,
TOP_RIGHT_BOTTOM_LEFT
}
private Direction direction = Direction.START_END;
private Angle angle = Angle.TOP_LEFT_BOTTOM_RIGHT;
private ViewPropertyAnimator animator;
private boolean animationStarted;
public Shimmer(Context context) {
this(context, null);
}
public Shimmer(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setVisibility(INVISIBLE);
setAlpha(0);
invalidateDrawable();
}
private void invalidateDrawable() {
setBackground(createShimmerDrawable(angle));
}
private GradientDrawable createShimmerDrawable(Angle angle) {
final GradientDrawable gradient = new GradientDrawable();
gradient.setColors(new int[]{
Color.parseColor("#00000000"),
Color.parseColor("#40FFFFFF"),
Color.parseColor("#00000000")
});
gradient.setGradientType(LINEAR_GRADIENT);
switch (angle) {
case TOP_LEFT_BOTTOM_RIGHT:
gradient.setOrientation(GradientDrawable.Orientation.TL_BR);
break;
case TOP_RIGHT_BOTTOM_LEFT:
gradient.setOrientation(GradientDrawable.Orientation.TR_BL);
break;
}
return gradient;
}
public void setAngle(Angle angle) {
this.angle = angle;
invalidateDrawable();
}
public void setDirection(Direction direction) {
this.direction = direction;
}
public void runShimmer() {
if (animationStarted) {
stopShimmer();
}
final ViewPropertyAnimator animator;
if (getMeasuredHeight() > getMeasuredWidth()) {
animator = getAnimatorVertical();
} else {
animator = getAnimatorHorizontal();
}
animator.start();
animationStarted = true;
}
private void stopShimmer() {
if (animator != null) {
animator.cancel();
}
animator = null;
animationStarted = false;
}
private ViewPropertyAnimator getAnimatorVertical() {
if (animator == null) {
setVisibility(VISIBLE);
setAlpha(1);
final float yStart;
final float yEnd;
switch (direction) {
case START_END:
yEnd = getMeasuredHeight();
yStart = -2 * getMeasuredHeight();
break;
case END_START:
yEnd = -2 * getMeasuredHeight();
yStart = getMeasuredHeight();
break;
default:
yEnd = 0;
yStart = 0;
}
setTranslationY(yStart);
final long duration = calculateSpeed(yEnd - yStart);
animator = animate()
.alpha(0)
.translationY(yEnd)
.setDuration(duration)
.withEndAction(() -> {
setAlpha(1);
setTranslationY(yStart);
setVisibility(View.GONE);
});
}
return animator;
}
private ViewPropertyAnimator getAnimatorHorizontal() {
if (animator == null) {
setVisibility(View.VISIBLE);
setAlpha(1);
final float xStart;
final float xEnd;
switch (direction) {
case START_END:
xEnd = getMeasuredWidth();
xStart = -2 * getMeasuredWidth();
break;
case END_START:
xEnd = -2 * getMeasuredWidth();
xStart = getMeasuredWidth();
break;
default:
xEnd = 0;
xStart = 0;
}
setTranslationX(xStart);
final long duration = calculateSpeed(xEnd - xStart);
animator = animate()
.alpha(0)
.translationX(xEnd)
.setDuration(duration)
.withEndAction(() -> {
setAlpha(1);
setTranslationX(xStart);
setVisibility(View.GONE);
});
}
return animator;
}
private long calculateSpeed(float dx) {
return (long) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
}
}
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
public class ShimmerLayout extends FrameLayout {
private Shimmer shimmer;
public ShimmerLayout(@NonNull Context context) {
this(context, null);
}
public ShimmerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ShimmerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
addShimmerView();
}
private void addShimmerView() {
shimmer = new Shimmer(getContext());
addView(shimmer);
}
public void run() {
if (shimmer != null) {
bringChildToFront(shimmer);
post(() -> shimmer.runShimmer());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize;
measureChildren(widthMeasureSpec, heightMeasureSpec);
View directChild = null;
if (getChildCount() > 0) {
directChild = getChildAt(0);
}
if (directChild != null) {
heightSize = directChild.getMeasuredHeight() - directChild.getPaddingBottom();
} else {
heightSize = MeasureSpec.getSize(heightMeasureSpec);
}
setMeasuredDimension(widthSize, heightSize);
}
public void setDirection(Shimmer.Direction direction) {
if (shimmer != null) {
shimmer.setDirection(direction);
}
}
public void setAngle(Shimmer.Angle angle) {
if (shimmer != null) {
shimmer.setAngle(angle);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment