Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a073e570 authored by Jim Miller's avatar Jim Miller
Browse files

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
parent a66c75a8
Loading
Loading
Loading
Loading
+100 −78
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.internal.widget.multiwaveview;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -27,6 +28,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -52,10 +54,11 @@ public class MultiWaveView extends View {

    // Wave state machine
    private static final int STATE_IDLE = 0;
    private static final int STATE_FIRST_TOUCH = 1;
    private static final int STATE_TRACKING = 2;
    private static final int STATE_SNAP = 3;
    private static final int STATE_FINISH = 4;
    private static final int STATE_START = 1;
    private static final int STATE_FIRST_TOUCH = 2;
    private static final int STATE_TRACKING = 3;
    private static final int STATE_SNAP = 4;
    private static final int STATE_FINISH = 5;

    // Animation properties.
    private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
@@ -74,17 +77,18 @@ public class MultiWaveView extends View {
    private static final int CHEVRON_INCREMENTAL_DELAY = 160;
    private static final int CHEVRON_ANIMATION_DURATION = 850;
    private static final int RETURN_TO_HOME_DELAY = 1200;
    private static final int RETURN_TO_HOME_DURATION = 300;
    private static final int RETURN_TO_HOME_DURATION = 200;
    private static final int HIDE_ANIMATION_DELAY = 200;
    private static final int HIDE_ANIMATION_DURATION = 200;
    private static final int SHOW_ANIMATION_DURATION = 200;
    private static final int SHOW_ANIMATION_DELAY = 50;
    private static final int INITIAL_SHOW_HANDLE_DURATION = 200;

    private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
    private static final float TARGET_SCALE_SELECTED = 0.8f;
    private static final long INITIAL_SHOW_HANDLE_DURATION = 200;
    private static final float TARGET_SCALE_UNSELECTED = 1.0f;
    private static final float RING_SCALE_UNSELECTED = 0.5f;
    private static final float RING_SCALE_SELECTED = 1.5f;
    private static final float TARGET_SCALE_EXPANDED = 1.0f;
    private static final float TARGET_SCALE_COLLAPSED = 0.8f;
    private static final float RING_SCALE_EXPANDED = 1.0f;
    private static final float RING_SCALE_COLLAPSED = 0.5f;

    private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut;

@@ -182,7 +186,7 @@ public class MultiWaveView extends View {
            if (mNewTargetResources != 0) {
                internalSetTargetResources(mNewTargetResources);
                mNewTargetResources = 0;
                hideTargets(false);
                hideTargets(false, false);
            }
            mAnimatingTargets = false;
        }
@@ -195,6 +199,7 @@ public class MultiWaveView extends View {
    private int mVerticalInset;
    private int mGravity = Gravity.TOP;
    private boolean mInitialLayout = true;
    private Tweener mBackgroundAnimator;

    public MultiWaveView(Context context) {
        this(context, null);
@@ -358,14 +363,21 @@ public class MultiWaveView extends View {
        switch (state) {
            case STATE_IDLE:
                deactivateTargets();
                hideTargets(true, false);
                startBackgroundAnimation(0, 0.0f);
                mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
                break;

            case STATE_START:
                deactivateHandle(0, 0, 1.0f, null);
                startBackgroundAnimation(0, 0.0f);
                break;

            case STATE_FIRST_TOUCH:
                stopHandleAnimation();
                deactivateTargets();
                showTargets(true);
                activateHandle();
                mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
                startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
                setGrabbedState(OnTriggerListener.CENTER_HANDLE);
                if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                    announceTargets();
@@ -384,17 +396,29 @@ public class MultiWaveView extends View {
        }
    }

    private void activateHandle() {
        mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
        if (mAlwaysTrackFinger) {
            mHandleAnimations.stop();
            mHandleDrawable.setAlpha(0.0f);
            mHandleAnimations.add(Tweener.to(mHandleDrawable, INITIAL_SHOW_HANDLE_DURATION,
    private void activateHandle(int duration, int delay, float finalAlpha,
            AnimatorListener finishListener) {
        mHandleAnimations.cancel();
        mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
                "ease", Ease.Cubic.easeIn,
                    "alpha", 1.0f,
                    "onUpdate", mUpdateListener));
                "delay", delay,
                "alpha", finalAlpha,
                "onUpdate", mUpdateListener,
                "onComplete", finishListener));
        mHandleAnimations.start();
    }

    private void deactivateHandle(int duration, int delay, float finalAlpha,
            AnimatorListener finishListener) {
        mHandleAnimations.cancel();
        mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
            "ease", Ease.Quart.easeOut,
            "delay", delay,
            "alpha", finalAlpha,
            "x", 0,
            "y", 0,
            "onUpdate", mUpdateListener,
            "onComplete", finishListener));
    }

    /**
@@ -441,14 +465,6 @@ public class MultiWaveView extends View {
        mChevronAnimations.start();
    }

    private void stopChevronAnimation() {
        mChevronAnimations.stop();
    }

    private void stopHandleAnimation() {
        mHandleAnimations.stop();
    }

    private void deactivateTargets() {
        final int count = mTargetDrawables.size();
        for (int i = 0; i < count; i++) {
@@ -493,39 +509,33 @@ public class MultiWaveView extends View {

    private void doFinish() {
        final int activeTarget = mActiveTarget;
        boolean targetHit =  activeTarget != -1;
        final boolean targetHit =  activeTarget != -1;

        // Hide unselected targets
        hideTargets(true);

        // Highlight the selected one
        mHandleAnimations.cancel();
        if (targetHit) {
            mHandleDrawable.setAlpha(0.0f);
            mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
            hideUnselected(activeTarget);
            if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);

            highlightSelected(activeTarget);

            // Inform listener of any active targets.  Typically only one will be active.
            if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
            deactivateHandle(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
            dispatchTriggerEvent(activeTarget);
        }

        } else {
            // Animate handle back to the center based on current state.
        int delay = targetHit ? RETURN_TO_HOME_DELAY : 0;
        int duration = RETURN_TO_HOME_DURATION;
        mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
                "ease", Ease.Quart.easeOut,
                "delay", delay,
                "alpha", mAlwaysTrackFinger ? 0.0f : 1.0f,
                "x", 0,
                "y", 0,
                "onUpdate", mUpdateListener,
                "onComplete", (mDragging && !targetHit) ? mResetListenerWithPing : mResetListener));
            deactivateHandle(HIDE_ANIMATION_DURATION, HIDE_ANIMATION_DELAY, 1.0f,
                    mResetListenerWithPing);
            hideTargets(true, false);
            mHandleAnimations.start();
        }

        setGrabbedState(OnTriggerListener.NO_HANDLE);
    }

    private void highlightSelected(int activeTarget) {
        // Highlight the given target and fade others
        mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
        hideUnselected(activeTarget);
    }

    private void hideUnselected(int active) {
        for (int i = 0; i < mTargetDrawables.size(); i++) {
            if (i != active) {
@@ -535,16 +545,15 @@ public class MultiWaveView extends View {
        mOuterRing.setAlpha(0.0f);
    }

    private void hideTargets(boolean animate) {
    private void hideTargets(boolean animate, boolean expanded) {
        mTargetAnimations.cancel();
        // 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;
        final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
        final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
        final boolean targetSelected = mActiveTarget != -1;

        final float targetScale = targetSelected ? TARGET_SCALE_SELECTED : TARGET_SCALE_UNSELECTED;
        final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
        final int length = mTargetDrawables.size();
        for (int i = 0; i < length; i++) {
            TargetDrawable target = mTargetDrawables.get(i);
@@ -558,7 +567,7 @@ public class MultiWaveView extends View {
                    "onUpdate", mUpdateListener));
        }

        final float ringScaleTarget = targetSelected ? RING_SCALE_SELECTED : RING_SCALE_UNSELECTED;
        final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
        mTargetAnimations.add(Tweener.to(mOuterRing, duration,
                "ease", Ease.Cubic.easeOut,
                "alpha", 0.0f,
@@ -580,8 +589,6 @@ public class MultiWaveView extends View {
        for (int i = 0; i < length; i++) {
            TargetDrawable target = mTargetDrawables.get(i);
            target.setState(TargetDrawable.STATE_INACTIVE);
            target.setScaleX(TARGET_SCALE_SELECTED);
            target.setScaleY(TARGET_SCALE_SELECTED);
            mTargetAnimations.add(Tweener.to(target, duration,
                    "ease", Ease.Cubic.easeOut,
                    "alpha", 1.0f,
@@ -732,17 +739,30 @@ public class MultiWaveView extends View {
     * @param animate
     */
    public void reset(boolean animate) {
        stopChevronAnimation();
        stopHandleAnimation();
        mChevronAnimations.stop();
        mHandleAnimations.stop();
        mTargetAnimations.stop();
        startBackgroundAnimation(0, 0.0f);
        hideChevrons();
        hideTargets(animate);
        mHandleDrawable.setX(0);
        mHandleDrawable.setY(0);
        mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
        hideTargets(animate, false);
        deactivateHandle(0, 0, 1.0f, null);
        Tweener.reset();
    }

    private void startBackgroundAnimation(int duration, float alpha) {
        Drawable background = getBackground();
        if (mAlwaysTrackFinger && background != null) {
            if (mBackgroundAnimator != null) {
                mBackgroundAnimator.animator.end();
            }
            mBackgroundAnimator = Tweener.to(background, duration,
                    "ease", Ease.Cubic.easeIn,
                    "alpha", new int[] {0, (int)(255.0f * alpha)},
                    "delay", SHOW_ANIMATION_DELAY);
            mBackgroundAnimator.animator.start();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
@@ -784,7 +804,10 @@ public class MultiWaveView extends View {
    }

    private void handleDown(MotionEvent event) {
        if (!trySwitchToFirstTouchState(event.getX(), event.getY())) {
        float eventX = event.getX();
        float eventY = event.getY();
        switchToState(STATE_START, eventX, eventY);
        if (!trySwitchToFirstTouchState(eventX, eventY)) {
            mDragging = false;
            mTargetAnimations.cancel();
            ping();
@@ -830,7 +853,9 @@ public class MultiWaveView extends View {

            if (!mDragging) {
                trySwitchToFirstTouchState(eventX, eventY);
            } else {
            }

            if (mDragging) {
                if (singleTarget) {
                    // Snap to outer ring if there's only one target
                    float snapRadius = mOuterRadius - mSnapMargin;
@@ -865,17 +890,11 @@ public class MultiWaveView extends View {
        if (activeTarget != -1) {
            switchToState(STATE_SNAP, x,y);
            TargetDrawable target = targets.get(activeTarget);
            float newX = singleTarget ? x : target.getX();
            float newY = singleTarget ? y : target.getY();
            final float newX = singleTarget ? x : target.getX();
            final float newY = singleTarget ? y : target.getY();
            moveHandleTo(newX, newY, false);
            mHandleAnimations.cancel();
            mHandleDrawable.setAlpha(0.0f);
        } else {
            switchToState(STATE_TRACKING, x, y);
            if (mActiveTarget != -1) {
                mHandleAnimations.cancel();
                mHandleDrawable.setAlpha(1.0f);
            }
            moveHandleTo(x, y, false);
        }

@@ -900,6 +919,9 @@ public class MultiWaveView extends View {
                    String targetContentDescription = getTargetDescription(activeTarget);
                    announceText(targetContentDescription);
                }
                activateHandle(0, 0, 0.0f, null);
            } else {
                activateHandle(0, 0, 1.0f, null);
            }
        }
        mActiveTarget = activeTarget;
@@ -1021,7 +1043,7 @@ public class MultiWaveView extends View {

        if (mInitialLayout) {
            hideChevrons();
            hideTargets(false);
            hideTargets(false, false);
            moveHandleTo(0, 0, false);
            mInitialLayout = false;
        }
+3 −0
Original line number Diff line number Diff line
@@ -83,6 +83,9 @@ class Tweener {
            } else if (value instanceof float[]) {
                props.add(PropertyValuesHolder.ofFloat(key,
                        ((float[])value)[0], ((float[])value)[1]));
            } else if (value instanceof int[]) {
                props.add(PropertyValuesHolder.ofInt(key,
                        ((int[])value)[0], ((int[])value)[1]));
            } else if (value instanceof Number) {
                float floatValue = ((Number)value).floatValue();
                props.add(PropertyValuesHolder.ofFloat(key, floatValue));
+21 −48
Original line number Diff line number Diff line
@@ -22,39 +22,16 @@
    xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/search_panel_container"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:paddingBottom="0dip">

    <RelativeLayout
        android:id="@+id/search_bg_protect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <RelativeLayout
            android:id="@+id/search_panel_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true">

            <View
                android:layout_width="0dip"
                android:layout_height="0dip"
                android:layout_alignTop="@id/multi_wave_view"
                android:layout_alignLeft="@id/multi_wave_view"
                android:layout_alignRight="@id/multi_wave_view"
                android:layout_alignBottom="@id/multi_wave_view"
                android:layout_marginBottom="@dimen/navigation_bar_size"
                android:background="@drawable/navbar_search_bg_scrim"/>
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <com.android.internal.widget.multiwaveview.MultiWaveView
        android:id="@+id/multi_wave_view"
                android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/navbar_search_panel_height"
                android:layout_alignParentBottom="true"
                android:gravity="top"
        android:layout_gravity="center_horizontal|bottom"
        android:gravity="center_horizontal|top"
        android:background="@drawable/navbar_search_bg_scrim"

        prvandroid:targetDrawables="@array/navbar_search_targets"
        prvandroid:targetDescriptions="@array/navbar_search_target_descriptions"
@@ -67,8 +44,4 @@
        prvandroid:vibrationDuration="@integer/config_vibration_duration"
        prvandroid:alwaysTrackFinger="true"/>

        </RelativeLayout>

    </RelativeLayout>

</com.android.systemui.SearchPanelView>
+22 −48
Original line number Diff line number Diff line
@@ -22,39 +22,17 @@
    xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/search_panel_container"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:paddingBottom="0dip">

    <RelativeLayout
        android:id="@+id/search_bg_protect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <RelativeLayout
            android:id="@+id/search_panel_container"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/navbar_search_panel_height"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="-120dip">

            <View
                android:layout_width="0dip"
                android:layout_height="0dip"
                android:layout_alignTop="@id/multi_wave_view"
                android:layout_alignLeft="@id/multi_wave_view"
                android:layout_alignRight="@id/multi_wave_view"
                android:layout_alignBottom="@id/multi_wave_view"
                android:layout_marginBottom="@dimen/navigation_bar_size"
                android:background="@drawable/navbar_search_bg_scrim"/>
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <com.android.internal.widget.multiwaveview.MultiWaveView
        android:id="@+id/multi_wave_view"
                android:orientation="horizontal"
        android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentBottom="true"
        android:layout_height="@dimen/navbar_search_panel_height"
        android:layout_gravity="left|bottom"
        android:gravity="top|right"
        android:layout_marginLeft="-150dip"
        android:background="@drawable/navbar_search_bg_scrim"

        prvandroid:targetDrawables="@array/navbar_search_targets"
        prvandroid:targetDescriptions="@array/navbar_search_target_descriptions"
@@ -67,8 +45,4 @@
        prvandroid:vibrationDuration="@integer/config_vibration_duration"
        prvandroid:alwaysTrackFinger="true"/>

        </RelativeLayout>

    </RelativeLayout>

</com.android.systemui.SearchPanelView>
+25 −45
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
@@ -38,7 +37,6 @@ import android.widget.FrameLayout;

import com.android.internal.widget.multiwaveview.MultiWaveView;
import com.android.internal.widget.multiwaveview.MultiWaveView.OnTriggerListener;
import com.android.server.am.ActivityManagerService;
import com.android.systemui.R;
import com.android.systemui.recent.StatusBarTouchProxy;
import com.android.systemui.statusbar.BaseStatusBar;
@@ -47,7 +45,8 @@ import com.android.systemui.statusbar.tablet.StatusBarPanel;
import com.android.systemui.statusbar.tablet.TabletStatusBar;

public class SearchPanelView extends FrameLayout implements
        StatusBarPanel, Animator.AnimatorListener {
        StatusBarPanel {
    private static final int SEARCH_PANEL_HOLD_DURATION = 500;
    static final String TAG = "SearchPanelView";
    static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
    private Context mContext;
@@ -123,7 +122,7 @@ public class SearchPanelView extends FrameLayout implements
    final MultiWaveView.OnTriggerListener mMultiWaveViewListener
            = new MultiWaveView.OnTriggerListener() {

        private int mTarget = -1;
        private boolean mWaitingForLaunch;

        public void onGrabbed(View v, int handle) {
        }
@@ -132,26 +131,28 @@ public class SearchPanelView extends FrameLayout implements
        }

        public void onGrabbedStateChange(View v, int handle) {
            if (mTarget == -1 && OnTriggerListener.NO_HANDLE == handle) {
            if (!mWaitingForLaunch && OnTriggerListener.NO_HANDLE == handle) {
                mBar.hideSearchPanel();
            }
        }

        public void onTrigger(View v, int target) {
            mTarget = target;
        }

        public void onFinishFinalAnimation() {
            if (mTarget != -1) {
                final int resId = mMultiWaveView.getResourceIdForTarget(mTarget);
                mTarget = -1; // a safety to make sure we never launch w/o prior call to onTrigger
        public void onTrigger(View v, final int target) {
            final int resId = mMultiWaveView.getResourceIdForTarget(target);
            switch (resId) {
                case com.android.internal.R.drawable.ic_lockscreen_search:
                    mWaitingForLaunch = true;
                    startAssistActivity();
                    postDelayed(new Runnable() {
                        public void run() {
                            mWaitingForLaunch = false;
                            mBar.hideSearchPanel();
                        }
                    }, SEARCH_PANEL_HOLD_DURATION);
                break;
            }
                mBar.hideSearchPanel();
        }

        public void onFinishFinalAnimation() {
        }
    };

@@ -194,15 +195,11 @@ public class SearchPanelView extends FrameLayout implements
    };

    public void show(final boolean show, boolean animate) {
        if (animate) {
            if (mShowing != show) {
                mShowing = show;
                // TODO: start animating ring
        if (!show) {
            final LayoutTransition transitioner = animate ? createLayoutTransitioner() : null;
            ((ViewGroup)mSearchTargetsContainer).setLayoutTransition(transitioner);
        }
        } else {
        mShowing = show;
            onAnimationEnd(null);
        }
        if (show) {
            if (getVisibility() != View.VISIBLE) {
                setVisibility(View.VISIBLE);
@@ -228,25 +225,6 @@ public class SearchPanelView extends FrameLayout implements
        }
    }

    public void onAnimationCancel(Animator animation) {
    }

    public void onAnimationEnd(Animator animation) {
        if (mShowing) {
            final LayoutTransition transitioner = new LayoutTransition();
            ((ViewGroup)mSearchTargetsContainer).setLayoutTransition(transitioner);
            createCustomAnimations(transitioner);
        } else {
            ((ViewGroup)mSearchTargetsContainer).setLayoutTransition(null);
        }
    }

    public void onAnimationRepeat(Animator animation) {
    }

    public void onAnimationStart(Animator animation) {
    }

    /**
     * We need to be aligned at the bottom.  LinearLayout can't do this, so instead,
     * let LinearLayout do all the hard work, and then shift everything down to the bottom.
@@ -293,9 +271,11 @@ public class SearchPanelView extends FrameLayout implements
        }
    }

    private void createCustomAnimations(LayoutTransition transitioner) {
    private LayoutTransition createLayoutTransitioner() {
        LayoutTransition transitioner = new LayoutTransition();
        transitioner.setDuration(200);
        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
        return transitioner;
    }
}
Loading