quickbeer.android.views.ProgressIndicatorBar.java Source code

Java tutorial

Introduction

Here is the source code for quickbeer.android.views.ProgressIndicatorBar.java

Source

/**
 * This file is part of QuickBeer.
 * Copyright (C) 2016 Antti Poikela <antti.poikela@iki.fi>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package quickbeer.android.views;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;

import quickbeer.android.R;
import quickbeer.android.providers.ProgressStatusProvider.ProgressStatus;
import quickbeer.android.providers.ProgressStatusProvider.Status;
import timber.log.Timber;

import static io.reark.reark.utils.Preconditions.checkNotNull;
import static io.reark.reark.utils.Preconditions.get;
import static quickbeer.android.providers.ProgressStatusProvider.Status.INDEFINITE;
import static quickbeer.android.providers.ProgressStatusProvider.Status.LOADING;

public class ProgressIndicatorBar extends FrameLayout {

    private static final int ANIMATION_SCROLL_DURATION = 1200;
    private static final int ANIMATION_PROGRESS_DURATION = 400;
    private static final int ANIMATION_END_SCALE_DURATION = 400;
    private static final int ANIMATION_END_PAUSE_DURATION = 300;
    private static final int ANIMATION_END_FADE_DURATION = 300;

    private final int progressBarWidth = getResources().getDimensionPixelSize(R.dimen.progress_indicator_width);

    private float barScale = 1.0f;

    @Nullable
    private View progressBar;

    @NonNull
    private ProgressStatus currentProgress = new ProgressStatus(Status.IDLE, 0.0f);

    @NonNull
    private ProgressStatus nextProgress = new ProgressStatus(Status.IDLE, 0.0f);

    public ProgressIndicatorBar(Context context) {
        super(context);
        addPreDrawListener();
    }

    public ProgressIndicatorBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        addPreDrawListener();
    }

    public void setProgress(@NonNull ProgressStatus progress) {
        Timber.v("New progress: " + get(progress));

        nextProgress = progress;

        // Only indefinite progress animation can be interrupted
        if (!isUninterruptibleAnimation()) {
            applyNextStatus();
        }
    }

    private void addPreDrawListener() {
        getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                getViewTreeObserver().removeOnPreDrawListener(this);
                initLayout();
                return true;
            }
        });
    }

    private void initLayout() {
        LayoutParams params = new LayoutParams(progressBarWidth, ViewGroup.LayoutParams.MATCH_PARENT);

        progressBar = new View(getContext());
        progressBar.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.orange));
        progressBar.setLayoutParams(params);
        progressBar.setVisibility(INVISIBLE);

        addView(progressBar);
        applyNextStatus();
    }

    private void applyNextStatus() {
        // Initialisation may not be finished yet
        if (progressBar == null) {
            return;
        }

        // Check if there's new status to apply
        if (statusesEqual(nextProgress, currentProgress)) {
            return;
        }

        currentProgress = nextProgress;

        switch (currentProgress.getStatus()) {
        case INDEFINITE:
            animateScroller();
            break;
        case LOADING:
            animateToProgress(currentProgress.getProgress());
            break;
        case IDLE:
            animateToEnd();
            break;
        }
    }

    private boolean isUninterruptibleAnimation() {
        if (progressBar == null || progressBar.getAnimation() == null) {
            return false;
        }

        if (progressBar.getAnimation().hasEnded()) {
            return false;
        }

        return true;
    }

    private void animateScroller() {
        Timber.v("animateScroller()");
        checkNotNull(progressBar);

        int animEnd = getWidth() - progressBarWidth;

        TranslateAnimation animation = new TranslateAnimation(0, animEnd, 0, 0);
        animation.setDuration(ANIMATION_SCROLL_DURATION);
        animation.setFillAfter(true);
        animation.setRepeatMode(Animation.REVERSE);
        animation.setRepeatCount(Animation.INFINITE);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                if (nextProgress.getStatus() != INDEFINITE) {
                    applyNextStatus();
                }
            }
        });

        progressBar.setVisibility(VISIBLE);
        progressBar.clearAnimation();
        progressBar.startAnimation(animation);
    }

    private void animateToProgress(float progress) {
        Timber.v("animateToProgress(%s)", progress);
        checkNotNull(progressBar);

        float newScale = (getWidth() * progress / progressBarWidth) + 1;

        Timber.v("animate scale %s -> %s", barScale, newScale);

        ScaleAnimation animation = new ScaleAnimation(barScale, newScale, progressBar.getHeight(),
                progressBar.getHeight());
        animation.setDuration(ANIMATION_PROGRESS_DURATION);
        animation.setFillAfter(true);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                barScale = newScale;
                applyNextStatus();
            }
        });

        progressBar.setVisibility(VISIBLE);
        progressBar.clearAnimation();
        progressBar.startAnimation(animation);
    }

    private void animateToEnd() {
        Timber.v("animateToEnd()");
        checkNotNull(progressBar);

        if (progressBar.getAnimation() == null) {
            // Non-active progress bar doesn't need end animation
            return;
        }

        ScaleAnimation animation = new ScaleAnimation(barScale, (getWidth() / (float) progressBarWidth) + 1,
                progressBar.getHeight(), progressBar.getHeight());
        animation.setDuration(ANIMATION_END_SCALE_DURATION);
        animation.setFillAfter(true);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                barScale = 1.0f;
                animateToHidden();
            }
        });

        progressBar.setVisibility(VISIBLE);
        progressBar.clearAnimation();
        progressBar.startAnimation(animation);
    }

    private void animateToHidden() {
        Timber.v("animateToHidden()");
        checkNotNull(progressBar);

        AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f);
        animation.setStartOffset(ANIMATION_END_PAUSE_DURATION);
        animation.setDuration(ANIMATION_END_FADE_DURATION);
        animation.setFillAfter(true);

        progressBar.clearAnimation();
        progressBar.startAnimation(animation);
    }

    private static boolean statusesEqual(@NonNull ProgressStatus first, @NonNull ProgressStatus second) {
        if (second.equals(first)) {
            return true;
        }

        // Consider progress only if loading, otherwise matching status is enough
        return first.getStatus() == second.getStatus() && (first.getStatus() != LOADING || first.equals(second));
    }
}