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