TaskView.java revision f5e22e71cb5f8699a4312c797af068f655cbe629
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.Path; 24import android.graphics.Point; 25import android.graphics.Rect; 26import android.graphics.RectF; 27import android.util.AttributeSet; 28import android.view.MotionEvent; 29import android.view.View; 30import android.view.animation.AccelerateInterpolator; 31import android.widget.FrameLayout; 32import com.android.systemui.R; 33import com.android.systemui.recents.BakedBezierInterpolator; 34import com.android.systemui.recents.Constants; 35import com.android.systemui.recents.RecentsConfiguration; 36import com.android.systemui.recents.Utilities; 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 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 113 @Override 114 public boolean onInterceptTouchEvent(MotionEvent ev) { 115 switch (ev.getAction()) { 116 case MotionEvent.ACTION_DOWN: 117 case MotionEvent.ACTION_MOVE: 118 mLastTouchDown.set((int) ev.getX(), (int) ev.getY()); 119 break; 120 } 121 return super.onInterceptTouchEvent(ev); 122 } 123 124 /** Set callback */ 125 void setCallbacks(TaskViewCallbacks cb) { 126 mCb = cb; 127 } 128 129 /** Gets the task */ 130 Task getTask() { 131 return mTask; 132 } 133 134 /** Synchronizes this view's properties with the task's transform */ 135 void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform, 136 TaskViewTransform toTransform, int duration) { 137 if (duration > 0) { 138 if (animateFromTransform != null) { 139 setTranslationY(animateFromTransform.translationY); 140 setScaleX(animateFromTransform.scale); 141 setScaleY(animateFromTransform.scale); 142 setAlpha(animateFromTransform.alpha); 143 } 144 animate().translationY(toTransform.translationY) 145 .scaleX(toTransform.scale) 146 .scaleY(toTransform.scale) 147 .alpha(toTransform.alpha) 148 .setDuration(duration) 149 .setInterpolator(BakedBezierInterpolator.INSTANCE) 150 .withLayer() 151 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 152 @Override 153 public void onAnimationUpdate(ValueAnimator animation) { 154 updateDimOverlayFromScale(); 155 } 156 }) 157 .start(); 158 } else { 159 setTranslationY(toTransform.translationY); 160 setScaleX(toTransform.scale); 161 setScaleY(toTransform.scale); 162 setAlpha(toTransform.alpha); 163 } 164 updateDimOverlayFromScale(); 165 invalidate(); 166 } 167 168 /** Resets this view's properties */ 169 void resetViewProperties() { 170 setTranslationX(0f); 171 setTranslationY(0f); 172 setScaleX(1f); 173 setScaleY(1f); 174 setAlpha(1f); 175 invalidate(); 176 } 177 178 /** 179 * When we are un/filtering, this method will set up the transform that we are animating to, 180 * in order to hide the task. 181 */ 182 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) { 183 // Fade the view out and slide it away 184 toTransform.alpha = 0f; 185 toTransform.translationY += 200; 186 } 187 188 /** 189 * When we are un/filtering, this method will setup the transform that we are animating from, 190 * in order to show the task. 191 */ 192 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) { 193 // Fade the view in 194 fromTransform.alpha = 0f; 195 } 196 197 /** Animates this task view as it enters recents */ 198 public void animateOnEnterRecents() { 199 RecentsConfiguration config = RecentsConfiguration.getInstance(); 200 mBarView.setAlpha(0f); 201 mBarView.animate() 202 .alpha(1f) 203 .setStartDelay(235) 204 .setInterpolator(BakedBezierInterpolator.INSTANCE) 205 .setDuration(config.taskBarEnterAnimDuration) 206 .withLayer() 207 .start(); 208 } 209 210 /** Animates this task view as it exits recents */ 211 public void animateOnLeavingRecents(final Runnable r) { 212 RecentsConfiguration config = RecentsConfiguration.getInstance(); 213 mBarView.animate() 214 .alpha(0f) 215 .setStartDelay(0) 216 .setInterpolator(BakedBezierInterpolator.INSTANCE) 217 .setDuration(config.taskBarExitAnimDuration) 218 .withLayer() 219 .withEndAction(new Runnable() { 220 @Override 221 public void run() { 222 post(r); 223 } 224 }) 225 .start(); 226 } 227 228 /** Returns the rect we want to clip (it may not be the full rect) */ 229 Rect getClippingRect(Rect outRect) { 230 getHitRect(outRect); 231 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster 232 outRect.right = outRect.left + mThumbnailView.getRight(); 233 outRect.bottom = outRect.top + mThumbnailView.getBottom(); 234 return outRect; 235 } 236 237 /** Returns whether this task has an info pane visible */ 238 boolean isInfoPaneVisible() { 239 return mTaskInfoPaneVisible; 240 } 241 242 /** Shows the info pane if it is not visible. */ 243 void showInfoPane(Rect taskVisibleRect) { 244 if (mTaskInfoPaneVisible) return; 245 246 // Remove the bar view from the visible rect and update the info pane contents 247 taskVisibleRect.top += mBarView.getMeasuredHeight(); 248 mInfoView.updateContents(taskVisibleRect); 249 250 // Show the info pane and animate it into view 251 mInfoView.setVisibility(View.VISIBLE); 252 mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true); 253 mInfoView.setOnClickListener(this); 254 mTaskInfoPaneVisible = true; 255 256 // Notify any callbacks 257 if (mCb != null) { 258 mCb.onTaskInfoPanelShown(this); 259 } 260 } 261 262 /** Hides the info pane if it is visible. */ 263 void hideInfoPane() { 264 if (!mTaskInfoPaneVisible) return; 265 RecentsConfiguration config = RecentsConfiguration.getInstance(); 266 267 // Cancel any circular clip animation 268 mInfoView.cancelCircularClipAnimation(); 269 270 // Animate the info pane out 271 mInfoView.animate() 272 .alpha(0f) 273 .setDuration(config.taskViewInfoPaneAnimDuration) 274 .setInterpolator(BakedBezierInterpolator.INSTANCE) 275 .withLayer() 276 .withEndAction(new Runnable() { 277 @Override 278 public void run() { 279 mInfoView.setVisibility(View.INVISIBLE); 280 mInfoView.setOnClickListener(null); 281 282 mInfoView.setAlpha(1f); 283 } 284 }) 285 .start(); 286 mTaskInfoPaneVisible = false; 287 288 // Notify any callbacks 289 if (mCb != null) { 290 mCb.onTaskInfoPanelHidden(this); 291 } 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 /** Update the dim as a function of the scale of this view. */ 305 void updateDimOverlayFromScale() { 306 float minScale = Constants.Values.TaskStackView.StackPeekMinScale; 307 float scaleRange = 1f - minScale; 308 float dim = (1f - getScaleX()) / scaleRange; 309 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f)); 310 mDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255))); 311 invalidate(); 312 } 313 314 @Override 315 public void draw(Canvas canvas) { 316 // Apply the rounded rect clip path on the whole view 317 canvas.clipPath(mRoundedRectClipPath); 318 319 super.draw(canvas); 320 321 // Apply the dim if necessary 322 if (mDim > 0) { 323 canvas.drawColor(mDim << 24); 324 } 325 } 326 327 /**** TaskCallbacks Implementation ****/ 328 329 /** Binds this task view to the task */ 330 public void onTaskBound(Task t) { 331 mTask = t; 332 mTask.setCallbacks(this); 333 } 334 335 @Override 336 public void onTaskDataLoaded(boolean reloadingTaskData) { 337 if (mThumbnailView != null && mBarView != null && mInfoView != null) { 338 // Bind each of the views to the new task data 339 mThumbnailView.rebindToTask(mTask, reloadingTaskData); 340 mBarView.rebindToTask(mTask, reloadingTaskData); 341 mInfoView.rebindToTask(mTask, reloadingTaskData); 342 // Rebind any listeners 343 mBarView.mApplicationIcon.setOnClickListener(this); 344 mInfoView.mAppInfoButton.setOnClickListener(this); 345 } 346 mTaskDataLoaded = true; 347 } 348 349 @Override 350 public void onTaskDataUnloaded() { 351 if (mThumbnailView != null && mBarView != null && mInfoView != null) { 352 // Unbind each of the views from the task data and remove the task callback 353 mTask.setCallbacks(null); 354 mThumbnailView.unbindFromTask(); 355 mBarView.unbindFromTask(); 356 // Unbind any listeners 357 mBarView.mApplicationIcon.setOnClickListener(null); 358 mInfoView.mAppInfoButton.setOnClickListener(null); 359 } 360 mTaskDataLoaded = false; 361 } 362 363 @Override 364 public void onClick(View v) { 365 if (v == mInfoView) { 366 // Do nothing 367 } else if (v == mBarView.mApplicationIcon) { 368 mCb.onTaskIconClicked(this); 369 } else if (v == mInfoView.mAppInfoButton) { 370 mCb.onTaskAppInfoClicked(this); 371 } 372 } 373}