From 290800b5b7d575fd709f244f54a5fa5b63b58876 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 5 Mar 2015 11:33:33 -0800 Subject: Adding a scrollable folder content implementation > Size is restricted to 3x3 for now > Drag-drop across page s not implemented yet > A-Z sorting is not implemented yet Change-Id: I84328caa6ad910d1edeeac6f3a7fb61b7292ea7e --- .../android/launcher3/AppsCustomizePagedView.java | 14 +- src/com/android/launcher3/FocusHelper.java | 45 +- src/com/android/launcher3/Folder.java | 39 +- src/com/android/launcher3/FolderCellLayout.java | 21 + src/com/android/launcher3/FolderPagedView.java | 581 +++++++++++++++++++++ 5 files changed, 664 insertions(+), 36 deletions(-) create mode 100644 src/com/android/launcher3/FolderPagedView.java (limited to 'src/com/android/launcher3') diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java index c1aa19ae7..9f8d499eb 100644 --- a/src/com/android/launcher3/AppsCustomizePagedView.java +++ b/src/com/android/launcher3/AppsCustomizePagedView.java @@ -36,7 +36,6 @@ import android.os.Process; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -45,6 +44,7 @@ import android.widget.ImageView; import android.widget.Toast; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.FocusHelper.PagedViewKeyListener; import com.android.launcher3.compat.AppWidgetManagerCompat; import java.util.ArrayList; @@ -139,7 +139,7 @@ class AppsCustomizeAsyncTask extends AsyncTask mRunningTasks; private static final int sPageSleepDelay = 200; + private final PagedViewKeyListener mKeyListener = new PagedViewKeyListener(); + private Runnable mInflateWidgetRunnable = null; private Runnable mBindWidgetRunnable = null; static final int WIDGET_NO_CLEANUP_REQUIRED = -1; @@ -449,10 +451,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mWidgetInstructionToast.show(); } - public boolean onKey(View v, int keyCode, KeyEvent event) { - return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event); - } - /* * PagedViewWithDraggableItems implementation */ @@ -959,7 +957,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen icon.setOnClickListener(mLauncher); icon.setOnLongClickListener(this); icon.setOnTouchListener(this); - icon.setOnKeyListener(this); + icon.setOnKeyListener(mKeyListener); icon.setOnFocusChangeListener(layout.mFocusHandlerView); int index = i - startIndex; @@ -1141,7 +1139,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen widget.setOnClickListener(this); widget.setOnLongClickListener(this); widget.setOnTouchListener(this); - widget.setOnKeyListener(this); + widget.setOnKeyListener(mKeyListener); // Layout each widget int ix = i % mWidgetCountX; diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index 737f6cca7..b090a7c3f 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -23,6 +23,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.FocusHelper.PagedViewKeyListener; import com.android.launcher3.util.FocusLogic; /** @@ -64,10 +65,33 @@ public class FocusHelper { // Key code handling methods. // + /** + * A keyboard listener for scrollable folders + */ + public static class PagedFolderKeyEventListener extends PagedViewKeyListener { + + private final Folder mFolder; + + public PagedFolderKeyEventListener(Folder folder) { + mFolder = folder; + } + + @Override + public void handleNoopKey(int keyCode, View v) { + if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + mFolder.mFolderName.requestFocus(); + playSoundEffect(keyCode, v); + } + } + } + /** * Handles key events in the all apps screen. */ - static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) { + public static class PagedViewKeyListener implements View.OnKeyListener { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent e) { boolean consume = FocusLogic.shouldConsume(keyCode); if (e.getAction() == KeyEvent.ACTION_UP) { return consume; @@ -87,15 +111,14 @@ public class FocusHelper { parentLayout = (ViewGroup) itemContainer.getParent(); countX = ((CellLayout) parentLayout).getCountX(); countY = ((CellLayout) parentLayout).getCountY(); - } else if (v.getParent() instanceof ViewGroup) { - //TODO(hyunyoungs): figure out when this needs to be called. - itemContainer = parentLayout = (ViewGroup) v.getParent(); - countX = ((PagedViewGridLayout) parentLayout).getCellCountX(); - countY = ((PagedViewGridLayout) parentLayout).getCellCountY(); } else { - throw new IllegalStateException( - "Parent of the focused item inside all apps screen is not a supported type."); + if (LauncherAppState.isDogfoodBuild()) { + throw new IllegalStateException("Parent of the focused item is not supported."); + } else { + return false; + } } + final int iconIndex = itemContainer.indexOfChild(v); final PagedView container = (PagedView) parentLayout.getParent(); final int pageIndex = container.indexToPage(container.indexOfChild(parentLayout)); @@ -109,6 +132,7 @@ public class FocusHelper { int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix, iconIndex, pageIndex, pageCount); if (newIconIndex == FocusLogic.NOOP) { + handleNoopKey(keyCode, v); return consume; } switch (newIconIndex) { @@ -163,10 +187,15 @@ public class FocusHelper { if (child != null) { child.requestFocus(); playSoundEffect(keyCode, v); + } else { + handleNoopKey(keyCode, v); } return consume; } + public void handleNoopKey(int keyCode, View v) { } + } + /** * Handles key events in the workspace hot seat (bottom of the screen). *

Currently we don't special case for the phone UI in different orientations, even though diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index 4414672cc..0a6557907 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -66,6 +66,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList * results in CellLayout being measured as UNSPECIFIED, which it does not support. */ private static final int MIN_CONTENT_DIMEN = 5; + private static final boolean ALLOW_FOLDER_SCROLL = true; static final int STATE_NONE = -1; static final int STATE_SMALL = 0; @@ -100,8 +101,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private View mContentWrapper; FolderEditText mFolderName; - private View mBottomContent; - private int mBottomContentHeight; + private View mFooter; + private int mFooterHeight; // Cell ranks used for drag and drop private int mTargetRank, mPrevTargetRank, mEmptyCellRank; @@ -175,13 +176,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mFolderName.setInputType(mFolderName.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); - // We only have the folder name at the bottom for now - mBottomContent = mFolderName; - // We find out how tall the bottom content wants to be (it is set to wrap_content), so that + mFooter = ALLOW_FOLDER_SCROLL ? findViewById(R.id.folder_footer) : mFolderName; + // We find out how tall footer wants to be (it is set to wrap_content), so that // we can allocate the appropriate amount of space for it. int measureSpec = MeasureSpec.UNSPECIFIED; - mBottomContent.measure(measureSpec, measureSpec); - mBottomContentHeight = mBottomContent.getMeasuredHeight(); + mFooter.measure(measureSpec, measureSpec); + mFooterHeight = mFooter.getMeasuredHeight(); } private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { @@ -225,7 +225,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mEmptyCellRank = item.rank; mCurrentDragView = v; - mContent.removeView(mCurrentDragView); + mContent.removeItem(mCurrentDragView); mInfo.remove(mCurrentDragInfo); mDragInProgress = true; mItemAddedBackToSelfViaIcon = false; @@ -359,7 +359,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList * @return A new UserFolder. */ static Folder fromXml(Context context) { - return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); + return (Folder) LayoutInflater.from(context).inflate( + ALLOW_FOLDER_SCROLL ? R.layout.user_folder_scroll : R.layout.user_folder, null); } /** @@ -434,8 +435,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList iconsAlpha.setStartDelay(mMaterialExpandStagger); iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - mBottomContent.setAlpha(0f); - Animator textAlpha = LauncherAnimUtils.ofFloat(mBottomContent, "alpha", 0f, 1f); + mFooter.setAlpha(0f); + Animator textAlpha = LauncherAnimUtils.ofFloat(mFooter, "alpha", 0f, 1f); textAlpha.setDuration(mMaterialExpandDuration); textAlpha.setStartDelay(mMaterialExpandStagger); textAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); @@ -795,7 +796,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList CellLayout.LANDSCAPE : CellLayout.PORTRAIT); int maxContentAreaHeight = grid.availableHeightPx - workspacePadding.top - workspacePadding.bottom - - mBottomContentHeight; + mFooterHeight; int height = Math.min(maxContentAreaHeight, mContent.getDesiredHeight()); return Math.max(height, MIN_CONTENT_DIMEN); @@ -810,7 +811,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } private int getFolderHeight(int contentAreaHeight) { - return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mBottomContentHeight; + return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight; } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @@ -822,10 +823,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mContent.setFixedSize(contentWidth, contentHeight); mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec); - - // Move the bottom content below mContent - mBottomContent.measure(contentAreaWidthSpec, - MeasureSpec.makeMeasureSpec(mBottomContentHeight, MeasureSpec.EXACTLY)); + mFooter.measure(contentAreaWidthSpec, + MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY)); int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth; int folderHeight = getFolderHeight(contentHeight); @@ -929,7 +928,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // This method keeps track of the last item in the folder for the purposes // of keyboard focus - private void updateTextViewFocus() { + public void updateTextViewFocus() { View lastChild = mContent.getLastItem(); if (lastChild != null) { mFolderName.setNextFocusDownId(lastChild.getId()); @@ -1028,7 +1027,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // the work associated with removing the item, so we don't have to do anything here. if (item == mCurrentDragInfo) return; View v = getViewForInfo(item); - mContent.removeView(v); + mContent.removeItem(v); if (mState == STATE_ANIMATING) { mRearrangeOnClose = true; } else { @@ -1090,7 +1089,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public static interface FolderContent { void setFolder(Folder f); - void removeView(View v); + void removeItem(View v); boolean isFull(); int getItemCount(); diff --git a/src/com/android/launcher3/FolderCellLayout.java b/src/com/android/launcher3/FolderCellLayout.java index b354ec74e..1566912b4 100644 --- a/src/com/android/launcher3/FolderCellLayout.java +++ b/src/com/android/launcher3/FolderCellLayout.java @@ -1,3 +1,19 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.launcher3; import android.content.Context; @@ -138,6 +154,11 @@ public class FolderCellLayout extends CellLayout implements Folder.FolderContent addViewToCellLayout(view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true); } + @Override + public void removeItem(View v) { + removeView(v); + } + /** * Updates the item cellX and cellY position */ diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java new file mode 100644 index 000000000..60c94e0ed --- /dev/null +++ b/src/com/android/launcher3/FolderPagedView.java @@ -0,0 +1,581 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener; +import com.android.launcher3.PageIndicator.PageMarkerResources; +import com.android.launcher3.Workspace.ItemOperator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +public class FolderPagedView extends PagedView implements Folder.FolderContent { + + private static final String TAG = "FolderPagedView"; + + private static final int REORDER_ANIMATION_DURATION = 230; + private static final int[] sTempPosArray = new int[2]; + + // TODO: Remove this restriction + private static final int MAX_ITEMS_PER_PAGE = 3; + + private final LayoutInflater mInflater; + private final IconCache mIconCache; + private final HashMap mPageChangingViews = new HashMap<>(); + + private final CellLayout mFirstPage; + + final int mMaxCountX; + final int mMaxCountY; + final int mMaxItemsPerPage; + + private int mAllocatedContentSize; + private int mGridCountX; + private int mGridCountY; + + private Folder mFolder; + private FocusIndicatorView mFocusIndicatorView; + private PagedFolderKeyEventListener mKeyListener; + + public FolderPagedView(Context context, AttributeSet attrs) { + super(context, attrs); + LauncherAppState app = LauncherAppState.getInstance(); + + mFirstPage = newCellLayout(); + addFullScreenPage(mFirstPage); + setCurrentPage(0); + setDataIsReady(); + + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + mMaxCountX = Math.min((int) grid.numColumns, MAX_ITEMS_PER_PAGE); + mMaxCountY = Math.min((int) grid.numRows, MAX_ITEMS_PER_PAGE); + mMaxItemsPerPage = mMaxCountX * mMaxCountY; + + mInflater = LayoutInflater.from(context); + mIconCache = app.getIconCache(); + } + + @Override + public void setFolder(Folder folder) { + mFolder = folder; + mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator); + mKeyListener = new PagedFolderKeyEventListener(folder); + } + + /** + * Sets up the grid size such that {@param count} items can fit in the grid. + * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while + * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}. + */ + private void setupContentDimensions(int count) { + mAllocatedContentSize = count; + boolean done; + if (count >= mMaxItemsPerPage) { + mGridCountX = mMaxCountX; + mGridCountY = mMaxCountY; + done = true; + } else { + mGridCountX = mFirstPage.getCountX(); + mGridCountY = mFirstPage.getCountY(); + done = false; + } + + while (!done) { + int oldCountX = mGridCountX; + int oldCountY = mGridCountY; + if (mGridCountX * mGridCountY < count) { + // Current grid is too small, expand it + if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) { + mGridCountX++; + } else if (mGridCountY < mMaxCountY) { + mGridCountY++; + } + if (mGridCountY == 0) mGridCountY++; + } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) { + mGridCountY = Math.max(0, mGridCountY - 1); + } else if ((mGridCountX - 1) * mGridCountY >= count) { + mGridCountX = Math.max(0, mGridCountX - 1); + } + done = mGridCountX == oldCountX && mGridCountY == oldCountY; + } + + setGridSize(mGridCountX, mGridCountY); + } + + public void setGridSize(int countX, int countY) { + mGridCountX = countX; + mGridCountY = countY; + mFirstPage.setGridSize(mGridCountX, mGridCountY); + for (int i = getPageCount() - 1; i > 0; i--) { + getPageAt(i).setGridSize(mGridCountX, mGridCountY); + } + } + + @Override + public ArrayList bindItems(ArrayList items) { + final int count = items.size(); + + if (getPageCount() > 1) { + Log.d(TAG, "Binding items to an non-empty view"); + removeAllViews(); + addView(mFirstPage); + mFirstPage.removeAllViews(); + } + + setupContentDimensions(count); + CellLayout page = mFirstPage; + int pagePosition = 0; + int rank = 0; + + for (ShortcutInfo item : items) { + if (pagePosition >= mMaxItemsPerPage) { + // This page is full, add a new page. + pagePosition = 0; + page = newCellLayout(); + addFullScreenPage(page); + } + + item.cellX = pagePosition % mGridCountX; + item.cellY = pagePosition / mGridCountX; + item.rank = rank; + addNewView(item, page); + + rank++; + pagePosition++; + } + return new ArrayList(); + } + + /** + * Create space for a new item at the end, and returns the rank for that item. + * Also sets the current page to the last page. + */ + @Override + public int allocateNewLastItemRank() { + int rank = getItemCount(); + int total = rank + 1; + if (rank < mMaxItemsPerPage) { + // Rearrange the items as the grid size might change. + mFolder.rearrangeChildren(total); + } else { + setupContentDimensions(total); + } + + // Add a new page if last page is full + if (getPageAt(getChildCount() - 1).getShortcutsAndWidgets().getChildCount() + >= mMaxItemsPerPage) { + addFullScreenPage(newCellLayout()); + } + setCurrentPage(getChildCount() - 1); + return rank; + } + + @Override + public View createAndAddViewForRank(ShortcutInfo item, int rank) { + int pageNo = updateItemXY(item, rank); + CellLayout page = getPageAt(pageNo); + return addNewView(item, page); + } + + @Override + public void addViewForRank(View view, ShortcutInfo item, int rank) { + int pageNo = updateItemXY(item, rank); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); + lp.cellX = item.cellX; + lp.cellY = item.cellY; + getPageAt(pageNo).addViewToCellLayout( + view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true); + } + + /** + * Updates the item cellX and cellY position and return the page number for that item. + */ + private int updateItemXY(ShortcutInfo item, int rank) { + item.rank = rank; + + int pagePos = item.rank % mMaxItemsPerPage; + item.cellX = pagePos % mGridCountX; + item.cellY = pagePos / mGridCountX; + + return item.rank / mMaxItemsPerPage; + } + + private View addNewView(ShortcutInfo item, CellLayout target) { + final BubbleTextView textView = (BubbleTextView) mInflater.inflate( + R.layout.folder_application, target.getShortcutsAndWidgets(), false); + textView.applyFromShortcutInfo(item, mIconCache, false); + textView.setOnClickListener(mFolder); + textView.setOnLongClickListener(mFolder); + textView.setOnFocusChangeListener(mFocusIndicatorView); + textView.setOnKeyListener(mKeyListener); + + CellLayout.LayoutParams lp = new CellLayout.LayoutParams( + item.cellX, item.cellY, item.spanX, item.spanY); + target.addViewToCellLayout( + textView, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true); + return textView; + } + + @Override + public CellLayout getPageAt(int index) { + return (CellLayout) getChildAt(index); + } + + public void removeCellLayoutView(View view) { + for (int i = getChildCount() - 1; i >= 0; i --) { + getPageAt(i).removeView(view); + } + } + + public CellLayout getCurrentCellLayout() { + return getPageAt(getNextPage()); + } + + @Override + public void addFullScreenPage(View page) { + LayoutParams lp = generateDefaultLayoutParams(); + lp.isFullScreenPage = true; + super.addView(page, -1, lp); + } + + private CellLayout newCellLayout() { + DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + + CellLayout layout = new CellLayout(getContext()); + layout.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); + layout.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); + layout.setInvertIfRtl(true); + + if (mFirstPage != null) { + layout.setGridSize(mFirstPage.getCountX(), mFirstPage.getCountY()); + } + + return layout; + } + + @Override + public void setFixedSize(int width, int height) { + for (int i = getChildCount() - 1; i >= 0; i --) { + ((CellLayout) getChildAt(i)).setFixedSize(width, height); + } + } + + @Override + public void removeItem(View v) { + for (int i = getChildCount() - 1; i >= 0; i --) { + getPageAt(i).removeView(v); + } + } + + /** + * Updates position and rank of all the children in the view. + * It essentially removes all views from all the pages and then adds them again in appropriate + * page. + * + * @param list the ordered list of children. + * @param itemCount if greater than the total children count, empty spaces are left + * at the end, otherwise it is ignored. + * + */ + @Override + public void arrangeChildren(ArrayList list, int itemCount) { + ArrayList pages = new ArrayList(); + for (int i = 0; i < getChildCount(); i++) { + CellLayout page = (CellLayout) getChildAt(i); + page.removeAllViews(); + pages.add(page); + } + setupContentDimensions(itemCount); + + Iterator pageItr = pages.iterator(); + CellLayout currentPage = null; + + int position = 0; + int newX, newY, rank; + + rank = 0; + for (View v : list) { + if (currentPage == null || position >= mMaxItemsPerPage) { + // Next page + if (pageItr.hasNext()) { + currentPage = pageItr.next(); + } else { + currentPage = newCellLayout(); + addFullScreenPage(currentPage); + } + position = 0; + } + + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); + newX = position % mGridCountX; + newY = position / mGridCountX; + ItemInfo info = (ItemInfo) v.getTag(); + if (info.cellX != newX || info.cellY != newY || info.rank != rank) { + info.cellX = newX; + info.cellY = newY; + info.rank = rank; + LauncherModel.addOrMoveItemInDatabase(getContext(), info, + mFolder.mInfo.id, 0, info.cellX, info.cellY); + } + lp.cellX = info.cellX; + lp.cellY = info.cellY; + rank ++; + position++; + currentPage.addViewToCellLayout( + v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true); + } + + boolean removed = false; + while (pageItr.hasNext()) { + CellLayout layout = pageItr.next(); + if (layout != mFirstPage) { + removeView(layout); + removed = true; + } + } + if (removed) { + setCurrentPage(0); + } + } + + @Override + protected void loadAssociatedPages(int page, boolean immediateAndOnly) { } + + @Override + public void syncPages() { } + + @Override + public void syncPageItems(int page, boolean immediate) { } + + public int getDesiredWidth() { + return mFirstPage.getDesiredWidth(); + } + + public int getDesiredHeight() { + return mFirstPage.getDesiredHeight(); + } + + @Override + public int getItemCount() { + int lastPage = getChildCount() - 1; + return getPageAt(lastPage).getShortcutsAndWidgets().getChildCount() + + lastPage * mMaxItemsPerPage; + } + + @Override + public int findNearestArea(int pixelX, int pixelY) { + int pageIndex = getNextPage(); + CellLayout page = getPageAt(pageIndex); + page.findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray); + if (mFolder.isLayoutRtl()) { + sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1; + } + return Math.min(mAllocatedContentSize - 1, + pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]); + } + + @Override + protected PageMarkerResources getPageIndicatorMarker(int pageIndex) { + return new PageMarkerResources(R.drawable.ic_pageindicator_current_dark, R.drawable.ic_pageindicator_default_dark); + } + + @Override + public boolean isFull() { + return false; + } + + @Override + public View getLastItem() { + if (getChildCount() < 1) { + return null; + } + ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets(); + int lastRank = lastContainer.getChildCount() - 1; + if (mGridCountX > 0) { + return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX); + } else { + return lastContainer.getChildAt(lastRank); + } + } + + @Override + public View iterateOverItems(ItemOperator op) { + for (int k = 0 ; k < getChildCount(); k++) { + CellLayout page = getPageAt(k); + for (int j = 0; j < page.getCountY(); j++) { + for (int i = 0; i < page.getCountX(); i++) { + View v = page.getChildAt(i, j); + if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) { + return v; + } + } + } + } + return null; + } + + @Override + public String getAccessibilityDescription() { + return String.format(getContext().getString(R.string.folder_opened), + mGridCountX, mGridCountY); + } + + @Override + public void setFocusOnFirstChild() { + View firstChild = getCurrentCellLayout().getChildAt(0, 0); + if (firstChild != null) { + firstChild.requestFocus(); + } + } + + @Override + protected void notifyPageSwitchListener() { + super.notifyPageSwitchListener(); + if (mFolder != null) { + mFolder.updateTextViewFocus(); + } + } + + @Override + public void realTimeReorder(int empty, int target) { + int delay = 0; + float delayAmount = 30; + + // Animation only happens on the current page. + int pageToAnimate = getNextPage(); + + int pageT = target / mMaxItemsPerPage; + int pagePosT = target % mMaxItemsPerPage; + + if (pageT != pageToAnimate) { + Log.e(TAG, "Cannot animate when the target cell is invisible"); + } + int pagePosE = empty % mMaxItemsPerPage; + int pageE = empty / mMaxItemsPerPage; + + int startPos, endPos; + int moveStart, moveEnd; + int direction; + + if (target == empty) { + // No animation + return; + } else if (target > empty) { + // Items will move backwards to make room for the empty cell. + direction = 1; + + // If empty cell is in a different page, move them instantly. + if (pageE < pageToAnimate) { + moveStart = empty; + // Instantly move the first item in the current page. + moveEnd = pageToAnimate * mMaxItemsPerPage; + // Animate the 2nd item in the current page, as the first item was already moved to + // the last page. + startPos = 0; + } else { + moveStart = moveEnd = -1; + startPos = pagePosE; + } + + endPos = pagePosT; + } else { + // The items will move forward. + direction = -1; + + if (pageE > pageToAnimate) { + // Move the items immediately. + moveStart = empty; + // Instantly move the last item in the current page. + moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1; + + // Animations start with the second last item in the page + startPos = mMaxItemsPerPage - 1; + } else { + moveStart = moveEnd = -1; + startPos = pagePosE; + } + + endPos = pagePosT; + } + + // Instant moving views. + while (moveStart != moveEnd) { + int rankToMove = moveStart + direction; + int p = rankToMove / mMaxItemsPerPage; + int pagePos = rankToMove % mMaxItemsPerPage; + int x = pagePos % mGridCountX; + int y = pagePos / mGridCountX; + + final CellLayout page = getPageAt(p); + final View v = page.getChildAt(x, y); + if (v != null) { + if (pageToAnimate != p) { + page.removeView(v); + addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart); + } else { + // Do a fake animation before removing it. + final int newRank = moveStart; + final float oldTranslateX = v.getTranslationX(); + + Runnable endAction = new Runnable() { + + @Override + public void run() { + mPageChangingViews.remove(v); + v.setTranslationX(oldTranslateX); + ((CellLayout) v.getParent().getParent()).removeView(v); + addViewForRank(v, (ShortcutInfo) v.getTag(), newRank); + } + }; + v.animate() + .translationXBy(direction > 0 ? -v.getWidth() : v.getWidth()) + .setDuration(REORDER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(endAction); + mPageChangingViews.put(v, endAction); + } + } + moveStart = rankToMove; + } + + if ((endPos - startPos) * direction <= 0) { + // No animation + return; + } + + CellLayout page = getPageAt(pageToAnimate); + for (int i = startPos; i != endPos; i += direction) { + int nextPos = i + direction; + View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX); + if (v != null) { + ((ItemInfo) v.getTag()).rank -= direction; + } + if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX, + REORDER_ANIMATION_DURATION, delay, true, true)) { + delay += delayAmount; + delayAmount *= 0.9; + } + } + } +} -- cgit v1.2.3