TaskView.java revision 47c4c69575e94635a9356e8a1faf4b56a0120452
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
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;
42
43
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};
63                mBarColor = colors[mTask.key.intent.getComponent().getPackageName().length() % colors.length];
64            }
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 */
210public 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
217    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
250    /** Set callback */
251    void setCallbacks(TaskViewCallbacks cb) {
252        mCb = cb;
253    }
254
255    /** 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        if (Constants.Values.TaskView.AnimateFrontTaskIconOnEnterUseClip) {
294            mIconView.setCircularClipRadius(0f);
295            mIconView.animateCircularClip(true, 1f,
296                Constants.Values.TaskView.Animation.TaskIconOnEnterDuration,
297                300, new AccelerateInterpolator(), null);
298        } else {
299            RecentsConfiguration config = RecentsConfiguration.getInstance();
300            int translate = config.pxFromDp(10);
301            mIconView.setScaleX(1.25f);
302            mIconView.setScaleY(1.25f);
303            mIconView.setAlpha(0f);
304            mIconView.setTranslationX(translate / 2);
305            mIconView.setTranslationY(-translate);
306            mIconView.animate()
307                    .alpha(1f)
308                    .scaleX(1f)
309                    .scaleY(1f)
310                    .translationX(0)
311                    .translationY(0)
312                    .setStartDelay(235)
313                    .setDuration(Constants.Values.TaskView.Animation.TaskIconOnEnterDuration)
314                    .start();
315        }
316    }
317
318    /** Animates this task view as it exits recents */
319    public void animateOnLeavingRecents(final Runnable r) {
320        if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) {
321            mIconView.animateCircularClip(false, 0f,
322                Constants.Values.TaskView.Animation.TaskIconOnLeavingDuration, 0,
323                new DecelerateInterpolator(),
324                new AnimatorListenerAdapter() {
325                    @Override
326                    public void onAnimationEnd(Animator animation) {
327                        r.run();
328                    }
329                });
330        } else {
331            mIconView.animate()
332                .alpha(0f)
333                .setDuration(Constants.Values.TaskView.Animation.TaskIconOnLeavingDuration)
334                .setInterpolator(new DecelerateInterpolator())
335                .setListener(
336                    new AnimatorListenerAdapter() {
337                        @Override
338                        public void onAnimationEnd(Animator animation) {
339                            r.run();
340                        }
341                    })
342                .start();
343        }
344    }
345
346    /** Returns the rect we want to clip (it may not be the full rect) */
347    Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) {
348        getHitRect(outRect);
349        // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
350        outRect.right = outRect.left + mThumbnailView.getRight();
351        outRect.bottom = outRect.top + mThumbnailView.getBottom();
352        // We need to shrink the next rect by the rounded corners since those are draw on
353        // top of the current view
354        if (accountForRoundedRects) {
355            RecentsConfiguration config = RecentsConfiguration.getInstance();
356            float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
357            outRect.inset((int) radius, (int) radius);
358        }
359        return outRect;
360    }
361
362    /** Enable the hw layers on this task view */
363    void enableHwLayers() {
364        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
365    }
366
367    /** Disable the hw layers on this task view */
368    void disableHwLayers() {
369        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
370    }
371
372    /**** TaskCallbacks Implementation ****/
373
374    /** Binds this task view to the task */
375    public void onTaskBound(Task t) {
376        mTask = t;
377        mTask.setCallbacks(this);
378    }
379
380    @Override
381    public void onTaskDataLoaded() {
382        // Bind each of the views to the new task data
383        mThumbnailView.rebindToTask(mTask, false);
384        mIconView.rebindToTask(mTask, false);
385    }
386
387    @Override
388    public void onTaskDataUnloaded() {
389        // Unbind each of the views from the task data and remove the task callback
390        mTask.setCallbacks(null);
391        mThumbnailView.unbindFromTask();
392        mIconView.unbindFromTask();
393    }
394
395    @Override
396    public void onClick(View v) {
397        mCb.onTaskIconClicked(this);
398    }
399}