From 74ec0fd88324ecd0dcc222b1af8260578fb31a0b Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Mon, 8 Aug 2016 02:57:21 -0700 Subject: [PATCH] Animated progress bar --- .../java/com/philliphsu/clock2/Timer.java | 38 +++++++++++++-- .../philliphsu/clock2/model/TimerCursor.java | 1 + .../philliphsu/clock2/model/TimersTable.java | 7 +-- .../clock2/model/TimersTableManager.java | 1 + .../clock2/timers/TimerViewHolder.java | 47 ++++++++++++++++++- app/src/main/res/layout/item_timer.xml | 1 - 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/philliphsu/clock2/Timer.java b/app/src/main/java/com/philliphsu/clock2/Timer.java index 7c671a2..7bd55da 100644 --- a/app/src/main/java/com/philliphsu/clock2/Timer.java +++ b/app/src/main/java/com/philliphsu/clock2/Timer.java @@ -18,6 +18,16 @@ public abstract class Timer extends ObjectWithId implements Parcelable { private long endTime; private long pauseTime; + private long duration; + + // Using this crashes the app when we create a Timer and start it... + // timeRemaining() is returning a negative value... but it doesn't even + // consider duration()....? + // My guess is the hour, minute, and second getters are returning 0 + // at this point...? +// private final long normalDuration = TimeUnit.HOURS.toMillis(hour()) +// + TimeUnit.MINUTES.toMillis(minute()) +// + TimeUnit.SECONDS.toMillis(second()); public abstract int hour(); public abstract int minute(); @@ -61,9 +71,12 @@ public abstract class Timer extends ObjectWithId implements Parcelable { } public long duration() { - return TimeUnit.HOURS.toMillis(hour()) - + TimeUnit.MINUTES.toMillis(minute()) - + TimeUnit.SECONDS.toMillis(second()); + if (duration == 0) { + duration = TimeUnit.HOURS.toMillis(hour()) + + TimeUnit.MINUTES.toMillis(minute()) + + TimeUnit.SECONDS.toMillis(second()); + } + return duration; } public void start() { @@ -89,6 +102,7 @@ public abstract class Timer extends ObjectWithId implements Parcelable { public void stop() { endTime = 0; pauseTime = 0; + duration = 0; } public void addOneMinute() { @@ -97,8 +111,17 @@ public abstract class Timer extends ObjectWithId implements Parcelable { // throw new IllegalStateException("Cannot extend a timer that is not running"); if (expired()) { endTime = SystemClock.elapsedRealtime() + MINUTE; + // If the timer's normal duration is >= MINUTE, then an extra run time of one minute + // will still be within the normal duration. Thus, the progress calculation does not + // need to change. For example, if the timer's normal duration is 2 minutes, an extra + // 1 minute run time is fully encapsulated within the 2 minute upper bound. + if (duration < MINUTE) { + // This scales the progress bar to a full minute. + duration = MINUTE; + } } else { endTime += MINUTE; + duration += MINUTE; } } @@ -131,6 +154,13 @@ public abstract class Timer extends ObjectWithId implements Parcelable { return pauseTime; } + /** + * TO ONLY BE CALLED BY TIMERDATABASEHELPER. + */ + public void setDuration(long duration) { + this.duration = duration; + } + @Override public int describeContents() { return 0; @@ -146,6 +176,7 @@ public abstract class Timer extends ObjectWithId implements Parcelable { dest.writeLong(getId()); dest.writeLong(endTime); dest.writeLong(pauseTime); + dest.writeLong(duration); } public static final Creator CREATOR = new Creator() { @@ -166,6 +197,7 @@ public abstract class Timer extends ObjectWithId implements Parcelable { t.setId(source.readLong()); t.endTime = source.readLong(); t.pauseTime = source.readLong(); + t.duration = source.readLong(); return t; } } diff --git a/app/src/main/java/com/philliphsu/clock2/model/TimerCursor.java b/app/src/main/java/com/philliphsu/clock2/model/TimerCursor.java index 70d3b06..0a579a6 100644 --- a/app/src/main/java/com/philliphsu/clock2/model/TimerCursor.java +++ b/app/src/main/java/com/philliphsu/clock2/model/TimerCursor.java @@ -26,6 +26,7 @@ public class TimerCursor extends BaseItemCursor { t.setId(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_ID))); t.setEndTime(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_END_TIME))); t.setPauseTime(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_PAUSE_TIME))); + t.setDuration(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_DURATION))); return t; } } diff --git a/app/src/main/java/com/philliphsu/clock2/model/TimersTable.java b/app/src/main/java/com/philliphsu/clock2/model/TimersTable.java index 184ff7b..9fe79d8 100644 --- a/app/src/main/java/com/philliphsu/clock2/model/TimersTable.java +++ b/app/src/main/java/com/philliphsu/clock2/model/TimersTable.java @@ -27,9 +27,9 @@ public final class TimersTable { public static final String COLUMN_END_TIME = "end_time"; public static final String COLUMN_PAUSE_TIME = "pause_time"; + public static final String COLUMN_DURATION = "duration"; - public static final String SORT_ORDER = - COLUMN_HOUR + " ASC, " + public static final String SORT_ORDER = COLUMN_HOUR + " ASC, " + COLUMN_MINUTE + " ASC, " + COLUMN_SECOND + " ASC, " // All else equal, newer timers first @@ -44,7 +44,8 @@ public final class TimersTable { + COLUMN_LABEL + " TEXT NOT NULL, " // + COLUMN_GROUP + " TEXT NOT NULL, " + COLUMN_END_TIME + " INTEGER NOT NULL, " - + COLUMN_PAUSE_TIME + " INTEGER NOT NULL);"); + + COLUMN_PAUSE_TIME + " INTEGER NOT NULL, " + + COLUMN_DURATION + " INTEGER NOT NULL);"); } public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { diff --git a/app/src/main/java/com/philliphsu/clock2/model/TimersTableManager.java b/app/src/main/java/com/philliphsu/clock2/model/TimersTableManager.java index 88234d7..1ab756e 100644 --- a/app/src/main/java/com/philliphsu/clock2/model/TimersTableManager.java +++ b/app/src/main/java/com/philliphsu/clock2/model/TimersTableManager.java @@ -53,6 +53,7 @@ public class TimersTableManager extends DatabaseTableManager { Log.d(TAG, "endTime = " + timer.endTime() + ", pauseTime = " + timer.pauseTime()); cv.put(TimersTable.COLUMN_END_TIME, timer.endTime()); cv.put(TimersTable.COLUMN_PAUSE_TIME, timer.pauseTime()); + cv.put(TimersTable.COLUMN_DURATION, timer.duration()); return cv; } diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java b/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java index 8d54ca1..818d4c1 100644 --- a/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java +++ b/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java @@ -1,5 +1,7 @@ package com.philliphsu.clock2.timers; +import android.animation.ObjectAnimator; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; @@ -20,9 +22,11 @@ import butterknife.OnClick; */ public class TimerViewHolder extends BaseViewHolder { private static final String TAG = "TimerViewHolder"; + private static final int MAX_PROGRESS = 10000; private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler; private TimerController mController; + private ObjectAnimator mProgressAnimator; @Bind(R.id.label) TextView mLabel; @Bind(R.id.duration) CountdownChronometer mChronometer; @@ -34,17 +38,20 @@ public class TimerViewHolder extends BaseViewHolder { public TimerViewHolder(ViewGroup parent, OnListItemInteractionListener listener, AsyncTimersTableUpdateHandler asyncTimersTableUpdateHandler) { super(parent, R.layout.item_timer, listener); + Log.d(TAG, "New TimerViewHolder"); mAsyncTimersTableUpdateHandler = asyncTimersTableUpdateHandler; } @Override - public void onBind(Timer timer) { + public void onBind(final Timer timer) { super.onBind(timer); + Log.d(TAG, "Binding TimerViewHolder"); // TOneverDO: create before super mController = new TimerController(timer, mAsyncTimersTableUpdateHandler); bindLabel(timer.label()); bindChronometer(timer); bindButtonControls(timer); + bindProgressBar(timer); } @OnClick(R.id.start_pause) @@ -106,4 +113,42 @@ public class TimerViewHolder extends BaseViewHolder { mAddOneMinute.setVisibility(visibility); mStop.setVisibility(visibility); } + + private void bindProgressBar(Timer timer) { + mProgressBar.setMax(MAX_PROGRESS); + final long timeRemaining = timer.timeRemaining(); + final int progress = (int) (MAX_PROGRESS * (double) timeRemaining / timer.duration()); + + // In case we're reusing an animator instance that could be running + if (mProgressAnimator != null && mProgressAnimator.isRunning()) { + mProgressAnimator.end(); + } + + if (!timer.isRunning()) { + mProgressBar.setProgress(progress); + } else { + mProgressAnimator = ObjectAnimator.ofInt( + // The object that has the property we wish to animate + mProgressBar, + // The name of the property of the object that identifies which setter method + // the animation will call to update its values. Here, a property name of + // "progress" will result in a call to the function setProgress() in ProgressBar. + // The docs for ObjectAnimator#setPropertyName() says that for best performance, + // the setter method should take a float or int parameter, and its return type + // should be void (both of which setProgress() satisfies). + "progress", + // The set of values to animate between. A single value implies that that value + // is the one being animated to. Two values imply starting and ending values. + // More than two values imply a starting value, values to animate through along + // the way, and an ending value (these values will be distributed evenly across + // the duration of the animation). + progress, 0); + mProgressAnimator.setDuration(timeRemaining); + // The algorithm that calculates intermediate values between keyframes. We use linear + // interpolation so that the animation runs at constant speed. + mProgressAnimator.setInterpolator(null/*results in linear interpolation*/); + // This MUST be run on the UI thread. + mProgressAnimator.start(); + } + } } diff --git a/app/src/main/res/layout/item_timer.xml b/app/src/main/res/layout/item_timer.xml index be3eca7..631b345 100644 --- a/app/src/main/res/layout/item_timer.xml +++ b/app/src/main/res/layout/item_timer.xml @@ -34,7 +34,6 @@ android:id="@+id/progress_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:progress="90" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_below="@id/duration"/>