Fixed SeekBar not animating after device rotation
This commit is contained in:
parent
b03c6123e9
commit
2daf03b754
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,7 +21,11 @@ import android.content.Context;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@ -64,6 +68,9 @@ public class ChronometerWithMillis extends TextView {
|
|||||||
private StringBuilder mFormatBuilder;
|
private StringBuilder mFormatBuilder;
|
||||||
private OnChronometerTickListener mOnChronometerTickListener;
|
private OnChronometerTickListener mOnChronometerTickListener;
|
||||||
private StringBuilder mRecycle = new StringBuilder(8);
|
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;
|
private static final int TICK_WHAT = 2;
|
||||||
|
|
||||||
@ -223,6 +230,11 @@ public class ChronometerWithMillis extends TextView {
|
|||||||
return mRunning;
|
return mRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setApplySizeSpan(boolean applySizeSpan) {
|
||||||
|
mApplySizeSpan = applySizeSpan;
|
||||||
|
init(); // update text again
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow() {
|
protected void onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
@ -272,7 +284,14 @@ public class ChronometerWithMillis extends TextView {
|
|||||||
// It looks like Google's Clock app strictly uses .
|
// It looks like Google's Clock app strictly uses .
|
||||||
".%02d", // The . before % is not a format specifier
|
".%02d", // The . before % is not a format specifier
|
||||||
centiseconds);
|
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() {
|
private void updateRunning() {
|
||||||
|
|||||||
@ -87,6 +87,7 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
|||||||
// have a null reference.
|
// have a null reference.
|
||||||
mActivityFab = new WeakReference<>((FloatingActionButton) getActivity().findViewById(R.id.fab));
|
mActivityFab = new WeakReference<>((FloatingActionButton) getActivity().findViewById(R.id.fab));
|
||||||
|
|
||||||
|
mChronometer.setApplySizeSpan(true);
|
||||||
if (mStartTime > 0) {
|
if (mStartTime > 0) {
|
||||||
long base = mStartTime;
|
long base = mStartTime;
|
||||||
if (mPauseTime > 0) {
|
if (mPauseTime > 0) {
|
||||||
@ -95,8 +96,11 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
|||||||
}
|
}
|
||||||
mChronometer.setBase(base);
|
mChronometer.setBase(base);
|
||||||
}
|
}
|
||||||
if (mPrefs.getBoolean(KEY_CHRONOMETER_RUNNING, false)) {
|
if (wasChronometerRunning()) {
|
||||||
mChronometer.start();
|
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
|
// Hides the mini fabs prematurely, so when we actually display this tab
|
||||||
// they won't show even briefly at all before hiding.
|
// they won't show even briefly at all before hiding.
|
||||||
@ -115,6 +119,13 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.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, "onDestroyView()");
|
||||||
Log.d(TAG, "mStartTime = " + mStartTime
|
Log.d(TAG, "mStartTime = " + mStartTime
|
||||||
+ ", mPauseTime = " + mPauseTime);
|
+ ", mPauseTime = " + mPauseTime);
|
||||||
@ -127,17 +138,18 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<LapCursor> loader, LapCursor data) {
|
public void onLoadFinished(Loader<LapCursor> loader, LapCursor data) {
|
||||||
|
Log.d(TAG, "onLoadFinished()");
|
||||||
super.onLoadFinished(loader, data);
|
super.onLoadFinished(loader, data);
|
||||||
// TODO: Will manipulating the cursor's position here affect the current
|
// TODO: Will manipulating the cursor's position here affect the current
|
||||||
// position in the adapter? Should we make a defensive copy and manipulate
|
// position in the adapter? Should we make a defensive copy and manipulate
|
||||||
// that copy instead?
|
// that copy instead?
|
||||||
if (data.moveToFirst()) {
|
if (data.moveToFirst()) {
|
||||||
mCurrentLap = data.getItem();
|
mCurrentLap = data.getItem();
|
||||||
Log.d(TAG, "Current lap ID = " + mCurrentLap.getId());
|
// Log.d(TAG, "Current lap ID = " + mCurrentLap.getId());
|
||||||
}
|
}
|
||||||
if (data.moveToNext()) {
|
if (data.moveToNext()) {
|
||||||
mPreviousLap = data.getItem();
|
mPreviousLap = data.getItem();
|
||||||
Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId());
|
// Log.d(TAG, "Previous lap ID = " + mPreviousLap.getId());
|
||||||
}
|
}
|
||||||
if (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
|
||||||
@ -151,7 +163,10 @@ 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.
|
||||||
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();
|
startNewProgressBarAnimator();
|
||||||
} else {
|
} else {
|
||||||
// I verified the bar was visible already without this, so we probably don't need this,
|
// 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);
|
mSeekBar.setVisibility(View.VISIBLE);
|
||||||
mProgressAnimator = ProgressBarUtils.startNewAnimator(
|
mProgressAnimator = ProgressBarUtils.startNewAnimator(
|
||||||
mSeekBar, getCurrentLapProgressRatio(), timeRemaining);
|
mSeekBar, getCurrentLapProgressRatio(), timeRemaining);
|
||||||
mProgressAnimator.addListener(mAnimatorListener);
|
mProgressAnimator.addListener(new Animator.AnimatorListener() {
|
||||||
}
|
private boolean cancelled;
|
||||||
|
|
||||||
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
|
@Override
|
||||||
private boolean cancelled;
|
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
|
@Override
|
||||||
public void onAnimationCancel(Animator animation) {
|
public void onAnimationEnd(Animator animation) {
|
||||||
cancelled = true;
|
// 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
|
@Override
|
||||||
public void onAnimationRepeat(Animator animation) {
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
@Override
|
||||||
};
|
public void onAnimationRepeat(Animator animation) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private double getCurrentLapProgressRatio() {
|
private double getCurrentLapProgressRatio() {
|
||||||
if (mPreviousLap == null)
|
if (mPreviousLap == null)
|
||||||
@ -356,6 +369,9 @@ public class StopwatchFragment extends RecyclerViewFragment<
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean wasChronometerRunning() {
|
||||||
|
return mPrefs.getBoolean(KEY_CHRONOMETER_RUNNING, false);
|
||||||
|
}
|
||||||
// ======================= DO NOT IMPLEMENT ============================
|
// ======================= DO NOT IMPLEMENT ============================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -17,14 +17,12 @@ import com.philliphsu.clock2.util.ProgressBarUtils;
|
|||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import butterknife.OnTouch;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Phillip Hsu on 7/25/2016.
|
* Created by Phillip Hsu on 7/25/2016.
|
||||||
*/
|
*/
|
||||||
public class TimerViewHolder extends BaseViewHolder<Timer> {
|
public class TimerViewHolder extends BaseViewHolder<Timer> {
|
||||||
private static final String TAG = "TimerViewHolder";
|
private static final String TAG = "TimerViewHolder";
|
||||||
private static final int MAX_PROGRESS = 100000;
|
|
||||||
|
|
||||||
private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
|
private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
|
||||||
private TimerController mController;
|
private TimerController mController;
|
||||||
@ -71,11 +69,6 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
|
|||||||
mController.stop();
|
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) {
|
private void bindLabel(String label) {
|
||||||
if (!label.isEmpty()) {
|
if (!label.isEmpty()) {
|
||||||
mLabel.setText(label);
|
mLabel.setText(label);
|
||||||
@ -137,9 +130,10 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
|
|||||||
if (!timer.isRunning()) {
|
if (!timer.isRunning()) {
|
||||||
// If our scale were 1, then casting ratio to an int will ALWAYS
|
// If our scale were 1, then casting ratio to an int will ALWAYS
|
||||||
// truncate down to zero.
|
// truncate down to zero.
|
||||||
mSeekBar.setMax(100);
|
// mSeekBar.setMax(100);
|
||||||
final int progress = (int) (100 * ratio);
|
// final int progress = (int) (100 * ratio);
|
||||||
mSeekBar.setProgress(progress);
|
// mSeekBar.setProgress(progress);
|
||||||
|
ProgressBarUtils.setProgress(mSeekBar, ratio);
|
||||||
// mSeekBar.getThumb().mutate().setAlpha(progress == 0 ? 0 : 255);
|
// mSeekBar.getThumb().mutate().setAlpha(progress == 0 ? 0 : 255);
|
||||||
} else {
|
} else {
|
||||||
// mSeekBar.getThumb().mutate().setAlpha(255);
|
// mSeekBar.getThumb().mutate().setAlpha(255);
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<SeekBar
|
<com.philliphsu.clock2.UntouchableSeekBar
|
||||||
android:id="@+id/seek_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"
|
||||||
|
|||||||
@ -33,9 +33,8 @@
|
|||||||
<!--TODO: Consider removing this bottom margin, because the seekbar
|
<!--TODO: Consider removing this bottom margin, because the seekbar
|
||||||
is rendering with HUGE top and bottom padding already. -->
|
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-->
|
<!--The default style has padding start and end, so we remove both-->
|
||||||
<SeekBar
|
<com.philliphsu.clock2.UntouchableSeekBar
|
||||||
android:id="@+id/seek_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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user