TaskStackViewTouchHandler.java revision 480dd72daf927283997bdb4060091299add66832
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.content.Context; 20import android.view.MotionEvent; 21import android.view.VelocityTracker; 22import android.view.View; 23import android.view.ViewConfiguration; 24import android.view.ViewParent; 25import com.android.systemui.recents.Constants; 26 27/* Handles touch events for a TaskStackView. */ 28class TaskStackViewTouchHandler implements SwipeHelper.Callback { 29 static int INACTIVE_POINTER_ID = -1; 30 31 TaskStackView mSv; 32 VelocityTracker mVelocityTracker; 33 34 boolean mIsScrolling; 35 36 int mInitialMotionX, mInitialMotionY; 37 int mLastMotionX, mLastMotionY; 38 int mActivePointerId = INACTIVE_POINTER_ID; 39 TaskView mActiveTaskView = null; 40 41 int mTotalScrollMotion; 42 int mMinimumVelocity; 43 int mMaximumVelocity; 44 // The scroll touch slop is used to calculate when we start scrolling 45 int mScrollTouchSlop; 46 // The page touch slop is used to calculate when we start swiping 47 float mPagingTouchSlop; 48 49 SwipeHelper mSwipeHelper; 50 boolean mInterceptedBySwipeHelper; 51 52 public TaskStackViewTouchHandler(Context context, TaskStackView sv) { 53 ViewConfiguration configuration = ViewConfiguration.get(context); 54 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 55 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 56 mScrollTouchSlop = configuration.getScaledTouchSlop(); 57 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 58 mSv = sv; 59 60 61 float densityScale = context.getResources().getDisplayMetrics().density; 62 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); 63 mSwipeHelper.setMinAlpha(1f); 64 } 65 66 /** Velocity tracker helpers */ 67 void initOrResetVelocityTracker() { 68 if (mVelocityTracker == null) { 69 mVelocityTracker = VelocityTracker.obtain(); 70 } else { 71 mVelocityTracker.clear(); 72 } 73 } 74 void initVelocityTrackerIfNotExists() { 75 if (mVelocityTracker == null) { 76 mVelocityTracker = VelocityTracker.obtain(); 77 } 78 } 79 void recycleVelocityTracker() { 80 if (mVelocityTracker != null) { 81 mVelocityTracker.recycle(); 82 mVelocityTracker = null; 83 } 84 } 85 86 /** Returns the view at the specified coordinates */ 87 TaskView findViewAtPoint(int x, int y) { 88 int childCount = mSv.getChildCount(); 89 for (int i = childCount - 1; i >= 0; i--) { 90 TaskView tv = (TaskView) mSv.getChildAt(i); 91 if (tv.getVisibility() == View.VISIBLE) { 92 if (mSv.isTransformedTouchPointInView(x, y, tv)) { 93 return tv; 94 } 95 } 96 } 97 return null; 98 } 99 100 /** Touch preprocessing for handling below */ 101 public boolean onInterceptTouchEvent(MotionEvent ev) { 102 // Return early if we have no children 103 boolean hasChildren = (mSv.getChildCount() > 0); 104 if (!hasChildren) { 105 return false; 106 } 107 108 // Pass through to swipe helper if we are swiping 109 mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); 110 if (mInterceptedBySwipeHelper) { 111 return true; 112 } 113 114 boolean wasScrolling = !mSv.mScroller.isFinished() || 115 (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); 116 int action = ev.getAction(); 117 switch (action & MotionEvent.ACTION_MASK) { 118 case MotionEvent.ACTION_DOWN: { 119 // Save the touch down info 120 mInitialMotionX = mLastMotionX = (int) ev.getX(); 121 mInitialMotionY = mLastMotionY = (int) ev.getY(); 122 mActivePointerId = ev.getPointerId(0); 123 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 124 // Stop the current scroll if it is still flinging 125 mSv.abortScroller(); 126 mSv.abortBoundScrollAnimation(); 127 // Initialize the velocity tracker 128 initOrResetVelocityTracker(); 129 mVelocityTracker.addMovement(ev); 130 // Check if the scroller is finished yet 131 mIsScrolling = !mSv.mScroller.isFinished(); 132 break; 133 } 134 case MotionEvent.ACTION_MOVE: { 135 if (mActivePointerId == INACTIVE_POINTER_ID) break; 136 137 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 138 int y = (int) ev.getY(activePointerIndex); 139 int x = (int) ev.getX(activePointerIndex); 140 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { 141 // Save the touch move info 142 mIsScrolling = true; 143 // Initialize the velocity tracker if necessary 144 initVelocityTrackerIfNotExists(); 145 mVelocityTracker.addMovement(ev); 146 // Disallow parents from intercepting touch events 147 final ViewParent parent = mSv.getParent(); 148 if (parent != null) { 149 parent.requestDisallowInterceptTouchEvent(true); 150 } 151 } 152 153 mLastMotionX = x; 154 mLastMotionY = y; 155 break; 156 } 157 case MotionEvent.ACTION_CANCEL: 158 case MotionEvent.ACTION_UP: { 159 // Animate the scroll back if we've cancelled 160 mSv.animateBoundScroll(); 161 // Reset the drag state and the velocity tracker 162 mIsScrolling = false; 163 mActivePointerId = INACTIVE_POINTER_ID; 164 mActiveTaskView = null; 165 mTotalScrollMotion = 0; 166 recycleVelocityTracker(); 167 break; 168 } 169 } 170 171 return wasScrolling || mIsScrolling; 172 } 173 174 /** Handles touch events once we have intercepted them */ 175 public boolean onTouchEvent(MotionEvent ev) { 176 // Short circuit if we have no children 177 boolean hasChildren = (mSv.getChildCount() > 0); 178 if (!hasChildren) { 179 return false; 180 } 181 182 // Pass through to swipe helper if we are swiping 183 if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { 184 return true; 185 } 186 187 // Update the velocity tracker 188 initVelocityTrackerIfNotExists(); 189 mVelocityTracker.addMovement(ev); 190 191 int action = ev.getAction(); 192 switch (action & MotionEvent.ACTION_MASK) { 193 case MotionEvent.ACTION_DOWN: { 194 // Save the touch down info 195 mInitialMotionX = mLastMotionX = (int) ev.getX(); 196 mInitialMotionY = mLastMotionY = (int) ev.getY(); 197 mActivePointerId = ev.getPointerId(0); 198 mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); 199 // Stop the current scroll if it is still flinging 200 mSv.abortScroller(); 201 mSv.abortBoundScrollAnimation(); 202 // Initialize the velocity tracker 203 initOrResetVelocityTracker(); 204 mVelocityTracker.addMovement(ev); 205 // Disallow parents from intercepting touch events 206 final ViewParent parent = mSv.getParent(); 207 if (parent != null) { 208 parent.requestDisallowInterceptTouchEvent(true); 209 } 210 break; 211 } 212 case MotionEvent.ACTION_POINTER_DOWN: { 213 final int index = ev.getActionIndex(); 214 mActivePointerId = ev.getPointerId(index); 215 mLastMotionX = (int) ev.getX(index); 216 mLastMotionY = (int) ev.getY(index); 217 break; 218 } 219 case MotionEvent.ACTION_MOVE: { 220 if (mActivePointerId == INACTIVE_POINTER_ID) break; 221 222 int activePointerIndex = ev.findPointerIndex(mActivePointerId); 223 int x = (int) ev.getX(activePointerIndex); 224 int y = (int) ev.getY(activePointerIndex); 225 int yTotal = Math.abs(y - mInitialMotionY); 226 int deltaY = mLastMotionY - y; 227 if (!mIsScrolling) { 228 if (yTotal > mScrollTouchSlop) { 229 mIsScrolling = true; 230 // Initialize the velocity tracker 231 initOrResetVelocityTracker(); 232 mVelocityTracker.addMovement(ev); 233 // Disallow parents from intercepting touch events 234 final ViewParent parent = mSv.getParent(); 235 if (parent != null) { 236 parent.requestDisallowInterceptTouchEvent(true); 237 } 238 } 239 } 240 if (mIsScrolling) { 241 int curStackScroll = mSv.getStackScroll(); 242 int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY); 243 if (overScrollAmount != 0) { 244 // Bound the overscroll to a fixed amount, and inversely scale the y-movement 245 // relative to how close we are to the max overscroll 246 float maxOverScroll = mSv.mStackAlgorithm.mTaskRect.height() / 3f; 247 deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount) 248 / maxOverScroll))); 249 } 250 mSv.setStackScroll(curStackScroll + deltaY); 251 if (mSv.isScrollOutOfBounds()) { 252 mVelocityTracker.clear(); 253 } 254 } 255 mLastMotionX = x; 256 mLastMotionY = y; 257 mTotalScrollMotion += Math.abs(deltaY); 258 break; 259 } 260 case MotionEvent.ACTION_UP: { 261 final VelocityTracker velocityTracker = mVelocityTracker; 262 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 263 int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); 264 265 if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { 266 int overscrollRange = (int) (Math.min(1f, 267 Math.abs((float) velocity / mMaximumVelocity)) * 268 Constants.Values.TaskStackView.TaskStackOverscrollRange); 269 // Fling scroll 270 mSv.mScroller.fling(0, mSv.getStackScroll(), 271 0, -velocity, 272 0, 0, 273 mSv.mMinScroll, mSv.mMaxScroll, 274 0, overscrollRange); 275 // Invalidate to kick off computeScroll 276 mSv.invalidate(mSv.mStackAlgorithm.mStackRect); 277 } else if (mSv.isScrollOutOfBounds()) { 278 // Animate the scroll back into bounds 279 mSv.animateBoundScroll(); 280 } 281 282 mActivePointerId = INACTIVE_POINTER_ID; 283 mIsScrolling = false; 284 mTotalScrollMotion = 0; 285 recycleVelocityTracker(); 286 break; 287 } 288 case MotionEvent.ACTION_POINTER_UP: { 289 int pointerIndex = ev.getActionIndex(); 290 int pointerId = ev.getPointerId(pointerIndex); 291 if (pointerId == mActivePointerId) { 292 // Select a new active pointer id and reset the motion state 293 final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; 294 mActivePointerId = ev.getPointerId(newPointerIndex); 295 mLastMotionX = (int) ev.getX(newPointerIndex); 296 mLastMotionY = (int) ev.getY(newPointerIndex); 297 mVelocityTracker.clear(); 298 } 299 break; 300 } 301 case MotionEvent.ACTION_CANCEL: { 302 if (mSv.isScrollOutOfBounds()) { 303 // Animate the scroll back into bounds 304 mSv.animateBoundScroll(); 305 } 306 mActivePointerId = INACTIVE_POINTER_ID; 307 mIsScrolling = false; 308 mTotalScrollMotion = 0; 309 recycleVelocityTracker(); 310 break; 311 } 312 } 313 return true; 314 } 315 316 /**** SwipeHelper Implementation ****/ 317 318 @Override 319 public View getChildAtPosition(MotionEvent ev) { 320 return findViewAtPoint((int) ev.getX(), (int) ev.getY()); 321 } 322 323 @Override 324 public boolean canChildBeDismissed(View v) { 325 return true; 326 } 327 328 @Override 329 public void onBeginDrag(View v) { 330 TaskView tv = (TaskView) v; 331 // Disable clipping with the stack while we are swiping 332 tv.setClipViewInStack(false); 333 // Disallow touch events from this task view 334 tv.setTouchEnabled(false); 335 // Hide the footer 336 tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration); 337 // Disallow parents from intercepting touch events 338 final ViewParent parent = mSv.getParent(); 339 if (parent != null) { 340 parent.requestDisallowInterceptTouchEvent(true); 341 } 342 } 343 344 @Override 345 public void onSwipeChanged(View v, float delta) { 346 // Do nothing 347 } 348 349 @Override 350 public void onChildDismissed(View v) { 351 TaskView tv = (TaskView) v; 352 // Re-enable clipping with the stack (we will reuse this view) 353 tv.setClipViewInStack(true); 354 // Re-enable touch events from this task view 355 tv.setTouchEnabled(true); 356 // Remove the task view from the stack 357 mSv.onTaskViewDismissed(tv); 358 } 359 360 @Override 361 public void onSnapBackCompleted(View v) { 362 TaskView tv = (TaskView) v; 363 // Re-enable clipping with the stack 364 tv.setClipViewInStack(true); 365 // Re-enable touch events from this task view 366 tv.setTouchEnabled(true); 367 // Restore the footer 368 tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration); 369 } 370 371 @Override 372 public void onDragCancelled(View v) { 373 // Do nothing 374 } 375} 376