io.github.sin3hz.wifispinnerview.WifiSpinnerDrawable.java Source code

Java tutorial

Introduction

Here is the source code for io.github.sin3hz.wifispinnerview.WifiSpinnerDrawable.java

Source

/*
 * Copyright (C) 2016 sin3hz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.github.sin3hz.wifispinnerview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

public class WifiSpinnerDrawable extends Drawable implements Animatable {

    private static final Interpolator ANGLE_ANIMATOR_INTERPOLATOR = new LinearInterpolator();
    private static final Interpolator SWEEP_ANIMATOR_INTERPOLATOR = new SweepInterpolator();

    private static final int SWEEP_ANIMATOR_DELAY = 200;
    private static final int SWEEP_ANIMATOR_DURATION = 850;
    private static final int ANGLE_ANIMATOR_DURATION = 5000;

    public static final int MAX_SWEEP_ANGLE = 180;
    public static final int SPINNER_ANGLE = 90;

    public static final int DEFAULT_SPINNER_COLOR = Color.parseColor("#4CAF50");
    public static final int DEFAULT_SPINNER_COUNT = 3;

    private Rect mBounds;
    private Paint mSpinnerPaint;

    private int mSpinnerCount;
    private Spinner[] mSpinners;

    private float mGlobalAngle;

    private int mSweepAnimatorDuration;
    private ValueAnimator mAngleAnimator;
    private AnimatorSet mSweepAnimator;
    private boolean mIsRunning;

    public WifiSpinnerDrawable(int color, int spinnerCount) {
        if (spinnerCount <= 0) {
            throw new IllegalArgumentException("spinner count must be positive");
        }
        mSpinnerPaint = new Paint();
        mSpinnerPaint.setAntiAlias(true);
        mSpinnerPaint.setStyle(Paint.Style.STROKE);

        super.setVisible(false, false);

        setColor(color);
        setupSpinnerCount(spinnerCount);
        setupAnimators();
    }

    public WifiSpinnerDrawable() {
        this(DEFAULT_SPINNER_COLOR, DEFAULT_SPINNER_COUNT);
    }

    static class SweepInterpolator implements Interpolator {

        TimeInterpolator mPathInterpolator = PathInterpolatorCompat.create(0.4f, 0.0f, 0.2f, 1f);

        @Override
        public float getInterpolation(float input) {
            if (input < 0.5)
                return 0;
            return mPathInterpolator.getInterpolation((input - 0.5f) / 0.5f);
        }
    }

    private void setupAnimators() {
        AnimatorSet set = new AnimatorSet();
        for (int i = 0; i < mSpinnerCount; i++) {
            final int index = i;
            final ValueAnimator sweepAnimator = ValueAnimator.ofFloat(0, MAX_SWEEP_ANGLE);
            sweepAnimator.setInterpolator(SWEEP_ANIMATOR_INTERPOLATOR);
            sweepAnimator.setDuration(mSweepAnimatorDuration);
            sweepAnimator.setRepeatMode(ValueAnimator.RESTART);
            sweepAnimator.setRepeatCount(ValueAnimator.INFINITE);
            sweepAnimator.setStartDelay(index * SWEEP_ANIMATOR_DELAY);
            sweepAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mSpinners[index].sweepAngle = (float) animation.getAnimatedValue();
                    mSpinners[index].updatePath();
                    invalidateSelf();
                }
            });
            sweepAnimator.addListener(new AnimatorListenerAdapter() {

                @Override
                public void onAnimationRepeat(Animator animation) {
                    mSpinners[index].sweepAngleOffset = (mSpinners[index].sweepAngleOffset + MAX_SWEEP_ANGLE) % 360;
                    mSpinners[index].updatePath();
                }
            });
            set.playTogether(sweepAnimator);
        }
        mSweepAnimator = set;

        mAngleAnimator = ValueAnimator.ofFloat(0, 360);
        mAngleAnimator.setInterpolator(ANGLE_ANIMATOR_INTERPOLATOR);
        mAngleAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAngleAnimator.setRepeatMode(ValueAnimator.RESTART);
        mAngleAnimator.setDuration(ANGLE_ANIMATOR_DURATION);
        mAngleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mGlobalAngle = (float) animation.getAnimatedValue();
                updatePath();
                invalidateSelf();
            }
        });
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        if (restart) {
            reset(true);
        }
        if (isVisible() != visible) {
            if (visible) {
                if (mIsRunning) {
                    startRunning();
                }
            } else {
                stopRunning();
            }
        }
        return super.setVisible(visible, restart);
    }

    private void reset(boolean resetGlobalAngle) {
        if (resetGlobalAngle) {
            mGlobalAngle = 0;
        }
        for (Spinner spinner : mSpinners) {
            spinner.sweepAngle = 0;
            spinner.sweepAngleOffset = 0;
        }
    }

    @Override
    public void start() {
        if (!isRunning()) {
            mIsRunning = true;
            if (isVisible()) {
                startRunning();
            }
        }
    }

    @Override
    public void stop() {
        if (isRunning()) {
            mIsRunning = false;
            stopRunning();
        }
    }

    @Override
    public boolean isRunning() {
        return mIsRunning;
    }

    private boolean isAnimatorStarted() {
        return mSweepAnimator.isStarted() || mAngleAnimator.isStarted();
    }

    private void startRunning() {
        if (isAnimatorStarted()) {
            return;
        }
        mSweepAnimator.start();
        mAngleAnimator.start();
    }

    private void stopRunning() {
        if (!isAnimatorStarted()) {
            return;
        }
        mSweepAnimator.end();
        mAngleAnimator.end();
        reset(true);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.save();
        canvas.rotate(mGlobalAngle, mBounds.centerX(), mBounds.centerY());
        for (int i = 0; i < mSpinnerCount; i++) {
            canvas.drawPath(mSpinners[i].path, mSpinners[i].paint);
        }
        canvas.restore();
    }

    @Override
    public void setAlpha(int alpha) {
        if (alpha != mSpinnerPaint.getAlpha()) {
            mSpinnerPaint.setAlpha(alpha);
            invalidateSelf();
        }
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mSpinnerPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        if (mBounds == null) {
            mBounds = new Rect();
        }
        int size = Math.min(bounds.height(), bounds.width());
        mBounds.left = bounds.centerX() - size / 2;
        mBounds.right = mBounds.left + size;
        mBounds.top = bounds.centerY() - size / 2;
        mBounds.bottom = mBounds.top + size;
        configureBounds();
    }

    private void configureBounds() {
        if (mBounds == null)
            return;
        float spinnerStrokeWidth = mBounds.width() / 2f / (mSpinnerCount * 2 - 1);
        mSpinnerPaint.setStrokeWidth(spinnerStrokeWidth);
        float halfSpinnerWidth = spinnerStrokeWidth / 2f;
        for (int i = mSpinnerCount - 1; i >= 0; i--) {
            float outsizeSpinnerWidth = spinnerStrokeWidth * 2 * (mSpinnerCount - 1 - i);
            mSpinners[i].bounds.set(mBounds.left + halfSpinnerWidth + outsizeSpinnerWidth,
                    mBounds.top + halfSpinnerWidth + outsizeSpinnerWidth,
                    mBounds.right - halfSpinnerWidth - outsizeSpinnerWidth,
                    mBounds.bottom - halfSpinnerWidth - outsizeSpinnerWidth);
        }
        updatePath();
    }

    private void updatePath() {
        for (Spinner spinner : mSpinners) {
            spinner.updatePath();
        }
    }

    public void setColor(int color) {
        if (color != mSpinnerPaint.getColor()) {
            mSpinnerPaint.setColor(color);
            invalidateSelf();
        }
    }

    public int getColor() {
        return mSpinnerPaint.getColor();
    }

    private int getSpinnerCount() {
        return mSpinnerCount;
    }

    private void setSpinnerCount(int spinnerCount) {
        if (isRunning()) {
            throw new IllegalStateException("spinner count must set before running");
        }
        if (mSpinnerCount != spinnerCount) {
            setupSpinnerCount(spinnerCount);
            setupAnimators();
        }
    }

    private void setupSpinnerCount(int spinnerCount) {
        mSpinnerCount = spinnerCount;
        mSweepAnimatorDuration = SWEEP_ANIMATOR_DURATION + SWEEP_ANIMATOR_DELAY * (mSpinnerCount - 1);
        mSpinners = new Spinner[spinnerCount];
        for (int i = mSpinners.length - 1; i >= 0; i--) {
            mSpinners[i] = new Spinner();
            mSpinners[i].paint = mSpinnerPaint;
        }
        configureBounds();
    }

    static final class Spinner {
        RectF bounds;
        float sweepAngle;
        float sweepAngleOffset;
        Path path;
        Paint paint;

        public Spinner() {
            bounds = new RectF();
            path = new Path();
        }

        public void updatePath() {
            path.rewind();
            path.addArc(bounds, sweepAngle + sweepAngleOffset, SPINNER_ANGLE);
        }
    }
}