TaskView.java revision 1e8d71b605b4872e93200706a80a88a3ff25498c
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.TimeInterpolator; 20import android.animation.ValueAnimator; 21import android.annotation.Nullable; 22import android.content.Context; 23import android.graphics.Canvas; 24import android.graphics.Outline; 25import android.graphics.Path; 26import android.graphics.Point; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.util.AttributeSet; 30import android.view.MotionEvent; 31import android.view.View; 32import android.view.animation.AccelerateInterpolator; 33import android.widget.FrameLayout; 34import com.android.systemui.R; 35import com.android.systemui.recents.Constants; 36import com.android.systemui.recents.RecentsConfiguration; 37import com.android.systemui.recents.model.Task; 38 39 40/* A task view */ 41public class TaskView extends FrameLayout implements View.OnClickListener, 42 Task.TaskCallbacks { 43 /** The TaskView callbacks */ 44 interface TaskViewCallbacks { 45 public void onTaskIconClicked(TaskView tv); 46 public void onTaskInfoPanelShown(TaskView tv); 47 public void onTaskInfoPanelHidden(TaskView tv); 48 public void onTaskAppInfoClicked(TaskView tv); 49 public void onTaskDismissed(TaskView tv); 50 51 // public void onTaskViewReboundToTask(TaskView tv, Task t); 52 } 53 54 int mDim; 55 int mMaxDim; 56 TimeInterpolator mDimInterpolator = new AccelerateInterpolator(); 57 58 Task mTask; 59 boolean mTaskDataLoaded; 60 boolean mTaskInfoPaneVisible; 61 boolean mIsFocused; 62 Point mLastTouchDown = new Point(); 63 Path mRoundedRectClipPath = new Path(); 64 65 TaskThumbnailView mThumbnailView; 66 TaskBarView mBarView; 67 TaskInfoView mInfoView; 68 TaskViewCallbacks mCb; 69 70 71 public TaskView(Context context) { 72 this(context, null); 73 } 74 75 public TaskView(Context context, AttributeSet attrs) { 76 this(context, attrs, 0); 77 } 78 79 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) { 80 this(context, attrs, defStyleAttr, 0); 81 } 82 83 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 84 super(context, attrs, defStyleAttr, defStyleRes); 85 setWillNotDraw(false); 86 } 87 88 @Override 89 protected void onFinishInflate() { 90 RecentsConfiguration config = RecentsConfiguration.getInstance(); 91 mMaxDim = config.taskStackMaxDim; 92 93 // Bind the views 94 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail); 95 mBarView = (TaskBarView) findViewById(R.id.task_view_bar); 96 mInfoView = (TaskInfoView) findViewById(R.id.task_view_info_pane); 97 98 if (mTaskDataLoaded) { 99 onTaskDataLoaded(false); 100 } 101 } 102 103 @Override 104 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 105 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 106 107 // Update the rounded rect clip path 108 RecentsConfiguration config = RecentsConfiguration.getInstance(); 109 float radius = config.taskViewRoundedCornerRadiusPx; 110 mRoundedRectClipPath.reset(); 111 mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), 112 radius, radius, Path.Direction.CW); 113 114 // Update the outline 115 Outline o = new Outline(); 116 o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), radius); 117 setOutline(o); 118 } 119 120 @Override 121 public boolean onInterceptTouchEvent(MotionEvent ev) { 122 switch (ev.getAction()) { 123 case MotionEvent.ACTION_DOWN: 124 case MotionEvent.ACTION_MOVE: 125 mLastTouchDown.set((int) ev.getX(), (int) ev.getY()); 126 break; 127 } 128 return super.onInterceptTouchEvent(ev); 129 } 130 131 /** Set callback */ 132 void setCallbacks(TaskViewCallbacks cb) { 133 mCb = cb; 134 } 135 136 /** Gets the task */ 137 Task getTask() { 138 return mTask; 139 } 140 141 /** Synchronizes this view's properties with the task's transform */ 142 void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform, 143 TaskViewTransform toTransform, int duration) { 144 RecentsConfiguration config = RecentsConfiguration.getInstance(); 145 int minZ = config.taskViewTranslationZMinPx; 146 int incZ = config.taskViewTranslationZIncrementPx; 147 148 // Update the bar view 149 mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration); 150 151 // Update this task view 152 if (duration > 0) { 153 if (animateFromTransform != null) { 154 setTranslationY(animateFromTransform.translationY); 155 if (Constants.DebugFlags.App.EnableShadows) { 156 setTranslationZ(Math.max(minZ, minZ + (animateFromTransform.t * incZ))); 157 } 158 setScaleX(animateFromTransform.scale); 159 setScaleY(animateFromTransform.scale); 160 setAlpha(animateFromTransform.alpha); 161 } 162 if (Constants.DebugFlags.App.EnableShadows) { 163 animate().translationZ(Math.max(minZ, minZ + (toTransform.t * incZ))); 164 } 165 animate().translationY(toTransform.translationY) 166 .scaleX(toTransform.scale) 167 .scaleY(toTransform.scale) 168 .alpha(toTransform.alpha) 169 .setDuration(duration) 170 .setInterpolator(config.defaultBezierInterpolator) 171 .withLayer() 172 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 173 @Override 174 public void onAnimationUpdate(ValueAnimator animation) { 175 updateDimOverlayFromScale(); 176 } 177 }) 178 .start(); 179 } else { 180 setTranslationY(toTransform.translationY); 181 if (Constants.DebugFlags.App.EnableShadows) { 182 setTranslationZ(Math.max(minZ, minZ + (toTransform.t * incZ))); 183 } 184 setScaleX(toTransform.scale); 185 setScaleY(toTransform.scale); 186 setAlpha(toTransform.alpha); 187 } 188 updateDimOverlayFromScale(); 189 invalidate(); 190 } 191 192 /** Resets this view's properties */ 193 void resetViewProperties() { 194 setTranslationX(0f); 195 setTranslationY(0f); 196 if (Constants.DebugFlags.App.EnableShadows) { 197 setTranslationZ(0f); 198 } 199 setScaleX(1f); 200 setScaleY(1f); 201 setAlpha(1f); 202 invalidate(); 203 } 204 205 /** 206 * When we are un/filtering, this method will set up the transform that we are animating to, 207 * in order to hide the task. 208 */ 209 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 210 // Fade the view out and slide it away 211 toTransform.alpha = 0f; 212 toTransform.translationY += 200; 213 } 214 215 /** 216 * When we are un/filtering, this method will setup the transform that we are animating from, 217 * in order to show the task. 218 */ 219 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 220 // Fade the view in 221 fromTransform.alpha = 0f; 222 } 223 224 /** Animates this task view as it enters recents */ 225 public void animateOnEnterRecents() { 226 RecentsConfiguration config = RecentsConfiguration.getInstance(); 227 mBarView.setAlpha(0f); 228 mBarView.animate() 229 .alpha(1f) 230 .setStartDelay(250) 231 .setInterpolator(config.defaultBezierInterpolator) 232 .setDuration(config.taskBarEnterAnimDuration) 233 .withLayer() 234 .start(); 235 } 236 237 /** Animates this task view as it exits recents */ 238 public void animateOnLeavingRecents(final Runnable r) { 239 RecentsConfiguration config = RecentsConfiguration.getInstance(); 240 mBarView.animate() 241 .alpha(0f) 242 .setStartDelay(0) 243 .setInterpolator(config.defaultBezierInterpolator) 244 .setDuration(config.taskBarExitAnimDuration) 245 .withLayer() 246 .withEndAction(new Runnable() { 247 @Override 248 public void run() { 249 post(r); 250 } 251 }) 252 .start(); 253 } 254 255 /** Animates the deletion of this task view */ 256 public void animateRemoval(final Runnable r) { 257 RecentsConfiguration config = RecentsConfiguration.getInstance(); 258 animate().translationX(config.taskViewRemoveAnimTranslationXPx) 259 .alpha(0f) 260 .setStartDelay(0) 261 .setInterpolator(config.defaultBezierInterpolator) 262 .setDuration(config.taskViewRemoveAnimDuration) 263 .withLayer() 264 .withEndAction(new Runnable() { 265 @Override 266 public void run() { 267 post(r); 268 } 269 }) 270 .start(); 271 } 272 273 /** Returns the rect we want to clip (it may not be the full rect) */ 274 Rect getClippingRect(Rect outRect) { 275 getHitRect(outRect); 276 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster 277 outRect.right = outRect.left + mThumbnailView.getRight(); 278 outRect.bottom = outRect.top + mThumbnailView.getBottom(); 279 return outRect; 280 } 281 282 /** Returns whether this task has an info pane visible */ 283 boolean isInfoPaneVisible() { 284 return mTaskInfoPaneVisible; 285 } 286 287 /** Shows the info pane if it is not visible. */ 288 void showInfoPane(Rect taskVisibleRect) { 289 if (mTaskInfoPaneVisible) return; 290 291 // Remove the bar view from the visible rect and update the info pane contents 292 taskVisibleRect.top += mBarView.getMeasuredHeight(); 293 mInfoView.updateContents(taskVisibleRect); 294 295 // Show the info pane and animate it into view 296 mInfoView.setVisibility(View.VISIBLE); 297 mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true); 298 mInfoView.setOnClickListener(this); 299 mTaskInfoPaneVisible = true; 300 301 // Notify any callbacks 302 if (mCb != null) { 303 mCb.onTaskInfoPanelShown(this); 304 } 305 } 306 307 /** Hides the info pane if it is visible. */ 308 void hideInfoPane() { 309 if (!mTaskInfoPaneVisible) return; 310 RecentsConfiguration config = RecentsConfiguration.getInstance(); 311 312 // Cancel any circular clip animation 313 mInfoView.cancelCircularClipAnimation(); 314 315 // Animate the info pane out 316 mInfoView.animate() 317 .alpha(0f) 318 .setDuration(config.taskViewInfoPaneAnimDuration) 319 .setInterpolator(config.defaultBezierInterpolator) 320 .withLayer() 321 .withEndAction(new Runnable() { 322 @Override 323 public void run() { 324 mInfoView.setVisibility(View.INVISIBLE); 325 mInfoView.setOnClickListener(null); 326 327 mInfoView.setAlpha(1f); 328 } 329 }) 330 .start(); 331 mTaskInfoPaneVisible = false; 332 333 // Notify any callbacks 334 if (mCb != null) { 335 mCb.onTaskInfoPanelHidden(this); 336 } 337 } 338 339 /** Enable the hw layers on this task view */ 340 void enableHwLayers() { 341 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 342 } 343 344 /** Disable the hw layers on this task view */ 345 void disableHwLayers() { 346 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null); 347 } 348 349 /** Update the dim as a function of the scale of this view. */ 350 void updateDimOverlayFromScale() { 351 float minScale = Constants.Values.TaskStackView.StackPeekMinScale; 352 float scaleRange = 1f - minScale; 353 float dim = (1f - getScaleX()) / scaleRange; 354 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f)); 355 mDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255))); 356 invalidate(); 357 } 358 359 @Override 360 public void draw(Canvas canvas) { 361 // Apply the rounded rect clip path on the whole view 362 canvas.clipPath(mRoundedRectClipPath); 363 364 super.draw(canvas); 365 366 // Apply the dim if necessary 367 if (mDim > 0) { 368 canvas.drawColor(mDim << 24); 369 } 370 } 371 372 /** 373 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen 374 * if the view is not currently visible, or we are in touch state (where we still want to keep 375 * track of focus). 376 */ 377 public void setFocusedTask() { 378 mIsFocused = true; 379 requestFocus(); 380 } 381 382 /** 383 * Updates the explicitly focused state when the view focus changes. 384 */ 385 @Override 386 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 387 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 388 if (!gainFocus) { 389 mIsFocused = false; 390 } 391 } 392 393 /** 394 * Returns whether we have explicitly been focused. 395 */ 396 public boolean isFocusedTask() { 397 return mIsFocused || isFocused(); 398 } 399 400 /**** TaskCallbacks Implementation ****/ 401 402 /** Binds this task view to the task */ 403 public void onTaskBound(Task t) { 404 mTask = t; 405 mTask.setCallbacks(this); 406 } 407 408 @Override 409 public void onTaskDataLoaded(boolean reloadingTaskData) { 410 if (mThumbnailView != null && mBarView != null && mInfoView != null) { 411 // Bind each of the views to the new task data 412 mThumbnailView.rebindToTask(mTask, reloadingTaskData); 413 mBarView.rebindToTask(mTask, reloadingTaskData); 414 mInfoView.rebindToTask(mTask, reloadingTaskData); 415 // Rebind any listeners 416 mBarView.mApplicationIcon.setOnClickListener(this); 417 mBarView.mDismissButton.setOnClickListener(this); 418 mInfoView.mAppInfoButton.setOnClickListener(this); 419 } 420 mTaskDataLoaded = true; 421 } 422 423 @Override 424 public void onTaskDataUnloaded() { 425 if (mThumbnailView != null && mBarView != null && mInfoView != null) { 426 // Unbind each of the views from the task data and remove the task callback 427 mTask.setCallbacks(null); 428 mThumbnailView.unbindFromTask(); 429 mBarView.unbindFromTask(); 430 // Unbind any listeners 431 mBarView.mApplicationIcon.setOnClickListener(null); 432 mInfoView.mAppInfoButton.setOnClickListener(null); 433 } 434 mTaskDataLoaded = false; 435 } 436 437 @Override 438 public void onClick(View v) { 439 if (v == mInfoView) { 440 hideInfoPane(); 441 } else if (v == mBarView.mApplicationIcon) { 442 mCb.onTaskIconClicked(this); 443 } else if (v == mBarView.mDismissButton) { 444 // Animate out the view and call the callback 445 final TaskView tv = this; 446 animateRemoval(new Runnable() { 447 @Override 448 public void run() { 449 mCb.onTaskDismissed(tv); 450 } 451 }); 452 } else if (v == mInfoView.mAppInfoButton) { 453 mCb.onTaskAppInfoClicked(this); 454 } 455 } 456}