Changed ProgressBar to SeekBar

This commit is contained in:
Phillip Hsu 2016-08-28 17:18:52 -07:00
parent 85d78e566b
commit b03c6123e9
3 changed files with 96 additions and 18 deletions

View File

@ -1,5 +1,6 @@
package com.philliphsu.clock2.stopwatch; package com.philliphsu.clock2.stopwatch;
import android.animation.Animator;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -14,7 +15,7 @@ import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar; import android.widget.SeekBar;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.RecyclerViewFragment; import com.philliphsu.clock2.RecyclerViewFragment;
@ -53,7 +54,7 @@ public class StopwatchFragment extends RecyclerViewFragment<
@Bind(R.id.chronometer) ChronometerWithMillis mChronometer; @Bind(R.id.chronometer) ChronometerWithMillis mChronometer;
@Bind(R.id.new_lap) FloatingActionButton mNewLapButton; @Bind(R.id.new_lap) FloatingActionButton mNewLapButton;
@Bind(R.id.stop) FloatingActionButton mStopButton; @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, * 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(); mPreviousLap = data.getItem();
Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId()); 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 // 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) // 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. // 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. // have different values for, e.g., t1 and pauseTime.
// //
// Therefore, we'll just always end the previous animator and start a new one. // 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. if (mChronometer.isRunning()) {
startNewProgressBarAnimator(); 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() { void stop() {
mChronometer.stop(); mChronometer.stop();
mChronometer.setBase(SystemClock.elapsedRealtime()); mChronometer.setBase(SystemClock.elapsedRealtime());
// ----------------------------------------------------------------------
// TOneverDO: Precede these with mProgressAnimator.end(), otherwise our
// Animator.onAnimationEnd() callback won't hide SeekBar in time.
mStartTime = 0; mStartTime = 0;
mPauseTime = 0; mPauseTime = 0;
// ----------------------------------------------------------------------
mCurrentLap = null; mCurrentLap = null;
mPreviousLap = null; mPreviousLap = null;
mUpdateHandler.asyncClear(); // Clear laps mUpdateHandler.asyncClear(); // Clear laps
@ -273,17 +286,67 @@ public class StopwatchFragment extends RecyclerViewFragment<
} }
private void startNewProgressBarAnimator() { private void startNewProgressBarAnimator() {
final long timeRemaining = remainingTimeBetweenLaps();
if (timeRemaining <= 0) {
mSeekBar.setVisibility(View.INVISIBLE);
return;
}
if (mProgressAnimator != null) { if (mProgressAnimator != null) {
mProgressAnimator.end(); mProgressAnimator.end();
} }
long timeRemaining = mPreviousLap.elapsed() - mCurrentLap.elapsed(); // This can't go in the onAnimationStart() callback because the listener is added
if (timeRemaining <= 0) // AFTER ProgressBarUtils.startNewAnimator() starts the animation.
return; 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 // 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. // always get zero since the numerator will always be less than the denominator.
double ratioTimeRemaining = timeRemaining / (double) mPreviousLap.elapsed(); return remainingTimeBetweenLaps() / (double) mPreviousLap.elapsed();
mProgressAnimator = ProgressBarUtils.startNewAnimator( }
mProgressBar, ratioTimeRemaining, timeRemaining);
private long remainingTimeBetweenLaps() {
if (mCurrentLap == null || mPreviousLap == null)
return 0;
return mPreviousLap.elapsed() - mCurrentLap.elapsed();
} }
private void savePrefs() { private void savePrefs() {

View File

@ -7,7 +7,7 @@ import android.widget.ProgressBar;
* Created by Phillip Hsu on 8/10/2016. * Created by Phillip Hsu on 8/10/2016.
*/ */
public class ProgressBarUtils { 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. * 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. * by {@link #MAX_PROGRESS}. A ratio of 1 means the bar is full.
* @return the created ObjectAnimator for holding a reference to * @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); bar.setMax(MAX_PROGRESS);
final int progress = (int) (MAX_PROGRESS * ratio); final int progress = scaleRatio(ratio);
ObjectAnimator animator = ObjectAnimator.ofInt( ObjectAnimator animator = ObjectAnimator.ofInt(
// The object that has the property we wish to animate // The object that has the property we wish to animate
bar, bar,
@ -45,4 +45,17 @@ public class ProgressBarUtils {
animator.start(); animator.start();
return animator; 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);
}
} }

View File

@ -18,20 +18,22 @@
android:background="@color/colorPrimary" android:background="@color/colorPrimary"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="@dimen/text_size_display_3" android:textSize="@dimen/text_size_display_3"
style="@style/TextAppearance.AppCompat.Inverse"/> style="@style/TextAppearance.AppCompat.Inverse"
android:layout_marginBottom="8dp"/>
<!-- RecyclerView --> <!-- RecyclerView -->
<include layout="@layout/fragment_recycler_view"/> <include layout="@layout/fragment_recycler_view"/>
</LinearLayout> </LinearLayout>
<ProgressBar <SeekBar
android:id="@+id/progress_bar" android:id="@+id/seek_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_anchor="@id/chronometer" app:layout_anchor="@id/chronometer"
app:layout_anchorGravity="bottom" app:layout_anchorGravity="bottom"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"/> android:paddingStart="0dp"
android:paddingEnd="0dp"/>
<!-- TODO: dimen resource for height --> <!-- TODO: dimen resource for height -->
<android.support.v7.widget.GridLayout <android.support.v7.widget.GridLayout