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