OSDN Git Service

Fix 6398209: Lots of improvements to gesture search from navbar
authorJim Miller <jaggies@google.com>
Wed, 16 May 2012 03:46:03 +0000 (20:46 -0700)
committerJim Miller <jaggies@google.com>
Wed, 16 May 2012 04:10:12 +0000 (21:10 -0700)
- Added ability to postpone animations until after window is shown
to ensure the animation is visible on slower devices.
- Fixed layout bug that prevented targets from being located on
outer ring.
- Fixed bug where some motion events were being ignored when handle
wasn't captured.
- Reduced temp object generation in several methods.
- Added containers to start all animations together and to facilitate
delayed start.
- Increased radius of outer ring on phones to closer match mocks.
- Decreased sensitivity of swipe up gesture on navbar so it's harder to false.

Change-Id: I084f840115aef6496a1f87202c4e42d9962c8c3e

core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
core/java/com/android/internal/widget/multiwaveview/Tweener.java
packages/SystemUI/res/drawable/navbar_search_outerring.xml
packages/SystemUI/res/values/dimens.xml
packages/SystemUI/src/com/android/systemui/SearchPanelView.java

index 60cd895..deb09e3 100644 (file)
@@ -79,18 +79,17 @@ public class MultiWaveView extends View {
     private static final int SHOW_ANIMATION_DURATION = 200;
     private static final int SHOW_ANIMATION_DELAY = 0;
     private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
-    private static final long RING_EXPAND_DURATION = 200;
     private static final float TARGET_INITIAL_POSITION_SCALE = 0.8f;
 
     private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut;
 
     private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
     private ArrayList<TargetDrawable> mChevronDrawables = new ArrayList<TargetDrawable>();
-    private ArrayList<Tweener> mChevronAnimations = new ArrayList<Tweener>();
-    private ArrayList<Tweener> mTargetAnimations = new ArrayList<Tweener>();
+    private AnimationBundle mChevronAnimations = new AnimationBundle();
+    private AnimationBundle mTargetAnimations = new AnimationBundle();
+    private AnimationBundle mHandleAnimations = new AnimationBundle();
     private ArrayList<String> mTargetDescriptions;
     private ArrayList<String> mDirectionDescriptions;
-    private Tweener mHandleAnimation;
     private OnTriggerListener mOnTriggerListener;
     private TargetDrawable mHandleDrawable;
     private TargetDrawable mOuterRing;
@@ -114,6 +113,33 @@ public class MultiWaveView extends View {
     private boolean mDragging;
     private int mNewTargetResources;
 
+    private class AnimationBundle extends ArrayList<Tweener> {
+        private static final long serialVersionUID = 0xA84D78726F127468L;
+        private boolean mSuspended;
+
+        public void start() {
+            if (mSuspended) return; // ignore attempts to start animations
+            final int count = size();
+            for (int i = 0; i < count; i++) {
+                Tweener anim = get(i);
+                anim.animator.start();
+            }
+        }
+
+        public void stop() {
+            final int count = size();
+            for (int i = 0; i < count; i++) {
+                Tweener anim = get(i);
+                anim.animator.end();
+            }
+            clear();
+        }
+
+        public void setSuspended(boolean suspend) {
+            mSuspended = suspend;
+        }
+    };
+
     private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
         public void onAnimationEnd(Animator animator) {
             switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
@@ -239,6 +265,7 @@ public class MultiWaveView extends View {
 
         a.recycle();
         setVibrateEnabled(mVibrationDuration > 0);
+        assignDefaultsIfNeeded();
     }
 
     private void dump() {
@@ -254,6 +281,21 @@ public class MultiWaveView extends View {
         Log.v(TAG, "VerticalOffset = " + mVerticalOffset);
     }
 
+    public void suspendAnimations() {
+        mChevronAnimations.setSuspended(true);
+        mTargetAnimations.setSuspended(true);
+        mHandleAnimations.setSuspended(true);
+    }
+
+    public void resumeAnimations() {
+        mChevronAnimations.setSuspended(false);
+        mTargetAnimations.setSuspended(false);
+        mHandleAnimations.setSuspended(false);
+        mChevronAnimations.start();
+        mTargetAnimations.start();
+        mHandleAnimations.start();
+    }
+
     @Override
     protected int getSuggestedMinimumWidth() {
         // View should be large enough to contain the background + handle and
@@ -340,6 +382,8 @@ public class MultiWaveView extends View {
 
         final int directionCount = mFeedbackCount > 0 ? mChevronDrawables.size()/mFeedbackCount : 0;
 
+        mChevronAnimations.stop();
+
         // Add an animation for all chevron drawables.  There are mFeedbackCount drawables
         // in each direction and directionCount directions.
         for (int direction = 0; direction < directionCount; direction++) {
@@ -367,24 +411,21 @@ public class MultiWaveView extends View {
                         "onUpdate", mUpdateListener));
             }
         }
+        mChevronAnimations.start();
     }
 
     private void stopChevronAnimation() {
-        for (Tweener anim : mChevronAnimations) {
-            anim.animator.end();
-        }
-        mChevronAnimations.clear();
+        mChevronAnimations.stop();
     }
 
     private void stopHandleAnimation() {
-        if (mHandleAnimation != null) {
-            mHandleAnimation.animator.end();
-            mHandleAnimation = null;
-        }
+        mHandleAnimations.stop();
     }
 
     private void deactivateTargets() {
-        for (TargetDrawable target : mTargetDrawables) {
+        final int count = mTargetDrawables.size();
+        for (int i = 0; i < count; i++) {
+            TargetDrawable target = mTargetDrawables.get(i);
             target.setState(TargetDrawable.STATE_INACTIVE);
         }
         mActiveTarget = -1;
@@ -445,14 +486,16 @@ public class MultiWaveView extends View {
         // Animate handle back to the center based on current state.
         int delay = targetHit ? RETURN_TO_HOME_DELAY : 0;
         int duration = targetHit ? 0 : RETURN_TO_HOME_DURATION;
-        mHandleAnimation = Tweener.to(mHandleDrawable, duration,
+        mHandleAnimations.stop();
+        mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
                 "ease", Ease.Quart.easeOut,
                 "delay", delay,
                 "alpha", 1.0f,
                 "x", 0,
                 "y", 0,
                 "onUpdate", mUpdateListener,
-                "onComplete", (mDragging && !targetHit) ? mResetListenerWithPing : mResetListener);
+                "onComplete", (mDragging && !targetHit) ? mResetListenerWithPing : mResetListener));
+        mHandleAnimations.start();
 
         setGrabbedState(OnTriggerListener.NO_HANDLE);
     }
@@ -467,9 +510,7 @@ public class MultiWaveView extends View {
     }
 
     private void hideTargets(boolean animate) {
-        if (mTargetAnimations.size() > 0) {
-            stopTargetAnimation();
-        }
+        mTargetAnimations.stop();
         // Note: these animations should complete at the same time so that we can swap out
         // the target assets asynchronously from the setTargetResources() call.
         mAnimatingTargets = animate;
@@ -497,12 +538,12 @@ public class MultiWaveView extends View {
                 "delay", delay,
                 "onUpdate", mUpdateListener,
                 "onComplete", mTargetUpdateListener));
+
+        mTargetAnimations.start();
     }
 
     private void showTargets(boolean animate) {
-        if (mTargetAnimations.size() > 0) {
-            stopTargetAnimation();
-        }
+        mTargetAnimations.stop();
         mAnimatingTargets = animate;
         final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
         final int length = mTargetDrawables.size();
@@ -521,7 +562,7 @@ public class MultiWaveView extends View {
         }
         mOuterRing.setScaleX(0.5f);
         mOuterRing.setScaleY(0.5f);
-        mTargetAnimations.add(Tweener.to(mOuterRing, animate ? RING_EXPAND_DURATION : 0,
+        mTargetAnimations.add(Tweener.to(mOuterRing, animate ? SHOW_ANIMATION_DURATION : 0,
                 "ease", Ease.Cubic.easeOut,
                 "alpha", 1.0f,
                 "scaleX", 1.0f,
@@ -529,13 +570,12 @@ public class MultiWaveView extends View {
                 "delay", delay,
                 "onUpdate", mUpdateListener,
                 "onComplete", mTargetUpdateListener));
+
+        mTargetAnimations.start();
     }
 
     private void stopTargetAnimation() {
-        for (Tweener anim : mTargetAnimations) {
-            anim.animator.end();
-        }
-        mTargetAnimations.clear();
+        mTargetAnimations.stop();
     }
 
     private void vibrate() {
@@ -658,7 +698,6 @@ public class MultiWaveView extends View {
      *
      */
     public void ping() {
-        stopChevronAnimation();
         startChevronAnimation();
     }
 
@@ -721,7 +760,7 @@ public class MultiWaveView extends View {
     }
 
     private void handleDown(MotionEvent event) {
-       if (!trySwitchToFirstTouchState(event)) {
+       if (!trySwitchToFirstTouchState(event.getX(), event.getY())) {
             mDragging = false;
             stopTargetAnimation();
             ping();
@@ -747,14 +786,11 @@ public class MultiWaveView extends View {
     }
 
     private void handleMove(MotionEvent event) {
-        if (!mDragging) {
-            trySwitchToFirstTouchState(event);
-            return;
-        }
-
         int activeTarget = -1;
         final int historySize = event.getHistorySize();
-        final boolean singleTarget = mTargetDrawables.size() == 1;
+        ArrayList<TargetDrawable> targets = mTargetDrawables;
+        int ntargets = targets.size();
+        final boolean singleTarget = ntargets == 1;
         float x = 0.0f;
         float y = 0.0f;
         for (int k = 0; k < historySize + 1; k++) {
@@ -768,25 +804,29 @@ public class MultiWaveView extends View {
             float limitX = tx * scale;
             float limitY = ty * scale;
 
-            if (singleTarget) {
-                // Snap to outer ring if there's only one target
-                float snapRadius = mOuterRadius - mSnapMargin;
-                if (touchRadius > snapRadius) {
-                    activeTarget = 0;
-                }
+            if (!mDragging) {
+                trySwitchToFirstTouchState(eventX, eventY);
             } else {
-                // If there's more than one target, snap to the closest one less than hitRadius away.
-                float best = Float.MAX_VALUE;
-                final float hitRadius2 = mHitRadius * mHitRadius;
-                for (int i = 0; i < mTargetDrawables.size(); i++) {
-                    // Snap to the first target in range
-                    TargetDrawable target = mTargetDrawables.get(i);
-                    float dx = limitX - target.getX();
-                    float dy = limitY - target.getY();
-                    float dist2 = dx*dx + dy*dy;
-                    if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) {
-                        activeTarget = i;
-                        best = dist2;
+                if (singleTarget) {
+                    // Snap to outer ring if there's only one target
+                    float snapRadius = mOuterRadius - mSnapMargin;
+                    if (touchRadius > snapRadius) {
+                        activeTarget = 0;
+                    }
+                } else {
+                    // For more than one target, snap to the closest one less than hitRadius away.
+                    float best = Float.MAX_VALUE;
+                    final float hitRadius2 = mHitRadius * mHitRadius;
+                    for (int i = 0; i < ntargets; i++) {
+                        // Snap to the first target in range
+                        TargetDrawable target = targets.get(i);
+                        float dx = limitX - target.getX();
+                        float dy = limitY - target.getY();
+                        float dist2 = dx*dx + dy*dy;
+                        if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) {
+                            activeTarget = i;
+                            best = dist2;
+                        }
                     }
                 }
             }
@@ -794,9 +834,13 @@ public class MultiWaveView extends View {
             y = limitY;
         }
 
+        if (!mDragging) {
+            return;
+        }
+
         if (activeTarget != -1) {
             switchToState(STATE_SNAP, x,y);
-            TargetDrawable target = mTargetDrawables.get(activeTarget);
+            TargetDrawable target = targets.get(activeTarget);
             float newX = singleTarget ? x : target.getX();
             float newY = singleTarget ? y : target.getY();
             moveHandleTo(newX, newY, false);
@@ -812,7 +856,7 @@ public class MultiWaveView extends View {
         if (mActiveTarget != activeTarget) {
             // Defocus the old target
             if (mActiveTarget != -1) {
-                TargetDrawable target = mTargetDrawables.get(mActiveTarget);
+                TargetDrawable target = targets.get(mActiveTarget);
                 if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
                     target.setState(TargetDrawable.STATE_INACTIVE);
                     mHandleDrawable.setAlpha(1.0f);
@@ -820,7 +864,7 @@ public class MultiWaveView extends View {
             }
             // Focus the new target
             if (activeTarget != -1) {
-                TargetDrawable target = mTargetDrawables.get(activeTarget);
+                TargetDrawable target = targets.get(activeTarget);
                 if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
                     target.setState(TargetDrawable.STATE_FOCUSED);
                     mHandleDrawable.setAlpha(0.0f);
@@ -877,9 +921,7 @@ public class MultiWaveView extends View {
         }
     }
 
-    private boolean trySwitchToFirstTouchState(MotionEvent event) {
-        final float x = event.getX();
-        final float y = event.getY();
+    private boolean trySwitchToFirstTouchState(float x, float y) {
         final float tx = x - mWaveCenterX;
         final float ty = y - mWaveCenterY;
         if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledTapRadiusSquared()) {
@@ -892,9 +934,9 @@ public class MultiWaveView extends View {
         return false;
     }
 
-    private void assignDefaultsIfNeeded(float centerX, float centerY) {
+    private void assignDefaultsIfNeeded() {
         if (mOuterRadius == 0.0f) {
-            mOuterRadius = 0.5f*(float) Math.hypot(centerX, centerY);
+            mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
         }
         if (mHitRadius == 0.0f) {
             // Use the radius of inscribed circle of the first target.
@@ -941,6 +983,7 @@ public class MultiWaveView extends View {
         super.onLayout(changed, left, top, right, bottom);
         final int width = right - left;
         final int height = bottom - top;
+
         // Target placement width/height. This puts the targets on the greater of the ring
         // width or the specified outer radius.
         final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
@@ -950,8 +993,6 @@ public class MultiWaveView extends View {
         float newWaveCenterY = mVerticalOffset + mVerticalInset
                 + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
 
-        assignDefaultsIfNeeded(newWaveCenterX, newWaveCenterY);
-
         if (mInitialLayout) {
             hideChevrons();
             hideTargets(false);
@@ -976,9 +1017,12 @@ public class MultiWaveView extends View {
 
     private void updateTargetPositions(float centerX, float centerY) {
         // Reposition the target drawables if the view changed.
-        for (int i = 0; i < mTargetDrawables.size(); i++) {
-            final TargetDrawable targetIcon = mTargetDrawables.get(i);
-            double angle = -2.0f * Math.PI * i / mTargetDrawables.size();
+        ArrayList<TargetDrawable> targets = mTargetDrawables;
+        final int size = targets.size();
+        final float alpha = (float) (-2.0f * Math.PI / size);
+        for (int i = 0; i < size; i++) {
+            final TargetDrawable targetIcon = targets.get(i);
+            final float angle = alpha * i;
             targetIcon.setPositionX(centerX);
             targetIcon.setPositionY(centerY);
             targetIcon.setX(mOuterRadius * (float) Math.cos(angle));
@@ -987,7 +1031,10 @@ public class MultiWaveView extends View {
     }
 
     private void updateChevronPositions(float centerX, float centerY) {
-        for (TargetDrawable target : mChevronDrawables) {
+        ArrayList<TargetDrawable> chevrons = mChevronDrawables;
+        final int size = chevrons.size();
+        for (int i = 0; i < size; i++) {
+            TargetDrawable target = chevrons.get(i);
             if (target != null) {
                 target.setPositionX(centerX);
                 target.setPositionY(centerY);
@@ -996,7 +1043,10 @@ public class MultiWaveView extends View {
     }
 
     private void hideChevrons() {
-        for (TargetDrawable chevron : mChevronDrawables) {
+        ArrayList<TargetDrawable> chevrons = mChevronDrawables;
+        final int size = chevrons.size();
+        for (int i = 0; i < size; i++) {
+            TargetDrawable chevron = chevrons.get(i);
             if (chevron != null) {
                 chevron.setAlpha(0.0f);
             }
@@ -1006,14 +1056,18 @@ public class MultiWaveView extends View {
     @Override
     protected void onDraw(Canvas canvas) {
         mOuterRing.draw(canvas);
-        for (TargetDrawable target : mTargetDrawables) {
+        final int ntargets = mTargetDrawables.size();
+        for (int i = 0; i < ntargets; i++) {
+            TargetDrawable target = mTargetDrawables.get(i);
             if (target != null) {
                 target.draw(canvas);
             }
         }
-        for (TargetDrawable target : mChevronDrawables) {
-            if (target != null) {
-                target.draw(canvas);
+        final int nchevrons = mChevronDrawables.size();
+        for (int i = 0; i < nchevrons; i++) {
+            TargetDrawable chevron = mChevronDrawables.get(i);
+            if (chevron != null) {
+                chevron.draw(canvas);
             }
         }
         mHandleDrawable.draw(canvas);
index bc8a62f..1d502ba 100644 (file)
@@ -122,7 +122,6 @@ class Tweener {
             anim.addListener(listener);
         }
         anim.addListener(mCleanupListener);
-        anim.start();
 
         return tween;
     }
index 37b6c1c..0dd081d 100644 (file)
@@ -19,5 +19,5 @@
     <size android:height="@dimen/navbar_search_outerring_diameter"
         android:width="@dimen/navbar_search_outerring_diameter" />
     <solid android:color="#00000000" />
-    <stroke android:color="#1affffff" android:width="2dp" />
+    <stroke android:color="#40ffffff" android:width="2dp" />
 </shape>
\ No newline at end of file
index f548166..c29f71b 100644 (file)
     <dimen name="navbar_search_hit_radius">60dip</dimen>
 
     <!-- Diameter of outer shape drawable shown in navbar search-->
-    <dimen name="navbar_search_outerring_diameter">270dp</dimen>
+    <dimen name="navbar_search_outerring_diameter">340dp</dimen>
 
     <!-- Threshold for swipe-up gesture to activate search dialog -->
-    <dimen name="navbar_search_up_threshhold">20dip</dimen>
+    <dimen name="navbar_search_up_threshhold">40dip</dimen>
 
     <!-- Height of search panel including navigation bar height -->
     <dimen name="navbar_search_panel_height">230dip</dimen>
index 57f15a8..05a3bec 100644 (file)
@@ -23,13 +23,14 @@ import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.speech.RecognizerIntent;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.widget.FrameLayout;
 
 import com.android.internal.widget.multiwaveview.MultiWaveView;
@@ -146,6 +147,14 @@ public class SearchPanelView extends FrameLayout implements
         }
     }
 
+    private OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
+        public boolean onPreDraw() {
+            getViewTreeObserver().removeOnPreDrawListener(this);
+            mMultiWaveView.resumeAnimations();
+            return false;
+        }
+    };
+
     public void show(final boolean show, boolean animate) {
         if (animate) {
             if (mShowing != show) {
@@ -156,16 +165,20 @@ public class SearchPanelView extends FrameLayout implements
             mShowing = show;
             onAnimationEnd(null);
         }
-        postDelayed(new Runnable() {
-            public void run() {
-                setVisibility(show ? View.VISIBLE : View.INVISIBLE);
-                if (show) {
-                    setFocusable(true);
-                    setFocusableInTouchMode(true);
-                    requestFocus();
-                }
+        if (show) {
+            if (getVisibility() != View.VISIBLE) {
+                setVisibility(View.VISIBLE);
+                // Don't start the animation until we've created the layer, which is done
+                // right before we are drawn
+                mMultiWaveView.suspendAnimations();
+                getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
             }
-        }, show ? 0 : 100);
+            setFocusable(true);
+            setFocusableInTouchMode(true);
+            requestFocus();
+        } else {
+            setVisibility(View.INVISIBLE);
+        }
     }
 
     public void hide(boolean animate) {