diff options
author | rachelzhang <rachelzhang@google.com> | 2014-09-17 13:23:54 -0700 |
---|---|---|
committer | rachelzhang <rachelzhang@google.com> | 2014-09-17 16:30:26 -0700 |
commit | c5deecb3c25796538d762857692182bf3e5f1e21 (patch) | |
tree | 43b6369f0c239c1cc2310e52e0607ef9c6ddb4c6 | |
parent | 5657f4cb95da0f5d11aceaadf7b328dde4044a47 (diff) | |
download | android_packages_apps_DeskClock-c5deecb3c25796538d762857692182bf3e5f1e21.tar.gz android_packages_apps_DeskClock-c5deecb3c25796538d762857692182bf3e5f1e21.tar.bz2 android_packages_apps_DeskClock-c5deecb3c25796538d762857692182bf3e5f1e21.zip |
Timer UX update
Bug: 17548145
Bug: 17549427
* Change reveal animation to rotate
* Change timer fire reset from reset to stop
* Swap delete and add buttons on timer
Change-Id: I5b72cbee92bba25d811bb28efc62d89b35a4e9a5
19 files changed, 157 insertions, 96 deletions
diff --git a/res/drawable-hdpi/ic_fab_reset.png b/res/drawable-hdpi/ic_fab_reset.png Binary files differdeleted file mode 100644 index acdfca40b..000000000 --- a/res/drawable-hdpi/ic_fab_reset.png +++ /dev/null diff --git a/res/drawable-hdpi/ic_fab_stop.png b/res/drawable-hdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..43b624d4f --- /dev/null +++ b/res/drawable-hdpi/ic_fab_stop.png diff --git a/res/drawable-mdpi/ic_fab_reset.png b/res/drawable-mdpi/ic_fab_reset.png Binary files differdeleted file mode 100644 index f474e3606..000000000 --- a/res/drawable-mdpi/ic_fab_reset.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_fab_stop.png b/res/drawable-mdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..c6bbb0662 --- /dev/null +++ b/res/drawable-mdpi/ic_fab_stop.png diff --git a/res/drawable-sw600dp-hdpi/ic_fab_stop.png b/res/drawable-sw600dp-hdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..58e6a8da9 --- /dev/null +++ b/res/drawable-sw600dp-hdpi/ic_fab_stop.png diff --git a/res/drawable-sw600dp-mdpi/ic_fab_stop.png b/res/drawable-sw600dp-mdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..f0962f79d --- /dev/null +++ b/res/drawable-sw600dp-mdpi/ic_fab_stop.png diff --git a/res/drawable-sw600dp-xhdpi/ic_fab_stop.png b/res/drawable-sw600dp-xhdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..b15835692 --- /dev/null +++ b/res/drawable-sw600dp-xhdpi/ic_fab_stop.png diff --git a/res/drawable-sw600dp-xxhdpi/ic_fab_stop.png b/res/drawable-sw600dp-xxhdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..190857be1 --- /dev/null +++ b/res/drawable-sw600dp-xxhdpi/ic_fab_stop.png diff --git a/res/drawable-sw600dp-xxxhdpi/ic_fab_stop.png b/res/drawable-sw600dp-xxxhdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..91cfc95c8 --- /dev/null +++ b/res/drawable-sw600dp-xxxhdpi/ic_fab_stop.png diff --git a/res/drawable-xhdpi/ic_fab_reset.png b/res/drawable-xhdpi/ic_fab_reset.png Binary files differdeleted file mode 100644 index f0a8832e4..000000000 --- a/res/drawable-xhdpi/ic_fab_reset.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_fab_stop.png b/res/drawable-xhdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..a6d1077c2 --- /dev/null +++ b/res/drawable-xhdpi/ic_fab_stop.png diff --git a/res/drawable-xxhdpi/ic_fab_reset.png b/res/drawable-xxhdpi/ic_fab_reset.png Binary files differdeleted file mode 100644 index 6276b7d0e..000000000 --- a/res/drawable-xxhdpi/ic_fab_reset.png +++ /dev/null diff --git a/res/drawable-xxhdpi/ic_fab_stop.png b/res/drawable-xxhdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..6da87c300 --- /dev/null +++ b/res/drawable-xxhdpi/ic_fab_stop.png diff --git a/res/drawable-xxxhdpi/ic_fab_reset.png b/res/drawable-xxxhdpi/ic_fab_reset.png Binary files differdeleted file mode 100644 index e4b4448a5..000000000 --- a/res/drawable-xxxhdpi/ic_fab_reset.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/ic_fab_stop.png b/res/drawable-xxxhdpi/ic_fab_stop.png Binary files differnew file mode 100644 index 000000000..4c3640282 --- /dev/null +++ b/res/drawable-xxxhdpi/ic_fab_stop.png diff --git a/res/layout/timer_full_screen_fragment.xml b/res/layout/timer_full_screen_fragment.xml index 464cacbb4..596226719 100644 --- a/res/layout/timer_full_screen_fragment.xml +++ b/res/layout/timer_full_screen_fragment.xml @@ -41,8 +41,8 @@ android:layout_gravity="center_horizontal|bottom" android:layout_margin="@dimen/footer_button_layout_margin" android:background="@drawable/floating_action_button" - android:src="@drawable/ic_fab_reset" - android:contentDescription="@string/timer_reset" /> + android:src="@drawable/ic_fab_stop" + android:contentDescription="@string/timer_stop" /> </FrameLayout> <com.android.deskclock.TimerSetupView diff --git a/src/com/android/deskclock/timer/TimerFragment.java b/src/com/android/deskclock/timer/TimerFragment.java index caadaa6ef..9c13752c7 100644 --- a/src/com/android/deskclock/timer/TimerFragment.java +++ b/src/com/android/deskclock/timer/TimerFragment.java @@ -20,8 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.app.Activity; +import android.animation.TimeInterpolator; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; @@ -29,7 +28,6 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Resources; import android.os.Bundle; -import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.view.ViewPager; import android.text.format.DateUtils; @@ -39,21 +37,18 @@ import android.transition.TransitionManager; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewAnimationUtils; import android.view.ViewGroup; -import android.view.ViewGroupOverlay; import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.Toast; +import com.android.deskclock.AnimatorUtils; import com.android.deskclock.DeskClock; import com.android.deskclock.DeskClockFragment; import com.android.deskclock.R; import com.android.deskclock.TimerSetupView; -import com.android.deskclock.ToastMaster; import com.android.deskclock.Utils; import com.android.deskclock.VerticalViewPager; @@ -64,9 +59,9 @@ public class TimerFragment extends DeskClockFragment implements OnSharedPreferen private static final String KEY_ENTRY_STATE = "entry_state"; private static final int PAGINATION_DOTS_COUNT = 4; private static final String CURR_PAGE = "_currPage"; - private static final Handler HANDLER = new Handler(); - private static final Interpolator REVEAL_INTERPOLATOR = - new PathInterpolator(0.0f, 0.0f, 0.2f, 1.0f); + private static final TimeInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); + private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); + private static final long ROTATE_ANIM_DURATION_MILIS = 150; private boolean mTicking = false; private TimerSetupView mSetupView; @@ -166,13 +161,15 @@ public class TimerFragment extends DeskClockFragment implements OnSharedPreferen @Override public void onClick(View v) { if (mAdapter.getCount() != 0) { - revealAnimation(getActivity(), v, Utils.getNextHourColor()); - HANDLER.postDelayed(new Runnable() { + final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override - public void run() { + public void onAnimationEnd(Animator animation) { + mSetupView.reset(); // Make sure the setup is cleared for next time + mSetupView.setScaleX(1.0f); // Reset the scale for setup view goToPagerView(); } - }, ANIMATION_TIME_MILLIS); + }; + createRotateAnimator(adapter, false).start(); } } }); @@ -368,39 +365,95 @@ public class TimerFragment extends DeskClockFragment implements OnSharedPreferen break; case TimerObj.STATE_TIMESUP: // time-up but didn't stopped, continue negative ticking mFab.setVisibility(View.VISIBLE); - mFab.setContentDescription(r.getString(R.string.timer_reset)); - mFab.setImageResource(R.drawable.ic_fab_reset); + mFab.setContentDescription(r.getString(R.string.timer_stop)); + mFab.setImageResource(R.drawable.ic_fab_stop); break; default: } } + private Animator getRotateFromAnimator(View view) { + final Animator animator = new ObjectAnimator().ofFloat(view, View.SCALE_X, 1.0f, 0.0f); + animator.setDuration(ROTATE_ANIM_DURATION_MILIS); + animator.setInterpolator(DECELERATE_INTERPOLATOR); + return animator; + } + + private Animator getRotateToAnimator(View view) { + final Animator animator = new ObjectAnimator().ofFloat(view, View.SCALE_X, 0.0f, 1.0f); + animator.setDuration(ROTATE_ANIM_DURATION_MILIS); + animator.setInterpolator(ACCELERATE_INTERPOLATOR); + return animator; + } + + private Animator getScaleFooterButtonsAnimator(final boolean show) { + final AnimatorSet animatorSet = new AnimatorSet(); + final Animator leftButtonAnimator = AnimatorUtils.getScaleAnimator( + mLeftButton, show ? 0.0f : 1.0f, show ? 1.0f : 0.0f); + final Animator rightButtonAnimator = AnimatorUtils.getScaleAnimator( + mRightButton, show ? 0.0f : 1.0f, show ? 1.0f : 0.0f); + final float fabStartScale = (show && mFab.getVisibility() == View.INVISIBLE) ? 0.0f : 1.0f; + final Animator fabAnimator = AnimatorUtils.getScaleAnimator( + mFab, fabStartScale, show ? 1.0f : 0.0f); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mLeftButton.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + mRightButton.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + restoreScale(mLeftButton); + restoreScale(mRightButton); + restoreScale(mFab); + } + }); + // If not show, means transiting from timer view to setup view, + // when the setup view starts to rotate, the footer buttons are already invisible, + // so the scaling has to finish before the setup view starts rotating + animatorSet.setDuration(show ? ROTATE_ANIM_DURATION_MILIS * 2 : ROTATE_ANIM_DURATION_MILIS); + animatorSet.play(leftButtonAnimator).with(rightButtonAnimator).with(fabAnimator); + return animatorSet; + } + + private void restoreScale(View view) { + view.setScaleX(1.0f); + view.setScaleY(1.0f); + } + + private Animator createRotateAnimator(AnimatorListenerAdapter adapter, boolean toSetup) { + final AnimatorSet animatorSet = new AnimatorSet(); + final Animator rotateFrom = getRotateFromAnimator(toSetup ? mTimerView : mSetupView); + rotateFrom.addListener(adapter); + final Animator rotateTo = getRotateToAnimator(toSetup ? mSetupView : mTimerView); + final Animator expandFooterButton = getScaleFooterButtonsAnimator(!toSetup); + animatorSet.play(rotateFrom).before(rotateTo).with(expandFooterButton); + return animatorSet; + } + @Override public void onFabClick(View view) { if (mLastView != mTimerView) { - final Activity activity = getActivity(); - revealAnimation(activity, mFab, activity.getResources().getColor(R.color.hot_pink)); - HANDLER.postDelayed(new Runnable() { + // Timer is at Setup View, so fab is "play", rotate from setup view to timer view + final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override - public void run() { - // Timer is at Setup View, so fab is "play" + public void onAnimationStart(Animator animation) { final int timerLength = mSetupView.getTime(); - if (timerLength == 0) { - return; - } - final TimerObj timerObj = new TimerObj(timerLength * DateUtils.SECOND_IN_MILLIS); timerObj.mState = TimerObj.STATE_RUNNING; updateTimerState(timerObj, Timers.START_TIMER); // Go to the newly created timer view mAdapter.addTimer(timerObj); - goToPagerView(); mViewPager.setCurrentItem(0); highlightPageIndicator(0); + } + + @Override + public void onAnimationEnd(Animator animation) { mSetupView.reset(); // Make sure the setup is cleared for next time + mSetupView.setScaleX(1.0f); // Reset the scale for setup view + goToPagerView(); } - }, ANIMATION_TIME_MILLIS); + }; + createRotateAnimator(adapter, false).start(); } else { // Timer is at view pager, so fab is "play" or "pause" or "square that means reset" final TimerObj t = getCurrentTimer(); @@ -445,48 +498,6 @@ public class TimerFragment extends DeskClockFragment implements OnSharedPreferen } - public static void revealAnimation(final Activity activity, final View centerView, int color) { - - final View decorView = activity.getWindow().getDecorView(); - final ViewGroupOverlay overlay = (ViewGroupOverlay) decorView.getOverlay(); - - // Create a transient view for performing the reveal animation. - final View revealView = new View(activity); - revealView.setRight(decorView.getWidth()); - revealView.setBottom(decorView.getHeight()); - revealView.setBackgroundColor(color); - overlay.add(revealView); - - final int[] clearLocation = new int[2]; - centerView.getLocationInWindow(clearLocation); - clearLocation[0] += centerView.getWidth() / 2; - clearLocation[1] += centerView.getHeight() / 2; - final int revealCenterX = clearLocation[0] - revealView.getLeft(); - final int revealCenterY = clearLocation[1] - revealView.getTop(); - - final int xMax = Math.max(revealCenterX, decorView.getWidth() - revealCenterX); - final int yMax = Math.max(revealCenterY, decorView.getHeight() - revealCenterY); - final float revealRadius = (float) Math.sqrt(Math.pow(xMax, 2.0) + Math.pow(yMax, 2.0)); - - final Animator revealAnimator = ViewAnimationUtils.createCircularReveal( - revealView, revealCenterX, revealCenterY, 0.0f, revealRadius); - revealAnimator.setInterpolator(REVEAL_INTERPOLATOR); - - float endAlpha = activity instanceof TimerAlertFullScreen ? 1.0f : 0.0f; - final ValueAnimator fadeAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, endAlpha); - fadeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - overlay.remove(revealView); - } - }); - - final AnimatorSet alertAnimator = new AnimatorSet(); - alertAnimator.setDuration(ANIMATION_TIME_MILLIS); - alertAnimator.play(revealAnimator).before(fadeAnimator); - alertAnimator.start(); - } - private TimerObj getCurrentTimer() { if (mViewPager == null) { return null; @@ -536,42 +547,46 @@ public class TimerFragment extends DeskClockFragment implements OnSharedPreferen mRightButton.setEnabled(true); mLeftButton.setVisibility(mLastView != mTimerView ? View.GONE : View.VISIBLE); mRightButton.setVisibility(mLastView != mTimerView ? View.GONE : View.VISIBLE); - mLeftButton.setImageResource(R.drawable.ic_add_timer); - mLeftButton.setContentDescription(getString(R.string.timer_add_timer)); - mRightButton.setImageResource(R.drawable.ic_delete); - mRightButton.setContentDescription(getString(R.string.timer_delete)); + mLeftButton.setImageResource(R.drawable.ic_delete); + mLeftButton.setContentDescription(getString(R.string.timer_delete)); + mRightButton.setImageResource(R.drawable.ic_add_timer); + mRightButton.setContentDescription(getString(R.string.timer_add_timer)); } } @Override - public void onLeftButtonClick(View view) { - // Respond to another timer - revealAnimation(getActivity(), view, Utils.getNextHourColor()); - HANDLER.postDelayed(new Runnable() { + public void onRightButtonClick(View view) { + // Respond to add another timer + final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override - public void run() { + public void onAnimationEnd(Animator animation) { mSetupView.reset(); + mTimerView.setScaleX(1.0f); // Reset the scale for timer view goToSetUpView(); } - }, ANIMATION_TIME_MILLIS); + }; + createRotateAnimator(adapter, true).start(); } @Override - public void onRightButtonClick(View view) { - // Respond to delete + public void onLeftButtonClick(View view) { + // Respond to delete timer final TimerObj timer = getCurrentTimer(); + if (timer == null) { + return; // Prevent NPE if user click delete faster than the fade animation + } if (timer.mState == TimerObj.STATE_TIMESUP) { mNotificationManager.cancel(timer.mTimerId); } if (mAdapter.getCount() == 1) { - final Activity activity = getActivity(); - revealAnimation(activity, view, activity.getResources().getColor(R.color.clock_white)); - HANDLER.postDelayed(new Runnable() { + final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { @Override - public void run() { + public void onAnimationEnd(Animator animation) { + mTimerView.setScaleX(1.0f); // Reset the scale for timer view deleteTimer(timer); } - }, ANIMATION_TIME_MILLIS); + }; + createRotateAnimator(adapter, true).start(); } else { TransitionManager.beginDelayedTransition(mContentView, mDeleteTransition); deleteTimer(timer); diff --git a/src/com/android/deskclock/timer/TimerFullScreenFragment.java b/src/com/android/deskclock/timer/TimerFullScreenFragment.java index 348dff7a8..7e11c4476 100644 --- a/src/com/android/deskclock/timer/TimerFullScreenFragment.java +++ b/src/com/android/deskclock/timer/TimerFullScreenFragment.java @@ -18,7 +18,9 @@ package com.android.deskclock.timer; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; @@ -27,7 +29,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; @@ -36,10 +37,14 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroupOverlay; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.TextView; @@ -70,6 +75,8 @@ public class TimerFullScreenFragment extends DeskClockFragment private static final String TAG = "TimerFragment1"; private static final String KEY_ENTRY_STATE = "entry_state"; + private static final Interpolator REVEAL_INTERPOLATOR = + new PathInterpolator(0.0f, 0.0f, 0.2f, 1.0f); public static final String GOTO_SETUP_VIEW = "deskclock.timers.gotosetup"; private Bundle mViewState; @@ -526,9 +533,7 @@ public class TimerFullScreenFragment extends DeskClockFragment mFab.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - final Activity activity = getActivity(); - TimerFragment.revealAnimation(activity, mFab, activity.getResources() - .getColor(R.color.clock_white)); + revealAnimation(mFab, getActivity().getResources().getColor(R.color.clock_white)); new Handler().postDelayed(new Runnable() { @Override public void run() { @@ -539,6 +544,47 @@ public class TimerFullScreenFragment extends DeskClockFragment }); } + private void revealAnimation(final View centerView, int color) { + final Activity activity = getActivity(); + final View decorView = activity.getWindow().getDecorView(); + final ViewGroupOverlay overlay = (ViewGroupOverlay) decorView.getOverlay(); + + // Create a transient view for performing the reveal animation. + final View revealView = new View(activity); + revealView.setRight(decorView.getWidth()); + revealView.setBottom(decorView.getHeight()); + revealView.setBackgroundColor(color); + overlay.add(revealView); + + final int[] clearLocation = new int[2]; + centerView.getLocationInWindow(clearLocation); + clearLocation[0] += centerView.getWidth() / 2; + clearLocation[1] += centerView.getHeight() / 2; + final int revealCenterX = clearLocation[0] - revealView.getLeft(); + final int revealCenterY = clearLocation[1] - revealView.getTop(); + + final int xMax = Math.max(revealCenterX, decorView.getWidth() - revealCenterX); + final int yMax = Math.max(revealCenterY, decorView.getHeight() - revealCenterY); + final float revealRadius = (float) Math.sqrt(Math.pow(xMax, 2.0) + Math.pow(yMax, 2.0)); + + final Animator revealAnimator = ViewAnimationUtils.createCircularReveal( + revealView, revealCenterX, revealCenterY, 0.0f, revealRadius); + revealAnimator.setInterpolator(REVEAL_INTERPOLATOR); + + final ValueAnimator fadeAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 1.0f); + fadeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + overlay.remove(revealView); + } + }); + + final AnimatorSet alertAnimator = new AnimatorSet(); + alertAnimator.setDuration(TimerFragment.ANIMATION_TIME_MILLIS); + alertAnimator.play(revealAnimator).before(fadeAnimator); + alertAnimator.start(); + } + @Override public void onPause() { if (getActivity() instanceof DeskClock) { diff --git a/src/com/android/deskclock/timer/TimerReceiver.java b/src/com/android/deskclock/timer/TimerReceiver.java index d92ee040e..5bf2bbb8f 100644 --- a/src/com/android/deskclock/timer/TimerReceiver.java +++ b/src/com/android/deskclock/timer/TimerReceiver.java @@ -444,10 +444,10 @@ public class TimerReceiver extends BroadcastReceiver { .addAction( timerObj.getDeleteAfterUse() ? android.R.drawable.ic_menu_close_clear_cancel - : R.drawable.ic_reset, + : R.drawable.ic_notify_stop, timerObj.getDeleteAfterUse() ? context.getResources().getString(R.string.timer_done) - : context.getResources().getString(R.string.timer_reset), + : context.getResources().getString(R.string.timer_stop), stopIntent) .setContentTitle(timerObj.getLabelOrDefault(context)) .setContentText(context.getResources().getString(R.string.timer_times_up)) |