Skip to content

Commit a073e57

Browse files
author
Jim Miller
committed
Fix 6398209: General animation improvements for swipe to search
This cleans up the animation for swipe to search from the navbar. In particular: 1. Wait for initial animation to finish if gesture was too quick. 2. Better fade animation 3. Hide background and fade in when ring is selected 4. Smoother target and outer ring animation when switching between states. Change-Id: I401197760cf9f06b6ff3e1cdb80bee86a03ef276
1 parent a66c75a commit a073e57

File tree

7 files changed

+204
-242
lines changed

7 files changed

+204
-242
lines changed

core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java

Lines changed: 100 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.animation.Animator;
2020
import android.animation.Animator.AnimatorListener;
2121
import android.animation.AnimatorListenerAdapter;
22+
import android.animation.ObjectAnimator;
2223
import android.animation.TimeInterpolator;
2324
import android.animation.ValueAnimator;
2425
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -27,6 +28,7 @@
2728
import android.content.res.TypedArray;
2829
import android.graphics.Canvas;
2930
import android.graphics.RectF;
31+
import android.graphics.drawable.Drawable;
3032
import android.os.Vibrator;
3133
import android.text.TextUtils;
3234
import 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
}

core/java/com/android/internal/widget/multiwaveview/Tweener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ public static Tweener to(Object object, long duration, Object... vars) {
8383
} else if (value instanceof float[]) {
8484
props.add(PropertyValuesHolder.ofFloat(key,
8585
((float[])value)[0], ((float[])value)[1]));
86+
} else if (value instanceof int[]) {
87+
props.add(PropertyValuesHolder.ofInt(key,
88+
((int[])value)[0], ((int[])value)[1]));
8689
} else if (value instanceof Number) {
8790
float floatValue = ((Number)value).floatValue();
8891
props.add(PropertyValuesHolder.ofFloat(key, floatValue));

0 commit comments

Comments
 (0)