1919import android .animation .Animator ;
2020import android .animation .Animator .AnimatorListener ;
2121import android .animation .AnimatorListenerAdapter ;
22+ import android .animation .ObjectAnimator ;
2223import android .animation .TimeInterpolator ;
2324import android .animation .ValueAnimator ;
2425import android .animation .ValueAnimator .AnimatorUpdateListener ;
2728import android .content .res .TypedArray ;
2829import android .graphics .Canvas ;
2930import android .graphics .RectF ;
31+ import android .graphics .drawable .Drawable ;
3032import android .os .Vibrator ;
3133import android .text .TextUtils ;
3234import android .util .AttributeSet ;
@@ -52,10 +54,11 @@ public class MultiWaveView extends View {
5254
5355 // Wave state machine
5456 private static final int STATE_IDLE = 0 ;
55- private static final int STATE_FIRST_TOUCH = 1 ;
56- private static final int STATE_TRACKING = 2 ;
57- private static final int STATE_SNAP = 3 ;
58- private static final int STATE_FINISH = 4 ;
57+ private static final int STATE_START = 1 ;
58+ private static final int STATE_FIRST_TOUCH = 2 ;
59+ private static final int STATE_TRACKING = 3 ;
60+ private static final int STATE_SNAP = 4 ;
61+ private static final int STATE_FINISH = 5 ;
5962
6063 // Animation properties.
6164 private static final float SNAP_MARGIN_DEFAULT = 20.0f ; // distance to ring before we snap to it
@@ -74,17 +77,18 @@ public interface OnTriggerListener {
7477 private static final int CHEVRON_INCREMENTAL_DELAY = 160 ;
7578 private static final int CHEVRON_ANIMATION_DURATION = 850 ;
7679 private static final int RETURN_TO_HOME_DELAY = 1200 ;
77- private static final int RETURN_TO_HOME_DURATION = 300 ;
80+ private static final int RETURN_TO_HOME_DURATION = 200 ;
7881 private static final int HIDE_ANIMATION_DELAY = 200 ;
7982 private static final int HIDE_ANIMATION_DURATION = 200 ;
8083 private static final int SHOW_ANIMATION_DURATION = 200 ;
8184 private static final int SHOW_ANIMATION_DELAY = 50 ;
85+ private static final int INITIAL_SHOW_HANDLE_DURATION = 200 ;
86+
8287 private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f ;
83- private static final float TARGET_SCALE_SELECTED = 0.8f ;
84- private static final long INITIAL_SHOW_HANDLE_DURATION = 200 ;
85- private static final float TARGET_SCALE_UNSELECTED = 1.0f ;
86- private static final float RING_SCALE_UNSELECTED = 0.5f ;
87- private static final float RING_SCALE_SELECTED = 1.5f ;
88+ private static final float TARGET_SCALE_EXPANDED = 1.0f ;
89+ private static final float TARGET_SCALE_COLLAPSED = 0.8f ;
90+ private static final float RING_SCALE_EXPANDED = 1.0f ;
91+ private static final float RING_SCALE_COLLAPSED = 0.5f ;
8892
8993 private TimeInterpolator mChevronAnimationInterpolator = Ease .Quad .easeOut ;
9094
@@ -182,7 +186,7 @@ public void onAnimationEnd(Animator animator) {
182186 if (mNewTargetResources != 0 ) {
183187 internalSetTargetResources (mNewTargetResources );
184188 mNewTargetResources = 0 ;
185- hideTargets (false );
189+ hideTargets (false , false );
186190 }
187191 mAnimatingTargets = false ;
188192 }
@@ -195,6 +199,7 @@ public void onAnimationEnd(Animator animator) {
195199 private int mVerticalInset ;
196200 private int mGravity = Gravity .TOP ;
197201 private boolean mInitialLayout = true ;
202+ private Tweener mBackgroundAnimator ;
198203
199204 public MultiWaveView (Context context ) {
200205 this (context , null );
@@ -358,14 +363,21 @@ private void switchToState(int state, float x, float y) {
358363 switch (state ) {
359364 case STATE_IDLE :
360365 deactivateTargets ();
366+ hideTargets (true , false );
367+ startBackgroundAnimation (0 , 0.0f );
361368 mHandleDrawable .setState (TargetDrawable .STATE_INACTIVE );
362369 break ;
363370
371+ case STATE_START :
372+ deactivateHandle (0 , 0 , 1.0f , null );
373+ startBackgroundAnimation (0 , 0.0f );
374+ break ;
375+
364376 case STATE_FIRST_TOUCH :
365- stopHandleAnimation ();
366377 deactivateTargets ();
367378 showTargets (true );
368- activateHandle ();
379+ mHandleDrawable .setState (TargetDrawable .STATE_ACTIVE );
380+ startBackgroundAnimation (INITIAL_SHOW_HANDLE_DURATION , 1.0f );
369381 setGrabbedState (OnTriggerListener .CENTER_HANDLE );
370382 if (AccessibilityManager .getInstance (mContext ).isEnabled ()) {
371383 announceTargets ();
@@ -384,17 +396,29 @@ private void switchToState(int state, float x, float y) {
384396 }
385397 }
386398
387- private void activateHandle () {
388- mHandleDrawable .setState (TargetDrawable .STATE_ACTIVE );
389- if (mAlwaysTrackFinger ) {
390- mHandleAnimations .stop ();
391- mHandleDrawable .setAlpha (0.0f );
392- mHandleAnimations .add (Tweener .to (mHandleDrawable , INITIAL_SHOW_HANDLE_DURATION ,
393- "ease" , Ease .Cubic .easeIn ,
394- "alpha" , 1.0f ,
395- "onUpdate" , mUpdateListener ));
396- mHandleAnimations .start ();
397- }
399+ private void activateHandle (int duration , int delay , float finalAlpha ,
400+ AnimatorListener finishListener ) {
401+ mHandleAnimations .cancel ();
402+ mHandleAnimations .add (Tweener .to (mHandleDrawable , duration ,
403+ "ease" , Ease .Cubic .easeIn ,
404+ "delay" , delay ,
405+ "alpha" , finalAlpha ,
406+ "onUpdate" , mUpdateListener ,
407+ "onComplete" , finishListener ));
408+ mHandleAnimations .start ();
409+ }
410+
411+ private void deactivateHandle (int duration , int delay , float finalAlpha ,
412+ AnimatorListener finishListener ) {
413+ mHandleAnimations .cancel ();
414+ mHandleAnimations .add (Tweener .to (mHandleDrawable , duration ,
415+ "ease" , Ease .Quart .easeOut ,
416+ "delay" , delay ,
417+ "alpha" , finalAlpha ,
418+ "x" , 0 ,
419+ "y" , 0 ,
420+ "onUpdate" , mUpdateListener ,
421+ "onComplete" , finishListener ));
398422 }
399423
400424 /**
@@ -441,14 +465,6 @@ private void startChevronAnimation() {
441465 mChevronAnimations .start ();
442466 }
443467
444- private void stopChevronAnimation () {
445- mChevronAnimations .stop ();
446- }
447-
448- private void stopHandleAnimation () {
449- mHandleAnimations .stop ();
450- }
451-
452468 private void deactivateTargets () {
453469 final int count = mTargetDrawables .size ();
454470 for (int i = 0 ; i < count ; i ++) {
@@ -493,39 +509,33 @@ private void dispatchOnFinishFinalAnimation() {
493509
494510 private void doFinish () {
495511 final int activeTarget = mActiveTarget ;
496- boolean targetHit = activeTarget != -1 ;
497-
498- // Hide unselected targets
499- hideTargets (true );
512+ final boolean targetHit = activeTarget != -1 ;
500513
501- // Highlight the selected one
502- mHandleAnimations .cancel ();
503514 if (targetHit ) {
504- mHandleDrawable . setAlpha ( 0.0f );
505- mTargetDrawables . get ( activeTarget ). setState ( TargetDrawable . STATE_ACTIVE );
506- hideUnselected (activeTarget );
515+ if ( DEBUG ) Log . v ( TAG , "Finish with target hit = " + targetHit );
516+
517+ highlightSelected (activeTarget );
507518
508519 // Inform listener of any active targets. Typically only one will be active.
509- if ( DEBUG ) Log . v ( TAG , "Finish with target hit = " + targetHit );
520+ deactivateHandle ( RETURN_TO_HOME_DURATION , RETURN_TO_HOME_DELAY , 0.0f , mResetListener );
510521 dispatchTriggerEvent (activeTarget );
522+ } else {
523+ // Animate handle back to the center based on current state.
524+ deactivateHandle (HIDE_ANIMATION_DURATION , HIDE_ANIMATION_DELAY , 1.0f ,
525+ mResetListenerWithPing );
526+ hideTargets (true , false );
527+ mHandleAnimations .start ();
511528 }
512529
513- // Animate handle back to the center based on current state.
514- int delay = targetHit ? RETURN_TO_HOME_DELAY : 0 ;
515- int duration = RETURN_TO_HOME_DURATION ;
516- mHandleAnimations .add (Tweener .to (mHandleDrawable , duration ,
517- "ease" , Ease .Quart .easeOut ,
518- "delay" , delay ,
519- "alpha" , mAlwaysTrackFinger ? 0.0f : 1.0f ,
520- "x" , 0 ,
521- "y" , 0 ,
522- "onUpdate" , mUpdateListener ,
523- "onComplete" , (mDragging && !targetHit ) ? mResetListenerWithPing : mResetListener ));
524- mHandleAnimations .start ();
525-
526530 setGrabbedState (OnTriggerListener .NO_HANDLE );
527531 }
528532
533+ private void highlightSelected (int activeTarget ) {
534+ // Highlight the given target and fade others
535+ mTargetDrawables .get (activeTarget ).setState (TargetDrawable .STATE_ACTIVE );
536+ hideUnselected (activeTarget );
537+ }
538+
529539 private void hideUnselected (int active ) {
530540 for (int i = 0 ; i < mTargetDrawables .size (); i ++) {
531541 if (i != active ) {
@@ -535,16 +545,15 @@ private void hideUnselected(int active) {
535545 mOuterRing .setAlpha (0.0f );
536546 }
537547
538- private void hideTargets (boolean animate ) {
548+ private void hideTargets (boolean animate , boolean expanded ) {
539549 mTargetAnimations .cancel ();
540550 // Note: these animations should complete at the same time so that we can swap out
541551 // the target assets asynchronously from the setTargetResources() call.
542552 mAnimatingTargets = animate ;
543553 final int duration = animate ? HIDE_ANIMATION_DURATION : 0 ;
544554 final int delay = animate ? HIDE_ANIMATION_DELAY : 0 ;
545- final boolean targetSelected = mActiveTarget != -1 ;
546555
547- final float targetScale = targetSelected ? TARGET_SCALE_SELECTED : TARGET_SCALE_UNSELECTED ;
556+ final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED ;
548557 final int length = mTargetDrawables .size ();
549558 for (int i = 0 ; i < length ; i ++) {
550559 TargetDrawable target = mTargetDrawables .get (i );
@@ -558,7 +567,7 @@ private void hideTargets(boolean animate) {
558567 "onUpdate" , mUpdateListener ));
559568 }
560569
561- final float ringScaleTarget = targetSelected ? RING_SCALE_SELECTED : RING_SCALE_UNSELECTED ;
570+ final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED ;
562571 mTargetAnimations .add (Tweener .to (mOuterRing , duration ,
563572 "ease" , Ease .Cubic .easeOut ,
564573 "alpha" , 0.0f ,
@@ -580,8 +589,6 @@ private void showTargets(boolean animate) {
580589 for (int i = 0 ; i < length ; i ++) {
581590 TargetDrawable target = mTargetDrawables .get (i );
582591 target .setState (TargetDrawable .STATE_INACTIVE );
583- target .setScaleX (TARGET_SCALE_SELECTED );
584- target .setScaleY (TARGET_SCALE_SELECTED );
585592 mTargetAnimations .add (Tweener .to (target , duration ,
586593 "ease" , Ease .Cubic .easeOut ,
587594 "alpha" , 1.0f ,
@@ -732,17 +739,30 @@ public void ping() {
732739 * @param animate
733740 */
734741 public void reset (boolean animate ) {
735- stopChevronAnimation ();
736- stopHandleAnimation ();
742+ mChevronAnimations . stop ();
743+ mHandleAnimations . stop ();
737744 mTargetAnimations .stop ();
745+ startBackgroundAnimation (0 , 0.0f );
738746 hideChevrons ();
739- hideTargets (animate );
740- mHandleDrawable .setX (0 );
741- mHandleDrawable .setY (0 );
742- mHandleDrawable .setState (TargetDrawable .STATE_INACTIVE );
747+ hideTargets (animate , false );
748+ deactivateHandle (0 , 0 , 1.0f , null );
743749 Tweener .reset ();
744750 }
745751
752+ private void startBackgroundAnimation (int duration , float alpha ) {
753+ Drawable background = getBackground ();
754+ if (mAlwaysTrackFinger && background != null ) {
755+ if (mBackgroundAnimator != null ) {
756+ mBackgroundAnimator .animator .end ();
757+ }
758+ mBackgroundAnimator = Tweener .to (background , duration ,
759+ "ease" , Ease .Cubic .easeIn ,
760+ "alpha" , new int [] {0 , (int )(255.0f * alpha )},
761+ "delay" , SHOW_ANIMATION_DELAY );
762+ mBackgroundAnimator .animator .start ();
763+ }
764+ }
765+
746766 @ Override
747767 public boolean onTouchEvent (MotionEvent event ) {
748768 final int action = event .getAction ();
@@ -784,7 +804,10 @@ private void moveHandleTo(float x, float y, boolean animate) {
784804 }
785805
786806 private void handleDown (MotionEvent event ) {
787- if (!trySwitchToFirstTouchState (event .getX (), event .getY ())) {
807+ float eventX = event .getX ();
808+ float eventY = event .getY ();
809+ switchToState (STATE_START , eventX , eventY );
810+ if (!trySwitchToFirstTouchState (eventX , eventY )) {
788811 mDragging = false ;
789812 mTargetAnimations .cancel ();
790813 ping ();
@@ -830,7 +853,9 @@ private void handleMove(MotionEvent event) {
830853
831854 if (!mDragging ) {
832855 trySwitchToFirstTouchState (eventX , eventY );
833- } else {
856+ }
857+
858+ if (mDragging ) {
834859 if (singleTarget ) {
835860 // Snap to outer ring if there's only one target
836861 float snapRadius = mOuterRadius - mSnapMargin ;
@@ -865,17 +890,11 @@ private void handleMove(MotionEvent event) {
865890 if (activeTarget != -1 ) {
866891 switchToState (STATE_SNAP , x ,y );
867892 TargetDrawable target = targets .get (activeTarget );
868- float newX = singleTarget ? x : target .getX ();
869- float newY = singleTarget ? y : target .getY ();
893+ final float newX = singleTarget ? x : target .getX ();
894+ final float newY = singleTarget ? y : target .getY ();
870895 moveHandleTo (newX , newY , false );
871- mHandleAnimations .cancel ();
872- mHandleDrawable .setAlpha (0.0f );
873896 } else {
874897 switchToState (STATE_TRACKING , x , y );
875- if (mActiveTarget != -1 ) {
876- mHandleAnimations .cancel ();
877- mHandleDrawable .setAlpha (1.0f );
878- }
879898 moveHandleTo (x , y , false );
880899 }
881900
@@ -900,6 +919,9 @@ private void handleMove(MotionEvent event) {
900919 String targetContentDescription = getTargetDescription (activeTarget );
901920 announceText (targetContentDescription );
902921 }
922+ activateHandle (0 , 0 , 0.0f , null );
923+ } else {
924+ activateHandle (0 , 0 , 1.0f , null );
903925 }
904926 }
905927 mActiveTarget = activeTarget ;
@@ -1021,7 +1043,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
10211043
10221044 if (mInitialLayout ) {
10231045 hideChevrons ();
1024- hideTargets (false );
1046+ hideTargets (false , false );
10251047 moveHandleTo (0 , 0 , false );
10261048 mInitialLayout = false ;
10271049 }
0 commit comments