diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java index d85e0d9..8396586 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java @@ -1,5 +1,6 @@ package com.philliphsu.clock2.stopwatch; +import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; @@ -14,7 +15,7 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ProgressBar; +import android.widget.SeekBar; import com.philliphsu.clock2.R; import com.philliphsu.clock2.RecyclerViewFragment; @@ -53,7 +54,7 @@ public class StopwatchFragment extends RecyclerViewFragment< @Bind(R.id.chronometer) ChronometerWithMillis mChronometer; @Bind(R.id.new_lap) FloatingActionButton mNewLapButton; @Bind(R.id.stop) FloatingActionButton mStopButton; - @Bind(R.id.progress_bar) ProgressBar mProgressBar; + @Bind(R.id.seek_bar) SeekBar mSeekBar; /** * This is called only when a new instance of this Fragment is being created, @@ -138,7 +139,7 @@ public class StopwatchFragment extends RecyclerViewFragment< mPreviousLap = data.getItem(); Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId()); } - if (mChronometer.isRunning() && mCurrentLap != null && mPreviousLap != null) { + if (mCurrentLap != null && mPreviousLap != null) { // We really only want to start a new animator when the NEWLY RETRIEVED current // and previous laps are different (i.e. different laps, NOT merely different instances) // from the CURRENT current and previous laps, as referenced by mCurrentLap and mPreviousLap. @@ -150,8 +151,16 @@ public class StopwatchFragment extends RecyclerViewFragment< // have different values for, e.g., t1 and pauseTime. // // Therefore, we'll just always end the previous animator and start a new one. - // TODO: We may as well move the contents of this method here, since we're the only caller. - startNewProgressBarAnimator(); + if (mChronometer.isRunning()) { + startNewProgressBarAnimator(); + } else { + // I verified the bar was visible already without this, so we probably don't need this, + // but it's just a safety measure.. + mSeekBar.setVisibility(View.VISIBLE); + ProgressBarUtils.setProgress(mSeekBar, getCurrentLapProgressRatio()); + } + } else { + mSeekBar.setVisibility(View.INVISIBLE); } } @@ -244,8 +253,12 @@ public class StopwatchFragment extends RecyclerViewFragment< void stop() { mChronometer.stop(); mChronometer.setBase(SystemClock.elapsedRealtime()); + // ---------------------------------------------------------------------- + // TOneverDO: Precede these with mProgressAnimator.end(), otherwise our + // Animator.onAnimationEnd() callback won't hide SeekBar in time. mStartTime = 0; mPauseTime = 0; + // ---------------------------------------------------------------------- mCurrentLap = null; mPreviousLap = null; mUpdateHandler.asyncClear(); // Clear laps @@ -273,17 +286,67 @@ public class StopwatchFragment extends RecyclerViewFragment< } private void startNewProgressBarAnimator() { + final long timeRemaining = remainingTimeBetweenLaps(); + if (timeRemaining <= 0) { + mSeekBar.setVisibility(View.INVISIBLE); + return; + } if (mProgressAnimator != null) { mProgressAnimator.end(); } - long timeRemaining = mPreviousLap.elapsed() - mCurrentLap.elapsed(); - if (timeRemaining <= 0) - return; + // This can't go in the onAnimationStart() callback because the listener is added + // AFTER ProgressBarUtils.startNewAnimator() starts the animation. + mSeekBar.setVisibility(View.VISIBLE); + mProgressAnimator = ProgressBarUtils.startNewAnimator( + mSeekBar, getCurrentLapProgressRatio(), timeRemaining); + mProgressAnimator.addListener(mAnimatorListener); + } + + private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { + private boolean cancelled; + + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + // Pausing the stopwatch (and the current lap) uses Animator.cancel(), which will + // not only fire onAnimationCancel(Animator), but also onAnimationEnd(Animator). + // We should only let this call through when actually Animator.end() was called, + // and that happens when we stop() the stopwatch. + // If we didn't have this check, we'd be hiding the SeekBar every time we pause + // a lap. + if (!cancelled) { + mSeekBar.setVisibility(View.INVISIBLE); + } + cancelled = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + cancelled = true; + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }; + + private double getCurrentLapProgressRatio() { + if (mPreviousLap == null) + return 0; // The cast is necessary, or else we'd have integer division between two longs and we'd // always get zero since the numerator will always be less than the denominator. - double ratioTimeRemaining = timeRemaining / (double) mPreviousLap.elapsed(); - mProgressAnimator = ProgressBarUtils.startNewAnimator( - mProgressBar, ratioTimeRemaining, timeRemaining); + return remainingTimeBetweenLaps() / (double) mPreviousLap.elapsed(); + } + + private long remainingTimeBetweenLaps() { + if (mCurrentLap == null || mPreviousLap == null) + return 0; + return mPreviousLap.elapsed() - mCurrentLap.elapsed(); } private void savePrefs() { diff --git a/app/src/main/java/com/philliphsu/clock2/util/ProgressBarUtils.java b/app/src/main/java/com/philliphsu/clock2/util/ProgressBarUtils.java index c1d01d4..94aa590 100644 --- a/app/src/main/java/com/philliphsu/clock2/util/ProgressBarUtils.java +++ b/app/src/main/java/com/philliphsu/clock2/util/ProgressBarUtils.java @@ -7,7 +7,7 @@ import android.widget.ProgressBar; * Created by Phillip Hsu on 8/10/2016. */ public class ProgressBarUtils { - private static final int MAX_PROGRESS = 100000; + private static final int MAX_PROGRESS = 1000000; /** * Constructs and starts a new countdown ObjectAnimator with the given properties. @@ -15,9 +15,9 @@ public class ProgressBarUtils { * by {@link #MAX_PROGRESS}. A ratio of 1 means the bar is full. * @return the created ObjectAnimator for holding a reference to */ - public static ObjectAnimator startNewAnimator(final ProgressBar bar, final double ratio, final long duration) { + public static ObjectAnimator startNewAnimator(ProgressBar bar, double ratio, long duration) { bar.setMax(MAX_PROGRESS); - final int progress = (int) (MAX_PROGRESS * ratio); + final int progress = scaleRatio(ratio); ObjectAnimator animator = ObjectAnimator.ofInt( // The object that has the property we wish to animate bar, @@ -45,4 +45,17 @@ public class ProgressBarUtils { animator.start(); return animator; } + + /** + * Wrapper around {@link ProgressBar#setProgress(int) setProgress(int)} that keeps {@code bar}'s + * max in sync with the animation's progress scale factor. + */ + public static void setProgress(ProgressBar bar, double ratio) { + bar.setMax(MAX_PROGRESS); + bar.setProgress(scaleRatio(ratio)); + } + + private static int scaleRatio(double ratio) { + return (int) (MAX_PROGRESS * ratio); + } } diff --git a/app/src/main/res/layout/fragment_stopwatch.xml b/app/src/main/res/layout/fragment_stopwatch.xml index 2a6b729..7b3d7ec 100644 --- a/app/src/main/res/layout/fragment_stopwatch.xml +++ b/app/src/main/res/layout/fragment_stopwatch.xml @@ -18,20 +18,22 @@ android:background="@color/colorPrimary" android:gravity="center_horizontal" android:textSize="@dimen/text_size_display_3" - style="@style/TextAppearance.AppCompat.Inverse"/> + style="@style/TextAppearance.AppCompat.Inverse" + android:layout_marginBottom="8dp"/> - + android:paddingStart="0dp" + android:paddingEnd="0dp"/>