From 2daf03b7546c6af45146c69b5f86c1c45daadb7a Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Sun, 28 Aug 2016 22:32:16 -0700 Subject: [PATCH] Fixed SeekBar not animating after device rotation --- .../philliphsu/clock2/UntouchableSeekBar.java | 29 +++++++ .../stopwatch/ChronometerWithMillis.java | 21 ++++- .../clock2/stopwatch/StopwatchFragment.java | 82 +++++++++++-------- .../clock2/timers/TimerViewHolder.java | 14 +--- .../main/res/layout/fragment_stopwatch.xml | 2 +- app/src/main/res/layout/item_timer.xml | 3 +- 6 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/com/philliphsu/clock2/UntouchableSeekBar.java diff --git a/app/src/main/java/com/philliphsu/clock2/UntouchableSeekBar.java b/app/src/main/java/com/philliphsu/clock2/UntouchableSeekBar.java new file mode 100644 index 0000000..78cbba7 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/UntouchableSeekBar.java @@ -0,0 +1,29 @@ +package com.philliphsu.clock2; + +import android.content.Context; +import android.support.v7.widget.AppCompatSeekBar; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * A SeekBar that cannot be touch controlled. + */ +public class UntouchableSeekBar extends AppCompatSeekBar { + + public UntouchableSeekBar(Context context) { + super(context); + } + + public UntouchableSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public UntouchableSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public final boolean onTouchEvent(MotionEvent event) { + return true; + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/ChronometerWithMillis.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/ChronometerWithMillis.java index 293f092..f8ca53f 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/ChronometerWithMillis.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/ChronometerWithMillis.java @@ -21,7 +21,11 @@ import android.content.Context; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextUtils; import android.text.format.DateUtils; +import android.text.style.RelativeSizeSpan; import android.util.AttributeSet; import android.util.Log; import android.widget.TextView; @@ -64,6 +68,9 @@ public class ChronometerWithMillis extends TextView { private StringBuilder mFormatBuilder; private OnChronometerTickListener mOnChronometerTickListener; private StringBuilder mRecycle = new StringBuilder(8); + private boolean mApplySizeSpan; + + private static final RelativeSizeSpan SIZE_SPAN = new RelativeSizeSpan(0.5f); private static final int TICK_WHAT = 2; @@ -223,6 +230,11 @@ public class ChronometerWithMillis extends TextView { return mRunning; } + public void setApplySizeSpan(boolean applySizeSpan) { + mApplySizeSpan = applySizeSpan; + init(); // update text again + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); @@ -272,7 +284,14 @@ public class ChronometerWithMillis extends TextView { // It looks like Google's Clock app strictly uses . ".%02d", // The . before % is not a format specifier centiseconds); - setText(text.concat(centisecondsText)); + if (mApplySizeSpan) { + SpannableString span = new SpannableString(centisecondsText); + span.setSpan(SIZE_SPAN, 0, centisecondsText.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + setText(TextUtils.concat(text, span), BufferType.SPANNABLE); + } else { + setText(text.concat(centisecondsText)); + } } private void updateRunning() { 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 8396586..f14033d 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java @@ -87,6 +87,7 @@ public class StopwatchFragment extends RecyclerViewFragment< // have a null reference. mActivityFab = new WeakReference<>((FloatingActionButton) getActivity().findViewById(R.id.fab)); + mChronometer.setApplySizeSpan(true); if (mStartTime > 0) { long base = mStartTime; if (mPauseTime > 0) { @@ -95,8 +96,11 @@ public class StopwatchFragment extends RecyclerViewFragment< } mChronometer.setBase(base); } - if (mPrefs.getBoolean(KEY_CHRONOMETER_RUNNING, false)) { + if (wasChronometerRunning()) { mChronometer.start(); + // Note: mChronometer.isRunning() will return false at this point and + // in other upcoming lifecycle methods because it is not yet visible + // (i.e. mVisible == false). } // Hides the mini fabs prematurely, so when we actually display this tab // they won't show even briefly at all before hiding. @@ -115,6 +119,13 @@ public class StopwatchFragment extends RecyclerViewFragment< @Override public void onDestroyView() { super.onDestroyView(); + // Every view that was in our tree is dereferenced for us. + // The reason we can control the animator here is because members + // are not dereferenced here, as evidenced by mStartTime and mPauseTime + // retaining their values. + if (mProgressAnimator != null) { + mProgressAnimator.removeAllListeners(); + } Log.d(TAG, "onDestroyView()"); Log.d(TAG, "mStartTime = " + mStartTime + ", mPauseTime = " + mPauseTime); @@ -127,17 +138,18 @@ public class StopwatchFragment extends RecyclerViewFragment< @Override public void onLoadFinished(Loader loader, LapCursor data) { + Log.d(TAG, "onLoadFinished()"); super.onLoadFinished(loader, data); // TODO: Will manipulating the cursor's position here affect the current // position in the adapter? Should we make a defensive copy and manipulate // that copy instead? if (data.moveToFirst()) { mCurrentLap = data.getItem(); - Log.d(TAG, "Current lap ID = " + mCurrentLap.getId()); +// Log.d(TAG, "Current lap ID = " + mCurrentLap.getId()); } if (data.moveToNext()) { mPreviousLap = data.getItem(); - Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId()); +// Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId()); } if (mCurrentLap != null && mPreviousLap != null) { // We really only want to start a new animator when the NEWLY RETRIEVED current @@ -151,7 +163,10 @@ 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. - if (mChronometer.isRunning()) { + // + // NOTE: If we just recreated ourselves due to rotation, mChronometer.isRunning() == false, + // because it is not yet visible (i.e. mVisible == false). + if (mChronometer.isRunning() || wasChronometerRunning()) { startNewProgressBarAnimator(); } else { // I verified the bar was visible already without this, so we probably don't need this, @@ -299,41 +314,39 @@ public class StopwatchFragment extends RecyclerViewFragment< mSeekBar.setVisibility(View.VISIBLE); mProgressAnimator = ProgressBarUtils.startNewAnimator( mSeekBar, getCurrentLapProgressRatio(), timeRemaining); - mProgressAnimator.addListener(mAnimatorListener); - } + mProgressAnimator.addListener(new Animator.AnimatorListener() { + private boolean cancelled; - private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { - private boolean cancelled; + @Override + public void onAnimationStart(Animator animation) { - @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 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 onAnimationRepeat(Animator animation) { + @Override + public void onAnimationCancel(Animator animation) { + cancelled = true; + } - } - }; + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + } private double getCurrentLapProgressRatio() { if (mPreviousLap == null) @@ -356,6 +369,9 @@ public class StopwatchFragment extends RecyclerViewFragment< .apply(); } + private boolean wasChronometerRunning() { + return mPrefs.getBoolean(KEY_CHRONOMETER_RUNNING, false); + } // ======================= DO NOT IMPLEMENT ============================ @Override 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 37b0d20..62fc0a2 100644 --- a/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java +++ b/app/src/main/java/com/philliphsu/clock2/timers/TimerViewHolder.java @@ -17,14 +17,12 @@ import com.philliphsu.clock2.util.ProgressBarUtils; import butterknife.Bind; import butterknife.OnClick; -import butterknife.OnTouch; /** * Created by Phillip Hsu on 7/25/2016. */ public class TimerViewHolder extends BaseViewHolder { private static final String TAG = "TimerViewHolder"; - private static final int MAX_PROGRESS = 100000; private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler; private TimerController mController; @@ -71,11 +69,6 @@ public class TimerViewHolder extends BaseViewHolder { mController.stop(); } - @OnTouch(R.id.seek_bar) - boolean captureTouchOnSeekBar() { - return true; // Do nothing when the user touches the seek bar - } - private void bindLabel(String label) { if (!label.isEmpty()) { mLabel.setText(label); @@ -137,9 +130,10 @@ public class TimerViewHolder extends BaseViewHolder { if (!timer.isRunning()) { // If our scale were 1, then casting ratio to an int will ALWAYS // truncate down to zero. - mSeekBar.setMax(100); - final int progress = (int) (100 * ratio); - mSeekBar.setProgress(progress); +// mSeekBar.setMax(100); +// final int progress = (int) (100 * ratio); +// mSeekBar.setProgress(progress); + ProgressBarUtils.setProgress(mSeekBar, ratio); // mSeekBar.getThumb().mutate().setAlpha(progress == 0 ? 0 : 255); } else { // mSeekBar.getThumb().mutate().setAlpha(255); diff --git a/app/src/main/res/layout/fragment_stopwatch.xml b/app/src/main/res/layout/fragment_stopwatch.xml index 7b3d7ec..e1744e9 100644 --- a/app/src/main/res/layout/fragment_stopwatch.xml +++ b/app/src/main/res/layout/fragment_stopwatch.xml @@ -26,7 +26,7 @@ - - -