Merge "Changed the swipe up search affordance" into lmp-dev

This commit is contained in:
Selim Cinek
2014-09-15 17:18:39 +00:00
committed by Android (Google) Code Review
14 changed files with 702 additions and 215 deletions

View File

@ -17,16 +17,8 @@
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" android:zAdjustment="top">
<alpha android:fromAlpha="0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@android:interpolator/decelerate_cubic"
android:duration="300"/>
<translate android:fromYDelta="100%" android:toYDelta="0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@android:interpolator/decelerate_cubic"
android:duration="300" />
</set>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@android:interpolator/decelerate_cubic"
android:duration="300"/>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2014 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/search_panel_card_color" />
<corners android:radius="@dimen/notification_material_rounded_rect_radius" />
</shape>

View File

@ -30,17 +30,14 @@
android:id="@+id/search_panel_scrim"
android:background="@drawable/search_panel_scrim" />
<FrameLayout
style="@style/SearchPanelCard"
android:id="@+id/search_panel_card"
android:background="@drawable/search_panel_card_bg"
android:elevation="12dp">
<com.android.systemui.SearchPanelCircleView
style="@style/SearchPanelCircle"
android:id="@+id/search_panel_circle">
<ImageView
style="@style/SearchPanelLogo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_logo" />
</FrameLayout>
</com.android.systemui.SearchPanelCircleView>
</com.android.systemui.SearchPanelView>

View File

@ -19,18 +19,6 @@
<item name="android:layout_width">360dp</item>
</style>
<style name="SearchPanelCard">
<item name="android:layout_width">@dimen/search_panel_card_height</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:layout_marginBottom">16dp</item>
<item name="android:layout_gravity">right</item>
</style>
<style name="SearchPanelLogo">
<item name="android:layout_gravity">top|left</item>
</style>
<style name="SearchPanelScrim">
<item name="android:layout_width">@dimen/search_panel_scrim_height</item>
<item name="android:layout_height">match_parent</item>

View File

@ -19,16 +19,6 @@
<item name="android:layout_width">480dp</item>
</style>
<style name="SearchPanelCard">
<item name="android:layout_width">550dp</item>
<item name="android:layout_height">@dimen/search_panel_card_height</item>
<item name="android:layout_gravity">center_horizontal|bottom</item>
</style>
<style name="SearchPanelLogo">
<item name="android:layout_gravity">top|center_horizontal</item>
</style>
<style name="SearchPanelScrim">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/search_panel_scrim_height</item>

View File

@ -109,7 +109,8 @@
<color name="notification_guts_text_color">#b2FFFFFF</color>
<color name="notification_guts_btn_color">#FFFFFFFF</color>
<color name="search_panel_card_color">#ffffff</color>
<color name="search_panel_circle_color">#ffffff</color>
<color name="search_panel_ripple_color">#ffbbbbbb</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
<color name="doze_small_icon_background_color">#ff434343</color>

View File

@ -434,16 +434,21 @@
from Keyguard. -->
<dimen name="go_to_full_shade_appearing_translation">200dp</dimen>
<!-- The height of the search panel card. -->
<dimen name="search_panel_card_height">300dp</dimen>
<!-- The diameter of the search panel circle. -->
<dimen name="search_panel_circle_size">88dp</dimen>
<!-- The height of the scrim behind the search panel card. -->
<!-- The margin to the edge of the screen from where the circle starts to appear -->
<dimen name="search_panel_circle_base_margin">80dp</dimen>
<!-- The amount the circle translates when appearing -->
<dimen name="search_panel_circle_travel_distance">80dp</dimen>
<!-- The elevation of the search panel circle -->
<dimen name="search_panel_circle_elevation">12dp</dimen>
<!-- The height of the scrim behind the search panel circle. -->
<dimen name="search_panel_scrim_height">250dp</dimen>
<!-- How much from the bottom of the screen the card should peek in when activating the search
panel -->
<dimen name="search_card_peek_height">100dp</dimen>
<!-- How far the user needs to drag up to invoke search. -->
<dimen name="search_panel_threshold">100dp</dimen>

View File

@ -239,16 +239,9 @@
<item name="android:textColor">#60000000</item>
</style>
<style name="SearchPanelCard">
<style name="SearchPanelCircle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/search_panel_card_height</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginEnd">8dp</item>
<item name="android:layout_gravity">bottom</item>
</style>
<style name="SearchPanelLogo">
<item name="android:layout_gravity">top|center_horizontal</item>
<item name="android:layout_height">match_parent</item>
</style>
<style name="SearchPanelScrim">

View File

@ -0,0 +1,592 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
public class SearchPanelCircleView extends FrameLayout {
private final int mCircleMinSize;
private final int mBaseMargin;
private final int mStaticOffset;
private final Paint mBackgroundPaint = new Paint();
private final Paint mRipplePaint = new Paint();
private final Rect mCircleRect = new Rect();
private final Rect mStaticRect = new Rect();
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mAppearInterpolator;
private final Interpolator mDisappearInterpolator;
private boolean mClipToOutline;
private final int mMaxElevation;
private boolean mAnimatingOut;
private float mOutlineAlpha;
private float mOffset;
private float mCircleSize;
private boolean mHorizontal;
private boolean mCircleHidden;
private ImageView mLogo;
private boolean mDraggedFarEnough;
private boolean mOffsetAnimatingIn;
private float mCircleAnimationEndValue;
private ArrayList<Ripple> mRipples = new ArrayList<Ripple>();
private ValueAnimator mOffsetAnimator;
private ValueAnimator mCircleAnimator;
private ValueAnimator mFadeOutAnimator;
private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
applyCircleSize((float) animation.getAnimatedValue());
updateElevation();
}
};
private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCircleAnimator = null;
}
};
private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setOffset((float) animation.getAnimatedValue());
}
};
public SearchPanelCircleView(Context context) {
this(context, null);
}
public SearchPanelCircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
if (mCircleSize > 0.0f) {
outline.setOval(mCircleRect);
} else {
outline.setEmpty();
}
outline.setAlpha(mOutlineAlpha);
}
});
setWillNotDraw(false);
mCircleMinSize = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_size);
mBaseMargin = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_base_margin);
mStaticOffset = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_travel_distance);
mMaxElevation = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_elevation);
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_linear_in);
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setColor(getResources().getColor(R.color.search_panel_circle_color));
mRipplePaint.setColor(getResources().getColor(R.color.search_panel_ripple_color));
mRipplePaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawRipples(canvas);
}
private void drawRipples(Canvas canvas) {
for (int i = 0; i < mRipples.size(); i++) {
Ripple ripple = mRipples.get(i);
ripple.draw(canvas);
}
}
private void drawBackground(Canvas canvas) {
canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
mBackgroundPaint);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLogo = (ImageView) findViewById(R.id.search_logo);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
if (changed) {
updateCircleRect(mStaticRect, mStaticOffset, true);
}
}
public void setCircleSize(float circleSize) {
setCircleSize(circleSize, false, null, 0, null);
}
public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable,
int startDelay, Interpolator interpolator) {
boolean isAnimating = mCircleAnimator != null;
boolean animationPending = isAnimating && !mCircleAnimator.isRunning();
boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0;
if (animated || animationPending || animatingOut) {
if (isAnimating) {
if (circleSize == mCircleAnimationEndValue) {
return;
}
mCircleAnimator.cancel();
}
mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
mCircleAnimator.addUpdateListener(mCircleUpdateListener);
mCircleAnimator.addListener(mClearAnimatorListener);
mCircleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (endRunnable != null) {
endRunnable.run();
}
}
});
Interpolator desiredInterpolator = interpolator != null ? interpolator
: circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator;
mCircleAnimator.setInterpolator(desiredInterpolator);
mCircleAnimator.setDuration(300);
mCircleAnimator.setStartDelay(startDelay);
mCircleAnimator.start();
mCircleAnimationEndValue = circleSize;
} else {
if (isAnimating) {
float diff = circleSize - mCircleAnimationEndValue;
PropertyValuesHolder[] values = mCircleAnimator.getValues();
values[0].setFloatValues(diff, circleSize);
mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
mCircleAnimationEndValue = circleSize;
} else {
applyCircleSize(circleSize);
updateElevation();
}
}
}
private void applyCircleSize(float circleSize) {
mCircleSize = circleSize;
updateLayout();
}
private void updateElevation() {
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
t = 1.0f - Math.max(t, 0.0f);
float offset = t * mMaxElevation;
setElevation(offset);
}
/**
* Sets the offset to the edge of the screen. By default this not not animated.
*
* @param offset The offset to apply.
*/
public void setOffset(float offset) {
setOffset(offset, false, 0, null, null);
}
/**
* Sets the offset to the edge of the screen.
*
* @param offset The offset to apply.
* @param animate Whether an animation should be performed.
* @param startDelay The desired start delay if animated.
* @param interpolator The desired interpolator if animated. If null,
* a default interpolator will be taken designed for appearing or
* disappearing.
* @param endRunnable The end runnable which should be executed when the animation is finished.
*/
private void setOffset(float offset, boolean animate, int startDelay,
Interpolator interpolator, final Runnable endRunnable) {
if (!animate) {
mOffset = offset;
updateLayout();
if (endRunnable != null) {
endRunnable.run();
}
} else {
if (mOffsetAnimator != null) {
mOffsetAnimator.removeAllListeners();
mOffsetAnimator.cancel();
}
mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOffsetAnimator = null;
if (endRunnable != null) {
endRunnable.run();
}
}
});
Interpolator desiredInterpolator = interpolator != null ?
interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator;
mOffsetAnimator.setInterpolator(desiredInterpolator);
mOffsetAnimator.setStartDelay(startDelay);
mOffsetAnimator.setDuration(300);
mOffsetAnimator.start();
mOffsetAnimatingIn = offset != 0;
}
}
private void updateLayout() {
updateCircleRect();
updateLogo();
invalidateOutline();
invalidate();
updateClipping();
}
private void updateClipping() {
boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty();
if (clip != mClipToOutline) {
setClipToOutline(clip);
mClipToOutline = clip;
}
}
private void updateLogo() {
boolean exitAnimationRunning = mFadeOutAnimator != null;
Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect;
float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f;
float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f;
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
if (!exitAnimationRunning) {
if (mHorizontal) {
translationX += t * mStaticOffset * 0.3f;
} else {
translationY += t * mStaticOffset * 0.3f;
}
float alpha = 1.0f-t;
alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
mLogo.setAlpha(alpha);
} else {
translationY += (mOffset - mStaticOffset) / 2;
}
mLogo.setTranslationX(translationX);
mLogo.setTranslationY(translationY);
}
private void updateCircleRect() {
updateCircleRect(mCircleRect, mOffset, false);
}
private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
int left, top;
float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
if (mHorizontal) {
left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset);
top = (int) ((getHeight() - circleSize) / 2);
} else {
left = (int) (getWidth() - circleSize) / 2;
top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
}
rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
}
public void setHorizontal(boolean horizontal) {
mHorizontal = horizontal;
updateCircleRect(mStaticRect, mStaticOffset, true);
updateLayout();
}
public void setDragDistance(float distance) {
if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) {
float circleSize = mCircleMinSize + rubberband(distance);
setCircleSize(circleSize);
}
}
private float rubberband(float diff) {
return (float) Math.pow(Math.abs(diff), 0.6f);
}
public void startAbortAnimation(Runnable endRunnable) {
if (mAnimatingOut) {
if (endRunnable != null) {
endRunnable.run();
}
return;
}
setCircleSize(0, true, null, 0, null);
setOffset(0, true, 0, null, endRunnable);
mCircleHidden = true;
}
public void startEnterAnimation() {
if (mAnimatingOut) {
return;
}
applyCircleSize(0);
setOffset(0);
setCircleSize(mCircleMinSize, true, null, 50, null);
setOffset(mStaticOffset, true, 50, null, null);
mCircleHidden = false;
}
public void startExitAnimation(final Runnable endRunnable) {
if (!mHorizontal) {
float offset = getHeight() / 2.0f;
setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null);
float xMax = getWidth() / 2;
float yMax = getHeight() / 2;
float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2);
setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator);
performExitFadeOutAnimation(50, 300, endRunnable);
} else {
// when in landscape, we don't wan't the animation as it interferes with the general
// rotation animation to the homescreen.
endRunnable.run();
}
}
private void performExitFadeOutAnimation(int startDelay, int duration,
final Runnable endRunnable) {
mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f);
// Linear since we are animating multiple values
mFadeOutAnimator.setInterpolator(new LinearInterpolator());
mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedFraction = animation.getAnimatedFraction();
float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f;
logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue);
float backgroundValue = animatedFraction < 0.2f ? 0.0f :
PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f);
backgroundValue = 1.0f - backgroundValue;
mBackgroundPaint.setAlpha((int) (backgroundValue * 255));
mOutlineAlpha = backgroundValue;
mLogo.setAlpha(logoValue);
invalidateOutline();
invalidate();
}
});
mFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (endRunnable != null) {
endRunnable.run();
}
mLogo.setAlpha(1.0f);
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
mFadeOutAnimator = null;
}
});
mFadeOutAnimator.setStartDelay(startDelay);
mFadeOutAnimator.setDuration(duration);
mFadeOutAnimator.start();
}
public void setDraggedFarEnough(boolean farEnough) {
if (farEnough != mDraggedFarEnough) {
if (farEnough) {
if (mCircleHidden) {
startEnterAnimation();
}
if (mOffsetAnimator == null) {
addRipple();
} else {
postDelayed(new Runnable() {
@Override
public void run() {
addRipple();
}
}, 100);
}
} else {
startAbortAnimation(null);
}
mDraggedFarEnough = farEnough;
}
}
private void addRipple() {
if (mRipples.size() > 1) {
// we only want 2 ripples at the time
return;
}
float xInterpolation, yInterpolation;
if (mHorizontal) {
xInterpolation = 0.75f;
yInterpolation = 0.5f;
} else {
xInterpolation = 0.5f;
yInterpolation = 0.75f;
}
float circleCenterX = mStaticRect.left * (1.0f - xInterpolation)
+ mStaticRect.right * xInterpolation;
float circleCenterY = mStaticRect.top * (1.0f - yInterpolation)
+ mStaticRect.bottom * yInterpolation;
float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f;
Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius);
ripple.start();
}
public void reset() {
mDraggedFarEnough = false;
mAnimatingOut = false;
mCircleHidden = true;
mClipToOutline = false;
if (mFadeOutAnimator != null) {
mFadeOutAnimator.cancel();
}
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
}
/**
* Check if an animation is currently running
*
* @param enterAnimation Is the animating queried the enter animation.
*/
public boolean isAnimationRunning(boolean enterAnimation) {
return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn);
}
public void performOnAnimationFinished(final Runnable runnable) {
if (mOffsetAnimator != null) {
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (runnable != null) {
runnable.run();
}
}
});
} else {
if (runnable != null) {
runnable.run();
}
}
}
public void setAnimatingOut(boolean animatingOut) {
mAnimatingOut = animatingOut;
}
/**
* @return Whether the circle is currently launching to the search activity or aborting the
* interaction
*/
public boolean isAnimatingOut() {
return mAnimatingOut;
}
@Override
public boolean hasOverlappingRendering() {
// not really true but it's ok during an animation, as it's never permanent
return false;
}
private class Ripple {
float x;
float y;
float radius;
float endRadius;
float alpha;
Ripple(float x, float y, float endRadius) {
this.x = x;
this.y = y;
this.endRadius = endRadius;
}
void start() {
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
// Linear since we are animating multiple values
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
alpha = 1.0f - animation.getAnimatedFraction();
alpha = mDisappearInterpolator.getInterpolation(alpha);
radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction());
radius *= endRadius;
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRipples.remove(Ripple.this);
updateClipping();
}
public void onAnimationStart(Animator animation) {
mRipples.add(Ripple.this);
updateClipping();
}
});
animator.setDuration(400);
animator.start();
}
public void draw(Canvas canvas) {
mRipplePaint.setAlpha((int) (alpha * 255));
canvas.drawCircle(x, y, radius, mRipplePaint);
}
}
}

View File

@ -16,10 +16,6 @@
package com.android.systemui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
@ -38,8 +34,6 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
@ -62,26 +56,19 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
private final Context mContext;
private BaseStatusBar mBar;
private View mCard;
private SearchPanelCircleView mCircle;
private ImageView mLogo;
private View mScrim;
private int mPeekHeight;
private int mThreshold;
private boolean mHorizontal;
private final Interpolator mLinearOutSlowInInterpolator;
private final Interpolator mFastOutLinearInInterpolator;
private boolean mAnimatingIn;
private boolean mAnimatingOut;
private boolean mLaunching;
private boolean mDragging;
private boolean mDraggedFarEnough;
private float mStartTouch;
private float mStartDrag;
private ObjectAnimator mEnterAnimator;
private boolean mStartExitAfterAnimatingIn;
private boolean mLaunchPending;
public SearchPanelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@ -90,12 +77,7 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
mPeekHeight = context.getResources().getDimensionPixelSize(R.dimen.search_card_peek_height);
mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
mLinearOutSlowInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
mFastOutLinearInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in);
}
private void startAssistActivity() {
@ -128,7 +110,7 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
protected void onFinishInflate() {
super.onFinishInflate();
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mCard = findViewById(R.id.search_panel_card);
mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
mLogo = (ImageView) findViewById(R.id.search_logo);
mScrim = findViewById(R.id.search_panel_scrim);
}
@ -170,16 +152,9 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
v.setImageDrawable(null);
}
private boolean pointInside(int x, int y, View v) {
final int l = v.getLeft();
final int r = v.getRight();
final int t = v.getTop();
final int b = v.getBottom();
return x >= l && x < r && y >= t && y < b;
}
@Override
public boolean isInContentArea(int x, int y) {
return pointInside(x, y, mCard);
return true;
}
private void vibrate() {
@ -199,16 +174,10 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
if (getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
vibrate();
mCard.setAlpha(1f);
if (animate) {
startEnterAnimation();
} else {
mScrim.setAlpha(1f);
if (mHorizontal) {
mCard.setX(getWidth() - mPeekHeight);
} else {
mCard.setY(getHeight() - mPeekHeight);
}
}
}
setFocusable(true);
@ -224,30 +193,7 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
}
private void startEnterAnimation() {
if (mHorizontal) {
mCard.setX(getWidth());
} else {
mCard.setY(getHeight());
}
mAnimatingIn = true;
mCard.animate().cancel();
mEnterAnimator = ObjectAnimator.ofFloat(mCard, mHorizontal ? View.X : View.Y,
mHorizontal ? mCard.getX() : mCard.getY(),
mHorizontal ? getWidth() - mPeekHeight : getHeight() - mPeekHeight);
mEnterAnimator.setDuration(300);
mEnterAnimator.setStartDelay(50);
mEnterAnimator.setInterpolator(mLinearOutSlowInInterpolator);
mEnterAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mEnterAnimator = null;
mAnimatingIn = false;
if (mStartExitAfterAnimatingIn) {
startExitAnimation();
}
}
});
mEnterAnimator.start();
mCircle.startEnterAnimation();
mScrim.setAlpha(0f);
mScrim.animate()
.alpha(1f)
@ -259,26 +205,17 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
}
private void startAbortAnimation() {
mCard.animate().cancel();
mAnimatingOut = true;
if (mHorizontal) {
mCard.animate().x(getWidth());
} else {
mCard.animate().y(getHeight());
}
mCard.animate()
.setDuration(150)
.setInterpolator(mFastOutLinearInInterpolator)
.withEndAction(new Runnable() {
mCircle.startAbortAnimation(new Runnable() {
@Override
public void run() {
mAnimatingOut = false;
mCircle.setAnimatingOut(false);
setVisibility(View.INVISIBLE);
}
});
mCircle.setAnimatingOut(true);
mScrim.animate()
.alpha(0f)
.setDuration(150)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(PhoneStatusBar.ALPHA_OUT);
}
@ -314,7 +251,7 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
* when the animation is done.
*/
public boolean isShowing() {
return getVisibility() == View.VISIBLE && !mAnimatingOut;
return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut();
}
public void setBar(BaseStatusBar bar) {
@ -326,60 +263,46 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
.getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
}
private float rubberband(float diff) {
return Math.signum(diff) * (float) Math.pow(Math.abs(diff), 0.8f);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mLaunching || mLaunchPending) {
return false;
}
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mStartTouch = mHorizontal ? event.getX() : event.getY();
mDragging = false;
mDraggedFarEnough = false;
mStartExitAfterAnimatingIn = false;
mCircle.reset();
break;
case MotionEvent.ACTION_MOVE:
float currentTouch = mHorizontal ? event.getX() : event.getY();
if (getVisibility() == View.VISIBLE && !mDragging &&
(!mAnimatingIn || Math.abs(mStartTouch - currentTouch) > mThreshold)) {
(!mCircle.isAnimationRunning(true /* enterAnimation */)
|| Math.abs(mStartTouch - currentTouch) > mThreshold)) {
mStartDrag = currentTouch;
mDragging = true;
}
if (!mDraggedFarEnough && Math.abs(mStartTouch - currentTouch) > mThreshold) {
mDraggedFarEnough = true;
}
if (mDragging) {
if (!mAnimatingIn && !mAnimatingOut) {
if (Math.abs(currentTouch - mStartDrag) > mThreshold) {
startExitAnimation();
} else {
if (mHorizontal) {
mCard.setX(getWidth() - mPeekHeight + rubberband(
currentTouch - mStartDrag));
} else {
mCard.setY(getHeight() - mPeekHeight + rubberband(
currentTouch - mStartDrag));
}
}
} else if (mAnimatingIn ) {
float diff = rubberband(currentTouch - mStartDrag);
PropertyValuesHolder[] values = mEnterAnimator.getValues();
values[0].setFloatValues(
mHorizontal ? getWidth() + diff : getHeight() + diff,
mHorizontal
? getWidth() - mPeekHeight + diff
: getHeight() - mPeekHeight + diff);
mEnterAnimator.setCurrentPlayTime(mEnterAnimator.getCurrentPlayTime());
}
float offset = Math.max(mStartDrag - currentTouch, 0.0f);
mCircle.setDragDistance(offset);
mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold;
mCircle.setDraggedFarEnough(mDraggedFarEnough);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDraggedFarEnough) {
if (mAnimatingIn) {
mStartExitAfterAnimatingIn = true;
if (mCircle.isAnimationRunning(true /* enterAnimation */)) {
mLaunchPending = true;
mCircle.setAnimatingOut(true);
mCircle.performOnAnimationFinished(new Runnable() {
@Override
public void run() {
startExitAnimation();
}
});
} else {
startExitAnimation();
}
@ -392,35 +315,31 @@ public class SearchPanelView extends FrameLayout implements StatusBarPanel {
}
private void startExitAnimation() {
if (mAnimatingOut || getVisibility() != View.VISIBLE) {
mLaunchPending = false;
if (mLaunching || getVisibility() != View.VISIBLE) {
return;
}
if (mEnterAnimator != null) {
mEnterAnimator.cancel();
}
mAnimatingOut = true;
mLaunching = true;
startAssistActivity();
vibrate();
mCard.animate()
.alpha(0f)
.withLayer()
.setDuration(250)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
.withEndAction(new Runnable() {
mCircle.setAnimatingOut(true);
mCircle.startExitAnimation(new Runnable() {
@Override
public void run() {
mAnimatingOut = false;
mLaunching = false;
mCircle.setAnimatingOut(false);
setVisibility(View.INVISIBLE);
}
});
mScrim.animate()
.alpha(0f)
.setDuration(250)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(PhoneStatusBar.ALPHA_OUT);
}
public void setHorizontal(boolean horizontal) {
mHorizontal = horizontal;
mCircle.setHorizontal(horizontal);
}
}

View File

@ -93,6 +93,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.policy.PreviewInflater;
@ -165,6 +166,9 @@ public abstract class BaseStatusBar extends SystemUI implements
protected int mLayoutDirection = -1; // invalid
protected AccessibilityManager mAccessibilityManager;
// on-screen navigation buttons
protected NavigationBarView mNavigationBarView = null;
private Locale mLocale;
private float mFontScale;
@ -1006,6 +1010,8 @@ public abstract class BaseStatusBar extends SystemUI implements
mSearchPanelView.setOnTouchListener(
new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
mSearchPanelView.setVisibility(View.GONE);
boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical();
mSearchPanelView.setHorizontal(vertical);
WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());

View File

@ -88,6 +88,7 @@ public class NavigationBarView extends LinearLayout {
private OnVerticalChangedListener mOnVerticalChangedListener;
private boolean mIsLayoutRtl;
private boolean mDelegateIntercepted;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@ -198,27 +199,45 @@ public class NavigationBarView extends LinearLayout {
public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
mOnVerticalChangedListener = onVerticalChangedListener;
notifyVerticalChangedListener(mVertical);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mTaskSwitchHelper.onTouchEvent(event)) {
initDownStates(event);
if (!mDelegateIntercepted && mTaskSwitchHelper.onTouchEvent(event)) {
return true;
}
if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
mDeadZone.poke(event);
}
if (mDelegateHelper != null) {
if (mDelegateHelper != null && mDelegateIntercepted) {
boolean ret = mDelegateHelper.onInterceptTouchEvent(event);
if (ret) return true;
}
return super.onTouchEvent(event);
}
private void initDownStates(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mDelegateIntercepted = false;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mTaskSwitchHelper.onInterceptTouchEvent(event) ||
mDelegateHelper.onInterceptTouchEvent(event);
initDownStates(event);
boolean intercept = mTaskSwitchHelper.onInterceptTouchEvent(event);
if (!intercept) {
mDelegateIntercepted = mDelegateHelper.onInterceptTouchEvent(event);
intercept = mDelegateIntercepted;
} else {
MotionEvent cancelEvent = MotionEvent.obtain(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
mDelegateHelper.onInterceptTouchEvent(cancelEvent);
cancelEvent.recycle();
}
return intercept;
}
private H mHandler = new H();
@ -426,12 +445,16 @@ public class NavigationBarView extends LinearLayout {
if (mDelegateHelper != null) {
mDelegateHelper.setSwapXY(mVertical);
}
boolean isRTL = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
mTaskSwitchHelper.setBarState(mVertical, isRTL);
updateTaskSwitchHelper();
setNavigationIconHints(mNavigationIconHints, true);
}
private void updateTaskSwitchHelper() {
boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
mTaskSwitchHelper.setBarState(mVertical, isRtl);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
@ -448,19 +471,24 @@ public class NavigationBarView extends LinearLayout {
mVertical = newVertical;
//Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
reorient();
if (mOnVerticalChangedListener != null) {
mOnVerticalChangedListener.onVerticalChanged(newVertical);
}
notifyVerticalChangedListener(newVertical);
}
postCheckForInvalidLayout("sizeChanged");
super.onSizeChanged(w, h, oldw, oldh);
}
private void notifyVerticalChangedListener(boolean newVertical) {
if (mOnVerticalChangedListener != null) {
mOnVerticalChangedListener.onVerticalChanged(newVertical);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateRTLOrder();
updateTaskSwitchHelper();
}
/**

View File

@ -55,7 +55,7 @@ public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnG
// task switcher detector
mTaskSwitcherDetector.onTouchEvent(event);
int action = event.getAction();
boolean interceptTouches = false;
boolean intercepted = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mTouchDownX = (int) event.getX();
@ -71,7 +71,6 @@ public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnG
? xDiff > mScrollTouchSlop && xDiff > yDiff
: yDiff > mScrollTouchSlop && yDiff > xDiff;
if (exceededTouchSlop) {
interceptTouches = true;
return true;
}
break;
@ -80,7 +79,7 @@ public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnG
case MotionEvent.ACTION_UP:
break;
}
return interceptTouches;
return intercepted;
}
public boolean onTouchEvent(MotionEvent event) {

View File

@ -316,8 +316,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int[] mPositionTmp = new int[2];
boolean mExpandedVisible;
// on-screen navigation buttons
private NavigationBarView mNavigationBarView = null;
private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
// the tracker view