blob: 66f5224bb33581dfb1cdd4dec43543a7046a7a17 [file] [log] [blame]
package com.android.launcher3;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import java.util.ArrayList;
public class FolderCellLayout extends CellLayout {
private static final int REORDER_ANIMATION_DURATION = 230;
private static final int START_VIEW_REORDER_DELAY = 30;
private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
private static final int[] sTempPosArray = new int[2];
private final FolderKeyEventListener mKeyListener = new FolderKeyEventListener();
private final LayoutInflater mInflater;
private final IconCache mIconCache;
private final int mMaxCountX;
private final int mMaxCountY;
private final int mMaxNumItems;
// Indicates the last number of items used to set up the grid size
private int mAllocatedContentSize;
private Folder mFolder;
private FocusIndicatorView mFocusIndicatorView;
public FolderCellLayout(Context context) {
this(context, null);
}
public FolderCellLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FolderCellLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
mMaxCountX = (int) grid.numColumns;
mMaxCountY = (int) grid.numRows;
mMaxNumItems = mMaxCountX * mMaxCountY;
mInflater = LayoutInflater.from(context);
mIconCache = app.getIconCache();
}
public void setFolder(Folder folder) {
mFolder = folder;
mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
}
/**
* 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;
int countX = getCountX();
int countY = getCountY();
boolean done = false;
while (!done) {
int oldCountX = countX;
int oldCountY = countY;
if (countX * countY < count) {
// Current grid is too small, expand it
if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
countX++;
} else if (countY < mMaxCountY) {
countY++;
}
if (countY == 0) countY++;
} else if ((countY - 1) * countX >= count && countY >= countX) {
countY = Math.max(0, countY - 1);
} else if ((countX - 1) * countY >= count) {
countX = Math.max(0, countX - 1);
}
done = countX == oldCountX && countY == oldCountY;
}
setGridSize(countX, countY);
}
/**
* Binds items to the layout.
* @return list of items that could not be bound, probably because we hit the max size limit.
*/
public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
setupContentDimensions(Math.min(items.size(), mMaxNumItems));
int countX = getCountX();
int rank = 0;
for (ShortcutInfo item : items) {
if (rank >= mMaxNumItems) {
extra.add(item);
continue;
}
item.rank = rank;
item.cellX = rank % countX;
item.cellY = rank / countX;
addNewView(item);
rank++;
}
return extra;
}
/**
* Create space for a new item at the end, and returns the rank for that item.
* Resizes the content if necessary.
*/
public int allocateNewLastItemRank() {
int rank = getItemCount();
mFolder.rearrangeChildren(rank + 1);
return rank;
}
/**
* Adds the new item to the end of the grid. Resizes the content if necessary.
*/
public View createAndAddShortcutToEnd(ShortcutInfo item) {
int rank = allocateNewLastItemRank();
return createAndAddViewForRank(item, rank);
}
public View createAndAddViewForRank(ShortcutInfo item, int rank) {
updateItemXY(item, rank);
return addNewView(item);
}
/**
* Adds the {@param view} to the layout based on {@param rank} and updated the position
* related attributes. It assumes that {@param item} is already attached to the view.
*/
public void addViewForRank(View view, ShortcutInfo item, int rank) {
updateItemXY(item, rank);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
lp.cellX = item.cellX;
lp.cellY = item.cellY;
addViewToCellLayout(view, -1, (int) item.id, lp, true);
}
/**
* Updates the item cellX and cellY position
*/
private void updateItemXY(ShortcutInfo item, int rank) {
item.rank = rank;
int countX = getCountX();
item.cellX = rank % countX;
item.cellY = rank / countX;
}
private View addNewView(ShortcutInfo item) {
final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
R.layout.folder_application, 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);
addViewToCellLayout(textView, -1, (int)item.id, lp, true);
return textView;
}
/**
* Refer {@link #findNearestArea(int, int, int, int, View, boolean, int[])}
*
* @return The rank of a vacant area that can contain this object,
* nearest the requested location.
*/
public int findNearestArea(int pixelX, int pixelY, int spanX, int spanY) {
findNearestArea(pixelX, pixelY, spanX, spanY, null, false, sTempPosArray);
if (mFolder.isLayoutRtl()) {
sTempPosArray[0] = getCountX() - sTempPosArray[0] - 1;
}
// Convert this position to rank.
return Math.min(mAllocatedContentSize - 1,
sTempPosArray[1] * getCountX() + sTempPosArray[0]);
}
public boolean isFull() {
return getItemCount() >= mMaxNumItems;
}
public int getItemCount() {
return getShortcutsAndWidgets().getChildCount();
}
/**
* Updates position and rank of all the children in the view based.
* @param list the ordered list of children.
* @param itemCount if greater than the total children count, empty spaces are left at the end.
*/
public void arrangeChildren(ArrayList<View> list, int itemCount) {
setupContentDimensions(itemCount);
removeAllViews();
int newX, newY;
int rank = 0;
int countX = getCountX();
for (View v : list) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
newX = rank % countX;
newY = rank / countX;
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 ++;
addViewToCellLayout(v, -1, (int)info.id, lp, true);
}
}
/**
* Reorders the items such that the {@param empty} spot moves to {@param target}
*/
public void realTimeReorder(int empty, int target) {
boolean wrap;
int startX;
int endX;
int startY;
int delay = 0;
float delayAmount = START_VIEW_REORDER_DELAY;
int countX = getCountX();
int emptyX = empty % getCountX();
int emptyY = empty / countX;
int targetX = target % countX;
int targetY = target / countX;
if (target > empty) {
wrap = emptyX == countX - 1;
startY = wrap ? emptyY + 1 : emptyY;
for (int y = startY; y <= targetY; y++) {
startX = y == emptyY ? emptyX + 1 : 0;
endX = y < targetY ? countX - 1 : targetX;
for (int x = startX; x <= endX; x++) {
View v = getChildAt(x,y);
if (animateChildToPosition(v, emptyX, emptyY,
REORDER_ANIMATION_DURATION, delay, true, true)) {
emptyX = x;
emptyY = y;
delay += delayAmount;
delayAmount *= VIEW_REORDER_DELAY_FACTOR;
}
}
}
} else {
wrap = emptyX == 0;
startY = wrap ? emptyY - 1 : emptyY;
for (int y = startY; y >= targetY; y--) {
startX = y == emptyY ? emptyX - 1 : countX - 1;
endX = y > targetY ? 0 : targetX;
for (int x = startX; x >= endX; x--) {
View v = getChildAt(x,y);
if (animateChildToPosition(v, emptyX, emptyY,
REORDER_ANIMATION_DURATION, delay, true, true)) {
emptyX = x;
emptyY = y;
delay += delayAmount;
delayAmount *= VIEW_REORDER_DELAY_FACTOR;
}
}
}
}
}
}