• 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.AnimationUtils;
31 import android.view.animation.Interpolator;
32 import android.view.animation.OvershootInterpolator;
33 import android.widget.FrameLayout;
34 import android.widget.ImageView;
35 
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 mAppearInterpolator;
47     private final Interpolator mDisappearInterpolator;
48     private final Interpolator mOvershootInterpolator = new OvershootInterpolator();
49 
50     private boolean mClipToOutline;
51     private final int mMaxElevation;
52     private float mOutlineAlpha;
53     private float mOffset;
54     private float mCircleSize;
55     private ImageView mLogo;
56     private float mCircleAnimationEndValue;
57 
58     private ValueAnimator mOffsetAnimator;
59     private ValueAnimator mCircleAnimator;
60 
61     private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
62             = new ValueAnimator.AnimatorUpdateListener() {
63         @Override
64         public void onAnimationUpdate(ValueAnimator animation) {
65             applyCircleSize((float) animation.getAnimatedValue());
66             updateElevation();
67         }
68     };
69     private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
70         @Override
71         public void onAnimationEnd(Animator animation) {
72             mCircleAnimator = null;
73         }
74     };
75     private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
76             = new ValueAnimator.AnimatorUpdateListener() {
77         @Override
78         public void onAnimationUpdate(ValueAnimator animation) {
79             mOffset = (float) animation.getAnimatedValue();
80             updateLayout();
81         }
82     };
83 
84 
AssistOrbView(Context context)85     public AssistOrbView(Context context) {
86         this(context, null);
87     }
88 
AssistOrbView(Context context, AttributeSet attrs)89     public AssistOrbView(Context context, AttributeSet attrs) {
90         this(context, attrs, 0);
91     }
92 
AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr)93     public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
94         this(context, attrs, defStyleAttr, 0);
95     }
96 
AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)97     public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr,
98             int defStyleRes) {
99         super(context, attrs, defStyleAttr, defStyleRes);
100         setOutlineProvider(new ViewOutlineProvider() {
101             @Override
102             public void getOutline(View view, Outline outline) {
103                 if (mCircleSize > 0.0f) {
104                     outline.setOval(mCircleRect);
105                 } else {
106                     outline.setEmpty();
107                 }
108                 outline.setAlpha(mOutlineAlpha);
109             }
110         });
111         setWillNotDraw(false);
112         mCircleMinSize = context.getResources().getDimensionPixelSize(
113                 R.dimen.assist_orb_size);
114         mBaseMargin = context.getResources().getDimensionPixelSize(
115                 R.dimen.assist_orb_base_margin);
116         mStaticOffset = context.getResources().getDimensionPixelSize(
117                 R.dimen.assist_orb_travel_distance);
118         mMaxElevation = context.getResources().getDimensionPixelSize(
119                 R.dimen.assist_orb_elevation);
120         mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
121                 android.R.interpolator.linear_out_slow_in);
122         mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
123                 android.R.interpolator.fast_out_linear_in);
124         mBackgroundPaint.setAntiAlias(true);
125         mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color));
126     }
127 
getLogo()128     public ImageView getLogo() {
129         return mLogo;
130     }
131 
132     @Override
onDraw(Canvas canvas)133     protected void onDraw(Canvas canvas) {
134         super.onDraw(canvas);
135         drawBackground(canvas);
136     }
137 
drawBackground(Canvas canvas)138     private void drawBackground(Canvas canvas) {
139         canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
140                 mBackgroundPaint);
141     }
142 
143     @Override
onFinishInflate()144     protected void onFinishInflate() {
145         super.onFinishInflate();
146         mLogo = (ImageView) findViewById(R.id.search_logo);
147     }
148 
149     @Override
onLayout(boolean changed, int l, int t, int r, int b)150     protected void onLayout(boolean changed, int l, int t, int r, int b) {
151         mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
152         if (changed) {
153             updateCircleRect(mStaticRect, mStaticOffset, true);
154         }
155     }
156 
animateCircleSize(float circleSize, long duration, long startDelay, Interpolator interpolator)157     public void animateCircleSize(float circleSize, long duration,
158             long startDelay, Interpolator interpolator) {
159         if (circleSize == mCircleAnimationEndValue) {
160             return;
161         }
162         if (mCircleAnimator != null) {
163             mCircleAnimator.cancel();
164         }
165         mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
166         mCircleAnimator.addUpdateListener(mCircleUpdateListener);
167         mCircleAnimator.addListener(mClearAnimatorListener);
168         mCircleAnimator.setInterpolator(interpolator);
169         mCircleAnimator.setDuration(duration);
170         mCircleAnimator.setStartDelay(startDelay);
171         mCircleAnimator.start();
172         mCircleAnimationEndValue = circleSize;
173     }
174 
applyCircleSize(float circleSize)175     private void applyCircleSize(float circleSize) {
176         mCircleSize = circleSize;
177         updateLayout();
178     }
179 
updateElevation()180     private void updateElevation() {
181         float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
182         t = 1.0f - Math.max(t, 0.0f);
183         float offset = t * mMaxElevation;
184         setElevation(offset);
185     }
186 
187     /**
188      * Animates the offset to the edge of the screen.
189      *
190      * @param offset The offset to apply.
191      * @param startDelay The desired start delay if animated.
192      *
193      * @param interpolator The desired interpolator if animated. If null,
194      *                     a default interpolator will be taken designed for appearing or
195      *                     disappearing.
196      */
animateOffset(float offset, long duration, long startDelay, Interpolator interpolator)197     private void animateOffset(float offset, long duration, long startDelay,
198             Interpolator interpolator) {
199         if (mOffsetAnimator != null) {
200             mOffsetAnimator.removeAllListeners();
201             mOffsetAnimator.cancel();
202         }
203         mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
204         mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
205         mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
206             @Override
207             public void onAnimationEnd(Animator animation) {
208                 mOffsetAnimator = null;
209             }
210         });
211         mOffsetAnimator.setInterpolator(interpolator);
212         mOffsetAnimator.setStartDelay(startDelay);
213         mOffsetAnimator.setDuration(duration);
214         mOffsetAnimator.start();
215     }
216 
updateLayout()217     private void updateLayout() {
218         updateCircleRect();
219         updateLogo();
220         invalidateOutline();
221         invalidate();
222         updateClipping();
223     }
224 
updateClipping()225     private void updateClipping() {
226         boolean clip = mCircleSize < mCircleMinSize;
227         if (clip != mClipToOutline) {
228             setClipToOutline(clip);
229             mClipToOutline = clip;
230         }
231     }
232 
233     private void updateLogo() {
234         float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f;
235         float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f
236                 - mLogo.getHeight() / 2.0f - mCircleMinSize / 7f;
237         float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
238         translationY += t * mStaticOffset * 0.1f;
239         float alpha = 1.0f-t;
240         alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
241         mLogo.setImageAlpha((int) (alpha * 255));
242         mLogo.setTranslationX(translationX);
243         mLogo.setTranslationY(translationY);
244     }
245 
246     private void updateCircleRect() {
247         updateCircleRect(mCircleRect, mOffset, false);
248     }
249 
250     private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
251         int left, top;
252         float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
253         left = (int) (getWidth() - circleSize) / 2;
254         top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
255         rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
256     }
257 
258     public void startExitAnimation(long delay) {
259         animateCircleSize(0, 200, delay, mDisappearInterpolator);
260         animateOffset(0, 200, delay, mDisappearInterpolator);
261     }
262 
263     public void startEnterAnimation() {
264         applyCircleSize(0);
265         post(new Runnable() {
266             @Override
267             public void run() {
268                 animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator);
269                 animateOffset(mStaticOffset, 400, 0 /* delay */, mAppearInterpolator);
270             }
271         });
272     }
273 
274     public void reset() {
275         mClipToOutline = false;
276         mBackgroundPaint.setAlpha(255);
277         mOutlineAlpha = 1.0f;
278     }
279 
280     @Override
281     public boolean hasOverlappingRendering() {
282         // not really true but it's ok during an animation, as it's never permanent
283         return false;
284     }
285 }
286