package com.tbuonomo.materialsquareloading; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.support.v7.widget.CardView; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.RelativeLayout; /** * Created by tommy on 02/07/16. */ public class MaterialSquareLoading extends RelativeLayout { private static final int DEFAULT_OUTER_COLOR = Color.parseColor("#1A237E"); private static final int DEFAULT_INNER_COLOR = Color.parseColor("#01579B"); public static final int DEFAULT_DURATION_ROTATION_INNER = 4862; public static final int DEFAULT_DURATION_ROTATION_OUTER = 6028; private CardView innerSquare, outerSquare; private int outerSize; private int innerSize; private boolean sizeChanged; private boolean requestStartAnimation; private int outerRotationDuration; private int innerRotationDuration; public MaterialSquareLoading(Context context) { super(context); init(context, null); } public MaterialSquareLoading(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public MaterialSquareLoading(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { inflateSelf(); setUpAttrs(attrs); if (!isInEditMode()) { setVisibility(INVISIBLE); } } private void setUpAttrs(AttributeSet attrs) { int innerColor = DEFAULT_INNER_COLOR; int outerColor = DEFAULT_OUTER_COLOR; float innerRadius = innerSquare.getRadius(); float outerRadius = outerSquare.getRadius(); outerRotationDuration = DEFAULT_DURATION_ROTATION_OUTER; innerRotationDuration = DEFAULT_DURATION_ROTATION_INNER; if (attrs != null) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MaterialSquareLoading); innerColor = a.getColor(R.styleable.MaterialSquareLoading_innerColor, DEFAULT_INNER_COLOR); outerColor = a.getColor(R.styleable.MaterialSquareLoading_outerColor, DEFAULT_OUTER_COLOR); innerRadius = a.getDimension(R.styleable.MaterialSquareLoading_innerRadius, innerSquare.getRadius()); outerRadius = a.getDimension(R.styleable.MaterialSquareLoading_outerRadius, outerSquare.getRadius()); innerRotationDuration = a.getInt(R.styleable.MaterialSquareLoading_rotationInnerDuration, DEFAULT_DURATION_ROTATION_INNER); outerRotationDuration = a.getInt(R.styleable.MaterialSquareLoading_rotationOuterDuration, DEFAULT_DURATION_ROTATION_OUTER); a.recycle(); } innerSquare.setCardBackgroundColor(innerColor); outerSquare.setCardBackgroundColor(outerColor); innerSquare.setRadius(innerRadius); outerSquare.setRadius(outerRadius); } private void inflateSelf() { inflate(getContext(), R.layout.material_square_loading_layout, this); outerSquare = (CardView) findViewById(R.id.material_square1); innerSquare = (CardView) findViewById(R.id.material_square2); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); setUpSquareSize(Math.min(h, w)); sizeChanged = true; if (requestStartAnimation) { show(); } } private void setUpSquareSize(int size) { double hypotenuse = Math.sqrt(2 * size * size); int realSize = size - (int) (hypotenuse - size); LayoutParams outerParams = (LayoutParams) outerSquare.getLayoutParams(); outerSize = outerParams.width = realSize; outerParams.height = realSize; LayoutParams innerParams = (LayoutParams) innerSquare.getLayoutParams(); innerSize = innerParams.width = realSize / 2; innerParams.height = realSize / 2; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); } public void show() { if (!sizeChanged) { requestStartAnimation = true; } else { startGlobalAnimation(); } } public void hide() { endGlobalAnimation(); } private void endGlobalAnimation() { cancelViewTagAnimator(this); // SCALE ENTER ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1, 0); scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float scale = (Float) animation.getAnimatedValue(); setScaleX(scale); setScaleY(scale); } }); scaleAnimator.setDuration(400); scaleAnimator.setInterpolator(new AccelerateInterpolator()); scaleAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); cancelViewTagAnimator(outerSquare); cancelViewTagAnimator(innerSquare); setVisibility(INVISIBLE); } }); scaleAnimator.start(); setTag(scaleAnimator); } private void startGlobalAnimation() { // OUTER SQUARE final ValueAnimator outerAnimator = createCosinusValueAnimator(outerSize, outerSize * 0.8f, new CosinusAnimatorUpdateListener() { @Override public void onCosinusAnimatorUpdate(float value) { LayoutParams params = (LayoutParams) outerSquare.getLayoutParams(); params.width = (int) value; params.height = (int) value; outerSquare.requestLayout(); } }); outerAnimator.setDuration(2000); outerAnimator.setInterpolator(new LinearInterpolator()); outerAnimator.setRepeatCount(ValueAnimator.INFINITE); ValueAnimator outerRotateAnimator = ObjectAnimator.ofFloat(outerSquare, View.ROTATION, 0, 360); outerRotateAnimator.setInterpolator(new LinearInterpolator()); outerRotateAnimator.setDuration(outerRotationDuration); outerRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); AnimatorSet outerAnimatorSet = new AnimatorSet(); outerAnimatorSet.playTogether(outerRotateAnimator, outerAnimator); outerSquare.setTag(outerAnimatorSet); outerAnimatorSet.start(); // INNER SQUARE final ValueAnimator innerAnimator = createCosinusValueAnimator(innerSize * 0.8f, innerSize, new CosinusAnimatorUpdateListener() { @Override public void onCosinusAnimatorUpdate(float value) { LayoutParams params = (LayoutParams) innerSquare.getLayoutParams(); params.width = (int) value; params.height = (int) value; innerSquare.requestLayout(); } }); innerAnimator.setDuration(2000); innerAnimator.setInterpolator(new LinearInterpolator()); innerAnimator.setRepeatCount(ValueAnimator.INFINITE); ValueAnimator innerRotateAnimator = ObjectAnimator.ofFloat(innerSquare, View.ROTATION, 0, -360); innerRotateAnimator.setInterpolator(new LinearInterpolator()); innerRotateAnimator.setDuration(innerRotationDuration); innerRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); AnimatorSet innerAnimatorSet = new AnimatorSet(); innerAnimatorSet.playTogether(innerRotateAnimator, innerAnimator); innerSquare.setTag(innerAnimatorSet); innerAnimatorSet.start(); // SCALE ENTER ValueAnimator scaleAnimator = ValueAnimator.ofFloat(0, 1); scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Float offset = (Float) animation.getAnimatedValue(); setScaleX(offset); setScaleY(offset); setRotation(-(1 - offset) * 50); } }); scaleAnimator.setDuration(400); scaleAnimator.setInterpolator(new DecelerateInterpolator()); scaleAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); setVisibility(VISIBLE); } }); scaleAnimator.start(); setTag(scaleAnimator); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); cancelViewTagAnimator(outerSquare); cancelViewTagAnimator(innerSquare); cancelViewTagAnimator(this); } public static ValueAnimator createCosinusValueAnimator(final float start, final float end, final CosinusAnimatorUpdateListener listener) { ValueAnimator valueAnimator = ValueAnimator.ofFloat((float) (-Math.PI), (float) (Math.PI)); valueAnimator.setInterpolator(new LinearInterpolator()); if (listener != null) { valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { double offset = (Math.cos((float) animation.getAnimatedValue()) + 1) / 2; float value = (float) (start + (end - start) * offset); listener.onCosinusAnimatorUpdate(value); } }); } return valueAnimator; } public static void cancelViewTagAnimator(View view) { if (view != null && view.getTag() != null && view.getTag() instanceof Animator) { ((Animator) view.getTag()).cancel(); } } public interface CosinusAnimatorUpdateListener { void onCosinusAnimatorUpdate(float value); } public interface AnimationListener { void onAnimationFinished(); } }