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