Skip to content

Instantly share code, notes, and snippets.

@f3401pal
Last active March 30, 2017 22:17
Show Gist options
  • Save f3401pal/5aa18553a080e5592dfbcbb39dcfbaea to your computer and use it in GitHub Desktop.
Save f3401pal/5aa18553a080e5592dfbcbb39dcfbaea to your computer and use it in GitHub Desktop.
BounceCircleLoadingView
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BounceCircleLoadingView">
<attr name="pointColor" format="color" />
<attr name="animationDuration" format="integer" />
<attr name="pointSize" format="dimension" />
<attr name="interpolator" format="enum">
<enum name="fastOutSlowIn" value="0"/>
<enum name="anticipate_overshoot" value="1"/>
<enum name="overshoot" value="2"/>
<enum name="accelerateDecelerate" value="3"/>
</attr>
</declare-styleable>
</resources>
package com.f3401pal.shb.android.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.support.annotation.Nullable;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.f3401pal.shb.R;
public class BounceCircleLoadingView extends LinearLayout {
private static final String TAG = BounceCircleLoadingView.class.getSimpleName();
private static final int NUM_POINTS = 3;
private static final int DEFAULT_DURATION = 1000;
private static final int DEFAULT_POINT_COLOR = Color.WHITE;
private enum InterpolatorValues {
FAST_IN_FAST_OUT(new FastOutSlowInInterpolator()),
ANTICIPATE_OVERSHOOT(new AnticipateOvershootInterpolator()),
OVERSHOOT(new OvershootInterpolator()),
ACCELERATE_DECELERATE(new AccelerateDecelerateInterpolator());
private Interpolator mInterpolator;
InterpolatorValues(Interpolator interpolator) {
this.mInterpolator = interpolator;
}
public Interpolator getInterpolator() {
return mInterpolator;
}
}
private ImageView[] points;
private int mDuration;
private Interpolator mInterpolator;
private int pointSize;
private float shootingHeight;
@Nullable
private AnimatorSet mCircleAnimator;
public BounceCircleLoadingView(Context context) {
super(context);
init(context, null);
}
public BounceCircleLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public BounceCircleLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
setOrientation(HORIZONTAL);
points = new ImageView[NUM_POINTS];
for(int i = 0 ; i < NUM_POINTS ; i++) {
ImageView point = (ImageView) LayoutInflater.from(context).inflate(R.layout.view_bounce_circle_loading, this, false);
points[i] = point;
addView(point);
}
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BounceCircleLoadingView);
int pointColor = a.getColor(R.styleable.BounceCircleLoadingView_pointColor, -1);
pointSize = a.getDimensionPixelSize(R.styleable.BounceCircleLoadingView_pointSize, 0);
for(ImageView circle : points) {
setUpCircleColors(circle, pointColor == -1 ?
DEFAULT_POINT_COLOR : pointColor);
if (pointSize > 0) {
setUpCircleSize(pointSize, circle);
}
}
int duration = a.getInt(R.styleable.BounceCircleLoadingView_animationDuration, -1);
int interpolator = a.getInt(R.styleable.BounceCircleLoadingView_interpolator, -1);
mInterpolator = interpolator == -1 ? mInterpolator = InterpolatorValues.FAST_IN_FAST_OUT.getInterpolator() :
InterpolatorValues.values()[interpolator].getInterpolator();
mDuration = duration == -1 || duration < 0 ? DEFAULT_DURATION : duration;
a.recycle();
} else {
for(ImageView circle : points) {
setUpCircleColors(circle, DEFAULT_POINT_COLOR);
}
mDuration = DEFAULT_DURATION;
}
}
private void startAnimation(int step, final ImageView... points) {
if(points.length < 2) {
throw new RuntimeException("Cannot animate with less than 2 points");
}
if(!isShown()) {
return;
}
step = step >= points.length - 1 ? 0 : step;
ImageView leftPoint = points[step];
ImageView rightPoint = points[step + 1];
// left point shooting to the right from above
final ObjectAnimator leftPointAnimatorX = ObjectAnimator.ofFloat(leftPoint, View.X, leftPoint.getX(), rightPoint.getX());
setupAnimator(leftPointAnimatorX);
final ObjectAnimator leftPointAnimatorY = ObjectAnimator.ofFloat(leftPoint, View.TRANSLATION_Y, 0, -shootingHeight, 0);
setupAnimator(leftPointAnimatorY);
Log.d(TAG, "left index=" + step + ", X: " + leftPoint.getX() + " -> " + rightPoint.getX());
// right point move to left horizontally
final ObjectAnimator rightPointAnimatorX = ObjectAnimator.ofFloat(rightPoint, View.X, rightPoint.getX(), leftPoint.getX());
setupAnimator(rightPointAnimatorX);
Log.d(TAG, "right index=" + (step + 1) + ", X: " + rightPoint.getX() + " -> " + leftPoint.getX());
final int nextStep = step + 1;
mCircleAnimator = new AnimatorSet();
mCircleAnimator.playTogether(leftPointAnimatorX, leftPointAnimatorY, rightPointAnimatorX);
mCircleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
ImageView temp = points[nextStep - 1];
points[nextStep - 1] = points[nextStep];
points[nextStep] = temp;
startAnimation(nextStep, points);
}
});
mCircleAnimator.start();
}
private void setupAnimator(Animator animator) {
animator.setDuration(mDuration);
animator.setInterpolator(mInterpolator);
}
private void setUpCircleSize(int size, ImageView circle) {
LayoutParams params = (LayoutParams) circle.getLayoutParams();
params.width = size;
params.height = size;
circle.setLayoutParams(params);
}
private void setUpCircleColors(ImageView circle, int color) {
GradientDrawable gradientDrawable = (GradientDrawable) circle.getDrawable();
gradientDrawable.setColor(color);
}
private void stopAnimation() {
if (mCircleAnimator != null) {
mCircleAnimator.removeAllListeners();
mCircleAnimator.cancel();
mCircleAnimator = null;
}
}
//*********************************************************
// Lifecycle
//*********************************************************
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
stopAnimation();
shootingHeight = (getMeasuredHeight() - pointSize) >> 1;
startAnimation(0, points);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopAnimation();
}
}
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_view_padding"
android:src="@drawable/point_circle"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment