TaskView.java revision 4d7b092a866d2fce3e11b5a12cda2b87a83af52d
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; 42import com.android.systemui.recents.model.TaskCallbacks; 43 44/** The TaskView callbacks */ 45interface TaskViewCallbacks { 46 public void onTaskIconClicked(TaskView tv); 47 // public void onTaskViewReboundToTask(TaskView tv, Task t); 48} 49 50/** The task thumbnail view */ 51class TaskThumbnailView extends ImageView { 52 Task mTask; 53 int mBarColor; 54 55 Path mRoundedRectClipPath = new Path(); 56 57 public TaskThumbnailView(Context context) { 58 super(context); 59 setScaleType(ScaleType.FIT_XY); 60 } 61 62 /** Binds the thumbnail view to the task */ 63 void rebindToTask(Task t, boolean animate) { 64 mTask = t; 65 if (t.thumbnail != null) { 66 // Update the bar color 67 if (Constants.Values.TaskView.DrawColoredTaskBars) { 68 int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7}; 69 mBarColor = colors[mTask.intent.getComponent().getPackageName().length() % colors.length]; 70 } 71 72 setImageBitmap(t.thumbnail); 73 if (animate) { 74 setAlpha(0f); 75 animate().alpha(1f) 76 .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration) 77 .start(); 78 } 79 } 80 } 81 82 /** Unbinds the thumbnail view from the task */ 83 void unbindFromTask() { 84 mTask = null; 85 setImageDrawable(null); 86 } 87 88 @Override 89 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 90 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 91 92 // Update the rounded rect clip path 93 RecentsConfiguration config = RecentsConfiguration.getInstance(); 94 float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps); 95 mRoundedRectClipPath.reset(); 96 mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), 97 radius, radius, Path.Direction.CW); 98 } 99 100 @Override 101 protected void onDraw(Canvas canvas) { 102 if (Constants.Values.TaskView.UseRoundedCorners) { 103 canvas.clipPath(mRoundedRectClipPath); 104 } 105 106 super.onDraw(canvas); 107 108 if (Constants.Values.TaskView.DrawColoredTaskBars) { 109 RecentsConfiguration config = RecentsConfiguration.getInstance(); 110 int taskBarHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps); 111 // XXX: If we actually use this, this should be pulled out into a TextView that we 112 // inflate 113 114 // Draw the task bar 115 Rect r = new Rect(); 116 Paint p = new Paint(); 117 p.setAntiAlias(true); 118 p.setSubpixelText(true); 119 p.setColor(mBarColor); 120 p.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); 121 canvas.drawRect(0, 0, getMeasuredWidth(), taskBarHeight, p); 122 p.setColor(0xFFffffff); 123 p.setTextSize(68); 124 p.getTextBounds("X", 0, 1, r); 125 int offset = (int) (taskBarHeight - r.height()) / 2; 126 canvas.drawText(mTask.title, offset, offset + r.height(), p); 127 } 128 } 129} 130 131/* The task icon view */ 132class TaskIconView extends ImageView { 133 Task mTask; 134 135 Path mClipPath = new Path(); 136 float mClipRadius; 137 Point mClipOrigin = new Point(); 138 ObjectAnimator mCircularClipAnimator; 139 140 public TaskIconView(Context context) { 141 super(context); 142 mClipPath = new Path(); 143 mClipRadius = 1f; 144 } 145 146 /** Binds the icon view to the task */ 147 void rebindToTask(Task t, boolean animate) { 148 mTask = t; 149 if (t.icon != null) { 150 setImageDrawable(t.icon); 151 if (animate) { 152 setAlpha(0f); 153 animate().alpha(1f) 154 .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration) 155 .start(); 156 } 157 } 158 } 159 160 /** Unbinds the icon view from the task */ 161 void unbindFromTask() { 162 mTask = null; 163 setImageDrawable(null); 164 } 165 166 /** Sets the circular clip radius on the icon */ 167 public void setCircularClipRadius(float r) { 168 Console.log(Constants.DebugFlags.UI.Clipping, "[TaskView|setCircularClip]", "" + r); 169 mClipRadius = r; 170 invalidate(); 171 } 172 173 /** Gets the circular clip radius on the icon */ 174 public float getCircularClipRadius() { 175 return mClipRadius; 176 } 177 178 /** Animates the circular clip radius on the icon */ 179 void animateCircularClip(boolean brNotTl, float newRadius, int duration, int startDelay, 180 TimeInterpolator interpolator, 181 AnimatorListenerAdapter listener) { 182 if (mCircularClipAnimator != null) { 183 mCircularClipAnimator.cancel(); 184 mCircularClipAnimator.removeAllListeners(); 185 } 186 if (brNotTl) { 187 mClipOrigin.set(0, 0); 188 } else { 189 mClipOrigin.set(getMeasuredWidth(), getMeasuredHeight()); 190 } 191 mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", newRadius); 192 mCircularClipAnimator.setStartDelay(startDelay); 193 mCircularClipAnimator.setDuration(duration); 194 mCircularClipAnimator.setInterpolator(interpolator); 195 if (listener != null) { 196 mCircularClipAnimator.addListener(listener); 197 } 198 mCircularClipAnimator.start(); 199 } 200 201 @Override 202 protected void onDraw(Canvas canvas) { 203 int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); 204 int width = getMeasuredWidth(); 205 int height = getMeasuredHeight(); 206 int maxSize = (int) Math.ceil(Math.sqrt(width * width + height * height)); 207 mClipPath.reset(); 208 mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * maxSize, Path.Direction.CW); 209 canvas.clipPath(mClipPath); 210 super.onDraw(canvas); 211 canvas.restoreToCount(saveCount); 212 } 213} 214 215/* A task view */ 216public class TaskView extends FrameLayout implements View.OnClickListener, TaskCallbacks { 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 the task and callback */ 251 void bindToTask(Task t, TaskViewCallbacks cb) { 252 mTask = t; 253 mTask.setCallbacks(this); 254 mCb = cb; 255 } 256 257 /** Actually synchronizes the model data into the views */ 258 private void syncToTask() { 259 mThumbnailView.rebindToTask(mTask, false); 260 mIconView.rebindToTask(mTask, false); 261 } 262 263 /** Unset the task and callback */ 264 private void unbindFromTask() { 265 mTask.setCallbacks(null); 266 mThumbnailView.unbindFromTask(); 267 mIconView.unbindFromTask(); 268 } 269 270 /** Gets the task */ 271 Task getTask() { 272 return mTask; 273 } 274 275 /** Synchronizes this view's properties with the task's transform */ 276 void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform, 277 TaskViewTransform transform, int duration) { 278 if (duration > 0) { 279 if (animateFromTransform != null) { 280 setTranslationY(animateFromTransform.translationY); 281 setScaleX(animateFromTransform.scale); 282 setScaleY(animateFromTransform.scale); 283 } 284 animate().translationY(transform.translationY) 285 .scaleX(transform.scale) 286 .scaleY(transform.scale) 287 .setDuration(duration) 288 .setInterpolator(new AccelerateDecelerateInterpolator()) 289 .start(); 290 } else { 291 setTranslationY(transform.translationY); 292 setScaleX(transform.scale); 293 setScaleY(transform.scale); 294 } 295 } 296 297 /** Resets this view's properties */ 298 void resetViewProperties() { 299 setTranslationX(0f); 300 setTranslationY(0f); 301 setScaleX(1f); 302 setScaleY(1f); 303 setAlpha(1f); 304 } 305 306 /** Animates this task view as it enters recents */ 307 public void animateOnEnterRecents() { 308 mIconView.setCircularClipRadius(0f); 309 mIconView.animateCircularClip(true, 1f, 310 Constants.Values.TaskView.Animation.TaskIconCircularClipInDuration, 311 300, new AccelerateInterpolator(), null); 312 } 313 314 /** Animates this task view as it exits recents */ 315 public void animateOnLeavingRecents(final Runnable r) { 316 if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) { 317 mIconView.animateCircularClip(false, 0f, 318 Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration, 0, 319 new DecelerateInterpolator(), 320 new AnimatorListenerAdapter() { 321 @Override 322 public void onAnimationEnd(Animator animation) { 323 r.run(); 324 } 325 }); 326 } else { 327 mIconView.animate() 328 .alpha(0f) 329 .setDuration(Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration) 330 .setInterpolator(new DecelerateInterpolator()) 331 .setListener( 332 new AnimatorListenerAdapter() { 333 @Override 334 public void onAnimationEnd(Animator animation) { 335 r.run(); 336 } 337 }) 338 .start(); 339 } 340 } 341 342 /** Returns the rect we want to clip (it may not be the full rect) */ 343 Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) { 344 getHitRect(outRect); 345 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster 346 outRect.right = outRect.left + mThumbnailView.getRight(); 347 outRect.bottom = outRect.top + mThumbnailView.getBottom(); 348 // We need to shrink the next rect by the rounded corners since those are draw on 349 // top of the current view 350 if (accountForRoundedRects) { 351 RecentsConfiguration config = RecentsConfiguration.getInstance(); 352 float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps); 353 outRect.inset((int) radius, (int) radius); 354 } 355 return outRect; 356 } 357 358 /** Enable the hw layers on this task view */ 359 void enableHwLayers() { 360 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 361 } 362 363 /** Disable the hw layers on this task view */ 364 void disableHwLayers() { 365 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null); 366 } 367 368 /**** TaskCallbacks Implementation ****/ 369 370 @Override 371 public void onTaskDataChanged(Task task) { 372 Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading, 373 "[TaskView|onTaskDataChanged]", task); 374 375 // Only update this task view if the changed task is the same as the task for this view 376 if (mTask == task) { 377 mThumbnailView.rebindToTask(mTask, true); 378 mIconView.rebindToTask(mTask, true); 379 } 380 } 381 382 @Override 383 public void onTaskBound() { 384 syncToTask(); 385 } 386 387 @Override 388 public void onTaskUnbound() { 389 unbindFromTask(); 390 } 391 392 @Override 393 public void onClick(View v) { 394 mCb.onTaskIconClicked(this); 395 } 396}