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