blob: ace24281f04ab455bf9076f2d22da66322a366ea [file] [log] [blame]
Winson Chung303e1ff2014-03-07 15:06:19 -08001/*
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
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.content.Context;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.Path;
27import android.graphics.Point;
28import android.graphics.Rect;
29import android.graphics.RectF;
30import android.graphics.Typeface;
31import android.view.Gravity;
32import android.view.View;
33import android.view.animation.AccelerateDecelerateInterpolator;
34import android.view.animation.AccelerateInterpolator;
35import android.view.animation.DecelerateInterpolator;
36import android.widget.FrameLayout;
37import android.widget.ImageView;
38import com.android.systemui.recents.Console;
39import com.android.systemui.recents.Constants;
40import com.android.systemui.recents.RecentsConfiguration;
41import com.android.systemui.recents.model.Task;
Winson Chung303e1ff2014-03-07 15:06:19 -080042
Winson Chung303e1ff2014-03-07 15:06:19 -080043
44/** The task thumbnail view */
45class TaskThumbnailView extends ImageView {
46 Task mTask;
47 int mBarColor;
48
49 Path mRoundedRectClipPath = new Path();
50
51 public TaskThumbnailView(Context context) {
52 super(context);
53 setScaleType(ScaleType.FIT_XY);
54 }
55
56 /** Binds the thumbnail view to the task */
57 void rebindToTask(Task t, boolean animate) {
58 mTask = t;
59 if (t.thumbnail != null) {
60 // Update the bar color
61 if (Constants.Values.TaskView.DrawColoredTaskBars) {
62 int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7};
Winson Chung04dfe0d2014-03-14 14:06:29 -070063 mBarColor = colors[mTask.key.intent.getComponent().getPackageName().length() % colors.length];
Winson Chung303e1ff2014-03-07 15:06:19 -080064 }
65
66 setImageBitmap(t.thumbnail);
67 if (animate) {
68 setAlpha(0f);
69 animate().alpha(1f)
70 .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
71 .start();
72 }
73 }
74 }
75
76 /** Unbinds the thumbnail view from the task */
77 void unbindFromTask() {
78 mTask = null;
79 setImageDrawable(null);
80 }
81
82 @Override
83 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
84 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
85
86 // Update the rounded rect clip path
87 RecentsConfiguration config = RecentsConfiguration.getInstance();
88 float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
89 mRoundedRectClipPath.reset();
90 mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
91 radius, radius, Path.Direction.CW);
92 }
93
94 @Override
95 protected void onDraw(Canvas canvas) {
96 if (Constants.Values.TaskView.UseRoundedCorners) {
97 canvas.clipPath(mRoundedRectClipPath);
98 }
99
100 super.onDraw(canvas);
101
102 if (Constants.Values.TaskView.DrawColoredTaskBars) {
103 RecentsConfiguration config = RecentsConfiguration.getInstance();
104 int taskBarHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
105 // XXX: If we actually use this, this should be pulled out into a TextView that we
106 // inflate
107
108 // Draw the task bar
109 Rect r = new Rect();
110 Paint p = new Paint();
111 p.setAntiAlias(true);
112 p.setSubpixelText(true);
113 p.setColor(mBarColor);
114 p.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
115 canvas.drawRect(0, 0, getMeasuredWidth(), taskBarHeight, p);
116 p.setColor(0xFFffffff);
117 p.setTextSize(68);
118 p.getTextBounds("X", 0, 1, r);
119 int offset = (int) (taskBarHeight - r.height()) / 2;
120 canvas.drawText(mTask.title, offset, offset + r.height(), p);
121 }
122 }
123}
124
125/* The task icon view */
126class TaskIconView extends ImageView {
127 Task mTask;
128
129 Path mClipPath = new Path();
130 float mClipRadius;
131 Point mClipOrigin = new Point();
132 ObjectAnimator mCircularClipAnimator;
133
134 public TaskIconView(Context context) {
135 super(context);
136 mClipPath = new Path();
137 mClipRadius = 1f;
138 }
139
140 /** Binds the icon view to the task */
141 void rebindToTask(Task t, boolean animate) {
142 mTask = t;
143 if (t.icon != null) {
144 setImageDrawable(t.icon);
145 if (animate) {
146 setAlpha(0f);
147 animate().alpha(1f)
148 .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
149 .start();
150 }
151 }
152 }
153
154 /** Unbinds the icon view from the task */
155 void unbindFromTask() {
156 mTask = null;
157 setImageDrawable(null);
158 }
159
160 /** Sets the circular clip radius on the icon */
161 public void setCircularClipRadius(float r) {
162 Console.log(Constants.DebugFlags.UI.Clipping, "[TaskView|setCircularClip]", "" + r);
163 mClipRadius = r;
164 invalidate();
165 }
166
167 /** Gets the circular clip radius on the icon */
168 public float getCircularClipRadius() {
169 return mClipRadius;
170 }
171
172 /** Animates the circular clip radius on the icon */
173 void animateCircularClip(boolean brNotTl, float newRadius, int duration, int startDelay,
174 TimeInterpolator interpolator,
175 AnimatorListenerAdapter listener) {
176 if (mCircularClipAnimator != null) {
177 mCircularClipAnimator.cancel();
178 mCircularClipAnimator.removeAllListeners();
179 }
180 if (brNotTl) {
181 mClipOrigin.set(0, 0);
182 } else {
183 mClipOrigin.set(getMeasuredWidth(), getMeasuredHeight());
184 }
185 mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", newRadius);
186 mCircularClipAnimator.setStartDelay(startDelay);
187 mCircularClipAnimator.setDuration(duration);
188 mCircularClipAnimator.setInterpolator(interpolator);
189 if (listener != null) {
190 mCircularClipAnimator.addListener(listener);
191 }
192 mCircularClipAnimator.start();
193 }
194
195 @Override
196 protected void onDraw(Canvas canvas) {
197 int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
198 int width = getMeasuredWidth();
199 int height = getMeasuredHeight();
200 int maxSize = (int) Math.ceil(Math.sqrt(width * width + height * height));
201 mClipPath.reset();
202 mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * maxSize, Path.Direction.CW);
203 canvas.clipPath(mClipPath);
204 super.onDraw(canvas);
205 canvas.restoreToCount(saveCount);
206 }
207}
208
209/* A task view */
Winson Chung04dfe0d2014-03-14 14:06:29 -0700210public class TaskView extends FrameLayout implements View.OnClickListener, Task.TaskCallbacks {
211 /** The TaskView callbacks */
212 interface TaskViewCallbacks {
213 public void onTaskIconClicked(TaskView tv);
214 // public void onTaskViewReboundToTask(TaskView tv, Task t);
215 }
216
Winson Chung303e1ff2014-03-07 15:06:19 -0800217 Task mTask;
218 TaskThumbnailView mThumbnailView;
219 TaskIconView mIconView;
220 TaskViewCallbacks mCb;
221
222 public TaskView(Context context) {
223 super(context);
224 mThumbnailView = new TaskThumbnailView(context);
225 mIconView = new TaskIconView(context);
226 mIconView.setOnClickListener(this);
227 addView(mThumbnailView);
228 addView(mIconView);
229
230 RecentsConfiguration config = RecentsConfiguration.getInstance();
231 int barHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
232 int iconSize = config.pxFromDp(Constants.Values.TaskView.TaskIconSizeDps);
233 int offset = barHeight - (iconSize / 2);
234
235 // XXX: Lets keep the icon in the corner for the time being
236 offset = iconSize / 4;
237
238 /*
239 ((LayoutParams) mThumbnailView.getLayoutParams()).leftMargin = barHeight / 2;
240 ((LayoutParams) mThumbnailView.getLayoutParams()).rightMargin = barHeight / 2;
241 ((LayoutParams) mThumbnailView.getLayoutParams()).bottomMargin = barHeight;
242 */
243 ((LayoutParams) mIconView.getLayoutParams()).gravity = Gravity.END;
244 ((LayoutParams) mIconView.getLayoutParams()).width = iconSize;
245 ((LayoutParams) mIconView.getLayoutParams()).height = iconSize;
246 ((LayoutParams) mIconView.getLayoutParams()).topMargin = offset;
247 ((LayoutParams) mIconView.getLayoutParams()).rightMargin = offset;
248 }
249
Winson Chung04dfe0d2014-03-14 14:06:29 -0700250 /** Set callback */
251 void setCallbacks(TaskViewCallbacks cb) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800252 mCb = cb;
253 }
254
Winson Chung303e1ff2014-03-07 15:06:19 -0800255 /** Gets the task */
256 Task getTask() {
257 return mTask;
258 }
259
260 /** Synchronizes this view's properties with the task's transform */
261 void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform,
262 TaskViewTransform transform, int duration) {
263 if (duration > 0) {
264 if (animateFromTransform != null) {
265 setTranslationY(animateFromTransform.translationY);
266 setScaleX(animateFromTransform.scale);
267 setScaleY(animateFromTransform.scale);
268 }
269 animate().translationY(transform.translationY)
270 .scaleX(transform.scale)
271 .scaleY(transform.scale)
272 .setDuration(duration)
273 .setInterpolator(new AccelerateDecelerateInterpolator())
274 .start();
275 } else {
276 setTranslationY(transform.translationY);
277 setScaleX(transform.scale);
278 setScaleY(transform.scale);
279 }
280 }
281
282 /** Resets this view's properties */
283 void resetViewProperties() {
284 setTranslationX(0f);
285 setTranslationY(0f);
286 setScaleX(1f);
287 setScaleY(1f);
288 setAlpha(1f);
289 }
290
291 /** Animates this task view as it enters recents */
292 public void animateOnEnterRecents() {
293 mIconView.setCircularClipRadius(0f);
294 mIconView.animateCircularClip(true, 1f,
295 Constants.Values.TaskView.Animation.TaskIconCircularClipInDuration,
296 300, new AccelerateInterpolator(), null);
297 }
298
299 /** Animates this task view as it exits recents */
300 public void animateOnLeavingRecents(final Runnable r) {
301 if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) {
302 mIconView.animateCircularClip(false, 0f,
303 Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration, 0,
304 new DecelerateInterpolator(),
305 new AnimatorListenerAdapter() {
306 @Override
307 public void onAnimationEnd(Animator animation) {
308 r.run();
309 }
310 });
311 } else {
312 mIconView.animate()
313 .alpha(0f)
314 .setDuration(Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration)
315 .setInterpolator(new DecelerateInterpolator())
316 .setListener(
317 new AnimatorListenerAdapter() {
318 @Override
319 public void onAnimationEnd(Animator animation) {
320 r.run();
321 }
322 })
323 .start();
324 }
325 }
326
327 /** Returns the rect we want to clip (it may not be the full rect) */
328 Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) {
329 getHitRect(outRect);
330 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
331 outRect.right = outRect.left + mThumbnailView.getRight();
332 outRect.bottom = outRect.top + mThumbnailView.getBottom();
333 // We need to shrink the next rect by the rounded corners since those are draw on
334 // top of the current view
335 if (accountForRoundedRects) {
336 RecentsConfiguration config = RecentsConfiguration.getInstance();
337 float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
338 outRect.inset((int) radius, (int) radius);
339 }
340 return outRect;
341 }
342
343 /** Enable the hw layers on this task view */
344 void enableHwLayers() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800345 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
346 }
347
348 /** Disable the hw layers on this task view */
349 void disableHwLayers() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800350 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
351 }
352
Winson Chung4d7b0922014-03-13 17:14:17 -0700353 /**** TaskCallbacks Implementation ****/
354
Winson Chung04dfe0d2014-03-14 14:06:29 -0700355 /** Binds this task view to the task */
356 public void onTaskBound(Task t) {
357 mTask = t;
358 mTask.setCallbacks(this);
Winson Chung303e1ff2014-03-07 15:06:19 -0800359 }
360
361 @Override
Winson Chung04dfe0d2014-03-14 14:06:29 -0700362 public void onTaskDataLoaded() {
363 // Bind each of the views to the new task data
364 mThumbnailView.rebindToTask(mTask, false);
365 mIconView.rebindToTask(mTask, false);
Winson Chung4d7b0922014-03-13 17:14:17 -0700366 }
367
368 @Override
Winson Chung04dfe0d2014-03-14 14:06:29 -0700369 public void onTaskDataUnloaded() {
370 // Unbind each of the views from the task data and remove the task callback
371 mTask.setCallbacks(null);
372 mThumbnailView.unbindFromTask();
373 mIconView.unbindFromTask();
Winson Chung4d7b0922014-03-13 17:14:17 -0700374 }
375
376 @Override
Winson Chung303e1ff2014-03-07 15:06:19 -0800377 public void onClick(View v) {
378 mCb.onTaskIconClicked(this);
379 }
380}