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}