• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.assist;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.graphics.Canvas;
24 import android.graphics.Outline;
25 import android.graphics.Paint;
26 import android.graphics.Rect;
27 import android.util.AttributeSet;
28 import android.view.View;
29 import android.view.ViewOutlineProvider;
30 import android.view.animation.Interpolator;
31 import android.view.animation.OvershootInterpolator;
32 import android.widget.FrameLayout;
33 import android.widget.ImageView;
34 
35 import com.android.systemui.Interpolators;
36 import com.android.systemui.R;
37 
38 public class AssistOrbView extends FrameLayout {
39 
40     private final int mCircleMinSize;
41     private final int mBaseMargin;
42     private final int mStaticOffset;
43     private final Paint mBackgroundPaint = new Paint();
44     private final Rect mCircleRect = new Rect();
45     private final Rect mStaticRect = new Rect();
46     private final Interpolator mOvershootInterpolator = new OvershootInterpolator();
47 
48     private boolean mClipToOutline;
49     private final int mMaxElevation;
50     private float mOutlineAlpha;
51     private float mOffset;
52     private float mCircleSize;
53     private ImageView mLogo;
54     private float mCircleAnimationEndValue;
55 
56     private ValueAnimator mOffsetAnimator;
57     private ValueAnimator mCircleAnimator;
58 
59     private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
60             = new ValueAnimator.AnimatorUpdateListener() {
61         @Override
62         public void onAnimationUpdate(ValueAnimator animation) {
63             applyCircleSize((float) animation.getAnimatedValue());
64             updateElevation();
65         }
66     };
67     private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
68         @Override
69         public void onAnimationEnd(Animator animation) {
70             mCircleAnimator = null;
71         }
72     };
73     private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
74             = new ValueAnimator.AnimatorUpdateListener() {
75         @Override
76         public void onAnimationUpdate(ValueAnimator animation) {
77             mOffset = (float) animation.getAnimatedValue();
78             updateLayout();
79         }
80     };
81 
82 
AssistOrbView(Context context)83     public AssistOrbView(Context context) {
84         this(context, null);
85     }
86 
AssistOrbView(Context context, AttributeSet attrs)87     public AssistOrbView(Context context, AttributeSet attrs) {
88         this(context, attrs, 0);
89     }
90 
AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr)91     public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
92         this(context, attrs, defStyleAttr, 0);
93     }
94 
AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)95     public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr,
96             int defStyleRes) {
97         super(context, attrs, defStyleAttr, defStyleRes);
98         setOutlineProvider(new ViewOutlineProvider() {
99             @Override
100             public void getOutline(View view, Outline outline) {
101                 if (mCircleSize > 0.0f) {
102                     outline.setOval(mCircleRect);
103                 } else {
104                     outline.setEmpty();
105                 }
106                 outline.setAlpha(mOutlineAlpha);
107             }
108         });
109         setWillNotDraw(false);
110         mCircleMinSize = context.getResources().getDimensionPixelSize(
111                 R.dimen.assist_orb_size);
112         mBaseMargin = context.getResources().getDimensionPixelSize(
113                 R.dimen.assist_orb_base_margin);
114         mStaticOffset = context.getResources().getDimensionPixelSize(
115                 R.dimen.assist_orb_travel_distance);
116         mMaxElevation = context.getResources().getDimensionPixelSize(
117                 R.dimen.assist_orb_elevation);
118         mBackgroundPaint.setAntiAlias(true);
119         mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color));
120     }
121 
getLogo()122     public ImageView getLogo() {
123         return mLogo;
124     }
125 
126     @Override
onDraw(Canvas canvas)127     protected void onDraw(Canvas canvas) {
128         super.onDraw(canvas);
129         drawBackground(canvas);
130     }
131 
drawBackground(Canvas canvas)132     private void drawBackground(Canvas canvas) {
133         canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
134                 mBackgroundPaint);
135     }
136 
137     @Override
onFinishInflate()138     protected void onFinishInflate() {
139         super.onFinishInflate();
140         mLogo = (ImageView) findViewById(R.id.search_logo);
141     }
142 
143     @Override
onLayout(boolean changed, int l, int t, int r, int b)144     protected void onLayout(boolean changed, int l, int t, int r, int b) {
145         mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
146         if (changed) {
147             updateCircleRect(mStaticRect, mStaticOffset, true);
148         }
149     }
150 
animateCircleSize(float circleSize, long duration, long startDelay, Interpolator interpolator)151     public void animateCircleSize(float circleSize, long duration,
152             long startDelay, Interpolator interpolator) {
153         if (circleSize == mCircleAnimationEndValue) {
154             return;
155         }
156         if (mCircleAnimator != null) {
157             mCircleAnimator.cancel();
158         }
159         mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
160         mCircleAnimator.addUpdateListener(mCircleUpdateListener);
161         mCircleAnimator.addListener(mClearAnimatorListener);
162         mCircleAnimator.setInterpolator(interpolator);
163         mCircleAnimator.setDuration(duration);
164         mCircleAnimator.setStartDelay(startDelay);
165         mCircleAnimator.start();
166         mCircleAnimationEndValue = circleSize;
167     }
168 
applyCircleSize(float circleSize)169     private void applyCircleSize(float circleSize) {
170         mCircleSize = circleSize;
171         updateLayout();
172     }
173 
updateElevation()174     private void updateElevation() {
175         float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
176         t = 1.0f - Math.max(t, 0.0f);
177         float offset = t * mMaxElevation;
178         setElevation(offset);
179     }
180 
181     /**
182      * Animates the offset to the edge of the screen.
183      *
184      * @param offset The offset to apply.
185      * @param startDelay The desired start delay if animated.
186      *
187      * @param interpolator The desired interpolator if animated. If null,
188      *                     a default interpolator will be taken designed for appearing or
189      *                     disappearing.
190      */
animateOffset(float offset, long duration, long startDelay, Interpolator interpolator)191     private void animateOffset(float offset, long duration, long startDelay,
192             Interpolator interpolator) {
193         if (mOffsetAnimator != null) {
194             mOffsetAnimator.removeAllListeners();
195             mOffsetAnimator.cancel();
196         }
197         mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
198         mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
199         mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
200             @Override
201             public void onAnimationEnd(Animator animation) {
202                 mOffsetAnimator = null;
203             }
204         });
205         mOffsetAnimator.setInterpolator(interpolator);
206         mOffsetAnimator.setStartDelay(startDelay);
207         mOffsetAnimator.setDuration(duration);
208         mOffsetAnimator.start();
209     }
210 
updateLayout()211     private void updateLayout() {
212         updateCircleRect();
213         updateLogo();
214         invalidateOutline();
215         invalidate();
216         updateClipping();
217     }
218 
updateClipping()219     private void updateClipping() {
220         boolean clip = mCircleSize < mCircleMinSize;
221         if (clip != mClipToOutline) {
222             setClipToOutline(clip);
223             mClipToOutline = clip;
224         }
225     }
226 
227     private void updateLogo() {
228         float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f;
229         float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f
230                 - mLogo.getHeight() / 2.0f - mCircleMinSize / 7f;
231         float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
232         translationY += t * mStaticOffset * 0.1f;
233         float alpha = 1.0f-t;
234         alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
235         mLogo.setImageAlpha((int) (alpha * 255));
236         mLogo.setTranslationX(translationX);
237         mLogo.setTranslationY(translationY);
238     }
239 
240     private void updateCircleRect() {
241         updateCircleRect(mCircleRect, mOffset, false);
242     }
243 
244     private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
245         int left, top;
246         float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
247         left = (int) (getWidth() - circleSize) / 2;
248         top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
249         rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
250     }
251 
252     public void startExitAnimation(long delay) {
253         animateCircleSize(0, 200, delay, Interpolators.FAST_OUT_LINEAR_IN);
254         animateOffset(0, 200, delay, Interpolators.FAST_OUT_LINEAR_IN);
255     }
256 
257     public void startEnterAnimation() {
258         applyCircleSize(0);
259         post(new Runnable() {
260             @Override
261             public void run() {
262                 animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator);
263                 animateOffset(mStaticOffset, 400, 0 /* delay */, Interpolators.LINEAR_OUT_SLOW_IN);
264             }
265         });
266     }
267 
268     public void reset() {
269         mClipToOutline = false;
270         mBackgroundPaint.setAlpha(255);
271         mOutlineAlpha = 1.0f;
272     }
273 
274     @Override
275     public boolean hasOverlappingRendering() {
276         // not really true but it's ok during an animation, as it's never permanent
277         return false;
278     }
279 }
280