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}