summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2015-03-05 11:33:33 -0800
committerSunny Goyal <sunnygoyal@google.com>2015-03-09 14:21:43 -0700
commit290800b5b7d575fd709f244f54a5fa5b63b58876 (patch)
tree6713f6dfc221f322d49942308044d8553479876c
parentd5bf4ab5f75bbfd28162d8a45043cc5801fbfcc5 (diff)
downloadandroid_packages_apps_Trebuchet-290800b5b7d575fd709f244f54a5fa5b63b58876.tar.gz
android_packages_apps_Trebuchet-290800b5b7d575fd709f244f54a5fa5b63b58876.tar.bz2
android_packages_apps_Trebuchet-290800b5b7d575fd709f244f54a5fa5b63b58876.zip
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
-rw-r--r--res/drawable-xxhdpi/ic_pageindicator_current_dark.pngbin0 -> 1168 bytes
-rw-r--r--res/drawable-xxhdpi/ic_pageindicator_default_dark.pngbin0 -> 927 bytes
-rw-r--r--res/layout/user_folder.xml1
-rw-r--r--res/layout/user_folder_scroll.xml78
-rw-r--r--src/com/android/launcher3/AppsCustomizePagedView.java14
-rw-r--r--src/com/android/launcher3/FocusHelper.java45
-rw-r--r--src/com/android/launcher3/Folder.java39
-rw-r--r--src/com/android/launcher3/FolderCellLayout.java21
-rw-r--r--src/com/android/launcher3/FolderPagedView.java581
9 files changed, 742 insertions, 37 deletions
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current_dark.png b/res/drawable-xxhdpi/ic_pageindicator_current_dark.png
new file mode 100644
index 000000000..d5c4c8d4e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_current_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default_dark.png b/res/drawable-xxhdpi/ic_pageindicator_default_dark.png
new file mode 100644
index 000000000..79d307b07
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_default_dark.png
Binary files differ
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 74cdf11dc..7a4d5e881 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -46,7 +46,6 @@
android:id="@+id/folder_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
android:background="#00000000"
android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
diff --git a/res/layout/user_folder_scroll.xml b/res/layout/user_folder_scroll.xml
new file mode 100644
index 000000000..421e426cb
--- /dev/null
+++ b/res/layout/user_folder_scroll.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<com.android.launcher3.Folder xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/quantum_panel"
+ android:orientation="vertical" >
+
+ <FrameLayout
+ android:id="@+id/folder_content_wrapper"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size -->
+
+ <com.android.launcher3.FocusIndicatorView
+ android:id="@+id/focus_indicator"
+ android:layout_width="20dp"
+ android:layout_height="20dp" />
+
+ <com.android.launcher3.FolderPagedView
+ android:id="@+id/folder_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:pageIndicator="@+id/folder_page_indicator" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/folder_footer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <com.android.launcher3.FolderEditText
+ android:id="@+id/folder_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:background="#00000000"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="center_horizontal"
+ android:hint="@string/folder_hint_text"
+ android:imeOptions="flagNoExtractUi"
+ android:paddingBottom="@dimen/folder_name_padding"
+ android:paddingTop="@dimen/folder_name_padding"
+ android:singleLine="true"
+ android:textColor="#ff777777"
+ android:textColorHighlight="#ffCCCCCC"
+ android:textColorHint="#ff808080"
+ android:textCursorDrawable="@null"
+ android:textSize="14sp" />
+
+ <include
+ android:id="@+id/folder_page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="12dp"
+ android:layout_gravity="center_vertical"
+ layout="@layout/page_indicator" />
+ </LinearLayout>
+
+</com.android.launcher3.Folder> \ No newline at end of file
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<AsyncTaskPageData, Void, AsyncTas
* The Apps/Customize page that displays all the applications, widgets, and shortcuts.
*/
public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
- View.OnClickListener, View.OnKeyListener, DragSource,
+ View.OnClickListener, DragSource,
PagedViewWidget.ShortPressListener, LauncherTransitionable {
static final String TAG = "AppsCustomizePagedView";
@@ -182,6 +182,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
ArrayList<AppsCustomizeAsyncTask> 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;
/**
@@ -65,9 +66,32 @@ public class FocusHelper {
//
/**
+ * 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).
* <p>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<View, Runnable> 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} &amp; {@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<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> 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<ShortcutInfo>();
+ }
+
+ /**
+ * 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<View> list, int itemCount) {
+ ArrayList<CellLayout> pages = new ArrayList<CellLayout>();
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout page = (CellLayout) getChildAt(i);
+ page.removeAllViews();
+ pages.add(page);
+ }
+ setupContentDimensions(itemCount);
+
+ Iterator<CellLayout> 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;
+ }
+ }
+ }
+}