Fixed SeekBar not animating after device rotation

This commit is contained in:
Phillip Hsu 2016-08-28 22:32:16 -07:00
parent b03c6123e9
commit 2daf03b754
6 changed files with 104 additions and 47 deletions

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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<LapCursor> 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

View File

@ -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<Timer> {
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<Timer> {
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<Timer> {
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);

View File

@ -26,7 +26,7 @@
</LinearLayout>
<SeekBar
<com.philliphsu.clock2.UntouchableSeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -33,9 +33,8 @@
<!--TODO: Consider removing this bottom margin, because the seekbar
is rendering with HUGE top and bottom padding already. -->
<!-- Cannot be touch controlled -->
<!--The default style has padding start and end, so we remove both-->
<SeekBar
<com.philliphsu.clock2.UntouchableSeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"