/** Focuses the task at the specified index in the stack */ public void scrollToChild(int childIndex) { if (getCurrentChildIndex() == childIndex) return; if (0 <= childIndex && childIndex < mCallback.getData().size()) { // Scroll the view into position (just center it in the curve) float newScroll = mLayoutAlgorithm.getStackScrollForTask(mCallback.getData().get(childIndex)) - 0.5f; newScroll = mStackScroller.getBoundedStackScroll(newScroll); mStackScroller.setStackScroll(newScroll); // Alternate (animated) way // mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, null); } }
/** Requests this task stacks to start it's exit-recents animation. */ public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { // Stop any scrolling mStackScroller.stopScroller(); mStackScroller.stopBoundScrollAnimation(); // Animate all the task views out of view ctx.offscreenTranslationY = mLayoutAlgorithm.mViewRect.bottom - (mLayoutAlgorithm.mTaskRect.top - mLayoutAlgorithm.mViewRect.top); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { DeckChildView tv = (DeckChildView) getChildAt(i); tv.startExitToHomeAnimation(ctx); } }
public void initialize(Callback<T> callback) { mCallback = callback; requestLayout(); mViewPool = new ViewPool<DeckChildView<T>, T>(getContext(), this); mInflater = LayoutInflater.from(getContext()); mLayoutAlgorithm = new DeckViewLayoutAlgorithm<T>(mConfig); mStackScroller = new DeckViewScroller(getContext(), mConfig, mLayoutAlgorithm); mStackScroller.setCallbacks(this); mTouchHandler = new DeckViewTouchHandler(getContext(), this, mConfig, mStackScroller); mUIDozeTrigger = new DozeTrigger( mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { @Override public void run() { // Show the task bar dismiss buttons int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { DeckChildView tv = (DeckChildView) getChildAt(i); tv.startNoUserInteractionAnimation(); } } }); }
/** Synchronizes the views with the model */ boolean synchronizeStackViewsWithModel() { if (mStackViewsDirty) { // Get all the task transforms ArrayList<T> data = mCallback.getData(); float stackScroll = mStackScroller.getStackScroll(); int[] visibleRange = mTmpVisibleRange; boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, data, stackScroll, visibleRange, false); // Return all the invisible children to the pool mTmpTaskViewMap.clear(); int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { DeckChildView<T> tv = (DeckChildView) getChildAt(i); T key = tv.getAttachedKey(); int taskIndex = data.indexOf(key); if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { mTmpTaskViewMap.put(key, tv); } else { mViewPool.returnViewToPool(tv); } } for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) { T key = data.get(i); DeckChildViewTransform transform = mCurrentTaskTransforms.get(i); DeckChildView tv = mTmpTaskViewMap.get(key); if (tv == null) { // TODO Check tv = mViewPool.pickUpViewFromPool(key, key); if (mStackViewsAnimationDuration > 0) { // For items in the list, put them in start animating them from the // approriate ends of the list where they are expected to appear if (Float.compare(transform.p, 0f) <= 0) { mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null); } else { mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null); } tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); } } // Animate the task into place tv.updateViewPropertiesToTaskTransform( mCurrentTaskTransforms.get(i), mStackViewsAnimationDuration, mRequestUpdateClippingListener); } // Reset the request-synchronize params mStackViewsAnimationDuration = 0; mStackViewsDirty = false; mStackViewsClipDirty = true; return true; } return false; }
/** Resets this TaskStackView for reuse. */ void reset() { // Reset the focused task resetFocusedTask(); // Return all the views to the pool int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { DeckChildView<T> tv = (DeckChildView) getChildAt(i); mViewPool.returnViewToPool(tv); } // Mark each task view for relayout if (mViewPool != null) { Iterator<DeckChildView<T>> iter = mViewPool.poolViewIterator(); if (iter != null) { while (iter.hasNext()) { DeckChildView tv = iter.next(); tv.reset(); } } } // Reset the stack state mStackViewsDirty = true; mStackViewsClipDirty = true; mAwaitingFirstLayout = true; mPrevAccessibilityFocusedIndex = -1; if (mUIDozeTrigger != null) { mUIDozeTrigger.stopDozing(); mUIDozeTrigger.resetTrigger(); } mStackScroller.reset(); }
@Override public void computeScroll() { mStackScroller.computeScroll(); // Synchronize the views synchronizeStackViewsWithModel(); clipTaskViews(); // Notify accessibility sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); }
/** Updates the min and max virtual scroll bounds */ void updateMinMaxScroll( boolean boundScrollToNewMinMax, boolean launchedWithAltTab, boolean launchedFromHome) { // Compute the min and max scroll values mLayoutAlgorithm.computeMinMaxScroll(mCallback.getData(), launchedWithAltTab, launchedFromHome); // Debug logging if (true) { mStackScroller.boundScroll(); } }
/** Focuses the task at the specified index in the stack */ void focusTask(int childIndex, boolean scrollToNewPosition, final boolean animateFocusedState) { // Return early if the task is already focused if (childIndex == mFocusedTaskIndex) return; ArrayList<T> data = mCallback.getData(); if (0 <= childIndex && childIndex < data.size()) { mFocusedTaskIndex = childIndex; // Focus the view if possible, otherwise, focus the view after we scroll into position T key = data.get(childIndex); DeckChildView tv = getChildViewForTask(key); Runnable postScrollRunnable = null; if (tv != null) { tv.setFocusedTask(animateFocusedState); } else { postScrollRunnable = new Runnable() { @Override public void run() { DeckChildView tv = getChildViewForTask(mCallback.getData().get(mFocusedTaskIndex)); if (tv != null) { tv.setFocusedTask(animateFocusedState); } } }; } // Scroll the view into position (just center it in the curve) if (scrollToNewPosition) { float newScroll = mLayoutAlgorithm.getStackScrollForTask(key) - 0.5f; newScroll = mStackScroller.getBoundedStackScroll(newScroll); mStackScroller.animateScroll( mStackScroller.getStackScroll(), newScroll, postScrollRunnable); } else { if (postScrollRunnable != null) { postScrollRunnable.run(); } } } }
public void notifyDataSetChanged() { // Get the stack scroll of the task to anchor to (since we are removing something, the front // most task will be our anchor task) T anchorTask = null; float prevAnchorTaskScroll = 0; boolean pullStackForward = mCallback.getData().size() > 0; if (pullStackForward) { anchorTask = mCallback.getData().get(mCallback.getData().size() - 1); prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); } // Update the min/max scroll and animate other task views into their new positions updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome); // Offset the stack by as much as the anchor task would otherwise move back if (pullStackForward) { float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); mStackScroller.setStackScroll( mStackScroller.getStackScroll() + (anchorTaskScroll - prevAnchorTaskScroll)); mStackScroller.boundScroll(); } // Animate all the tasks into place requestSynchronizeStackViewsWithModel(200); T newFrontMostTask = mCallback.getData().size() > 0 ? mCallback.getData().get(mCallback.getData().size() - 1) : null; // Update the new front most task if (newFrontMostTask != null) { DeckChildView<T> frontTv = getChildViewForTask(newFrontMostTask); if (frontTv != null) { frontTv.onTaskBound(newFrontMostTask); } } // If there are no remaining tasks if (mCallback.getData().size() == 0) { mCallback.onNoViewsToDeck(); } }
/** * This is called with the full window width and height to allow stack view children to perform * the full screen transition down. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); // 可见区域 Rect _taskStackBounds = new Rect(); mConfig.getTaskStackBounds( width, height, mConfig.systemInsets.top, mConfig.systemInsets.right, _taskStackBounds); setStackInsetRect(_taskStackBounds); // Compute our stack/task rects // Rect taskStackBounds = new Rect(mTaskStackBounds); // taskStackBounds.bottom -= mConfig.systemInsets.bottom; computeRects( width, height, _taskStackBounds, mConfig.launchedWithAltTab, mConfig.launchedFromHome); // If this is the first layout, then scroll to the front of the stack and synchronize the // stack views immediately to load all the views if (mAwaitingFirstLayout) { mStackScroller.setStackScrollToInitialState(); // requestSynchronizeStackViewsWithModel(); // synchronizeStackViewsWithModel(); } // Measure each of the TaskViews int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { DeckChildView tv = (DeckChildView) getChildAt(i); // if (tv.getBackground() != null) { // tv.getBackground().getPadding(mTmpRect); // } else { // mTmpRect.setEmpty(); // } tv.measure( MeasureSpec.makeMeasureSpec( mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, MeasureSpec.EXACTLY)); // tv.measure( // widthMeasureSpec, // heightMeasureSpec); } setMeasuredDimension(width, height); }
@Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); int childCount = getChildCount(); if (childCount > 0) { DeckChildView<T> backMostTask = (DeckChildView) getChildAt(0); DeckChildView<T> frontMostTask = (DeckChildView) getChildAt(childCount - 1); event.setFromIndex(mCallback.getData().indexOf(backMostTask.getAttachedKey())); event.setToIndex(mCallback.getData().indexOf(frontMostTask.getAttachedKey())); } event.setItemCount(mCallback.getData().size()); event.setScrollY(mStackScroller.mScroller.getCurrY()); event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP)); }
/** Requests this task stacks to start it's enter-recents animation */ public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) { // If we are still waiting to layout, then just defer until then if (mAwaitingFirstLayout) { mStartEnterAnimationRequestedAfterLayout = true; mStartEnterAnimationContext = ctx; return; } if (mCallback.getData().size() > 0) { int childCount = getChildCount(); // Animate all the task views into view for (int i = childCount - 1; i >= 0; i--) { DeckChildView<T> tv = (DeckChildView) getChildAt(i); T key = tv.getAttachedKey(); ctx.currentTaskTransform = new DeckChildViewTransform(); ctx.currentStackViewIndex = i; ctx.currentStackViewCount = childCount; ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect; // TODO: this needs to go ctx.currentTaskOccludesLaunchTarget = false; ctx.updateListener = mRequestUpdateClippingListener; mLayoutAlgorithm.getStackTransform( key, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null); tv.startEnterRecentsAnimation(ctx); } // Add a runnable to the post animation ref counter to clear all the views ctx.postAnimationTrigger.addLastDecrementRunnable( new Runnable() { @Override public void run() { mStartEnterAnimationCompleted = true; // Poke the dozer to restart the trigger after the animation completes mUIDozeTrigger.poke(); } }); } }