| /* |
| * Copyright (C) 2008 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.launcher2; |
| |
| import java.util.ArrayList; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ObjectAnimator; |
| import android.animation.PropertyValuesHolder; |
| import android.animation.ValueAnimator; |
| import android.animation.ValueAnimator.AnimatorUpdateListener; |
| import android.content.Context; |
| import android.graphics.Matrix; |
| import android.graphics.Rect; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.animation.AccelerateInterpolator; |
| import android.view.animation.DecelerateInterpolator; |
| import android.widget.AdapterView; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| import android.widget.AdapterView.OnItemClickListener; |
| import android.widget.AdapterView.OnItemLongClickListener; |
| |
| import com.android.launcher.R; |
| import com.android.launcher2.FolderInfo.FolderListener; |
| |
| /** |
| * Represents a set of icons chosen by the user or generated by the system. |
| */ |
| public class Folder extends LinearLayout implements DragSource, OnItemLongClickListener, |
| OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener { |
| |
| protected DragController mDragController; |
| |
| protected Launcher mLauncher; |
| |
| protected FolderInfo mInfo; |
| |
| /** |
| * Which item is being dragged |
| */ |
| protected ShortcutInfo mDragItem; |
| |
| private static final String TAG = "Launcher.Folder"; |
| |
| static final int STATE_NONE = -1; |
| static final int STATE_SMALL = 0; |
| static final int STATE_ANIMATING = 1; |
| static final int STATE_OPEN = 2; |
| |
| private int mExpandDuration; |
| protected CellLayout mContent; |
| private final LayoutInflater mInflater; |
| private final IconCache mIconCache; |
| private int mState = STATE_NONE; |
| private int[] mDragItemPosition = new int[2]; |
| private static final int FULL_GROW = 0; |
| private static final int PARTIAL_GROW = 1; |
| private int mMode = PARTIAL_GROW; |
| private boolean mRearrangeOnClose = false; |
| private FolderIcon mFolderIcon; |
| private int mMaxCountX; |
| private int mMaxCountY; |
| private Rect mNewSize = new Rect(); |
| |
| /** |
| * Used to inflate the Workspace from XML. |
| * |
| * @param context The application's context. |
| * @param attrs The attribtues set containing the Workspace's customization values. |
| */ |
| public Folder(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| setAlwaysDrawnWithCacheEnabled(false); |
| mInflater = LayoutInflater.from(context); |
| mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache(); |
| mExpandDuration = getResources().getInteger(R.integer.config_folderAnimDuration); |
| |
| mMaxCountX = LauncherModel.getCellCountX() - 1; |
| mMaxCountY = LauncherModel.getCellCountY() - 1; |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mContent = (CellLayout) findViewById(R.id.folder_content); |
| mContent.setGridSize(0, 0); |
| mContent.enableHardwareLayers(); |
| } |
| |
| public void onItemClick(AdapterView parent, View v, int position, long id) { |
| ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position); |
| int[] pos = new int[2]; |
| v.getLocationOnScreen(pos); |
| app.intent.setSourceBounds(new Rect(pos[0], pos[1], |
| pos[0] + v.getWidth(), pos[1] + v.getHeight())); |
| mLauncher.startActivitySafely(app.intent, app); |
| } |
| |
| public void onClick(View v) { |
| Object tag = v.getTag(); |
| if (tag instanceof ShortcutInfo) { |
| // refactor this code from Folder |
| ShortcutInfo item = (ShortcutInfo) tag; |
| int[] pos = new int[2]; |
| v.getLocationOnScreen(pos); |
| item.intent.setSourceBounds(new Rect(pos[0], pos[1], |
| pos[0] + v.getWidth(), pos[1] + v.getHeight())); |
| mLauncher.startActivitySafely(item.intent, item); |
| } |
| } |
| |
| public boolean onLongClick(View v) { |
| Object tag = v.getTag(); |
| if (tag instanceof ShortcutInfo) { |
| mLauncher.closeFolder(this); |
| |
| ShortcutInfo item = (ShortcutInfo) tag; |
| if (!v.isInTouchMode()) { |
| return false; |
| } |
| |
| mLauncher.getWorkspace().onDragStartedWithItem(v); |
| mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY); |
| mDragItemPosition[0] = item.cellX; |
| mDragItemPosition[1] = item.cellY; |
| mInfo.remove(item); |
| |
| mDragItem = item; |
| } else { |
| mLauncher.closeFolder(this); |
| mLauncher.showRenameDialog(mInfo); |
| } |
| return true; |
| } |
| |
| /** |
| * We need to handle touch events to prevent them from falling through to the workspace below. |
| */ |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| return true; |
| } |
| |
| public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { |
| if (!view.isInTouchMode()) { |
| return false; |
| } |
| |
| ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position); |
| |
| mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY); |
| mLauncher.closeFolder(this); |
| mDragItem = app; |
| |
| return true; |
| } |
| |
| public void setDragController(DragController dragController) { |
| mDragController = dragController; |
| } |
| |
| public void onDragViewVisible() { |
| } |
| |
| void setLauncher(Launcher launcher) { |
| mLauncher = launcher; |
| } |
| |
| void setFolderIcon(FolderIcon icon) { |
| mFolderIcon = icon; |
| } |
| |
| /** |
| * @return the FolderInfo object associated with this folder |
| */ |
| FolderInfo getInfo() { |
| return mInfo; |
| } |
| |
| void onOpen() { |
| // When the folder opens, we need to refresh the GridView's selection by |
| // forcing a layout |
| // TODO: find out if this is still necessary |
| mContent.requestLayout(); |
| requestFocus(); |
| } |
| |
| void onClose() { |
| final Workspace workspace = mLauncher.getWorkspace(); |
| workspace.getChildAt(workspace.getCurrentPage()).requestFocus(); |
| } |
| |
| void bind(FolderInfo info) { |
| mInfo = info; |
| ArrayList<ShortcutInfo> children = info.contents; |
| for (int i = 0; i < children.size(); i++) { |
| ShortcutInfo child = (ShortcutInfo) children.get(i); |
| onAdd(child); |
| } |
| mInfo.addListener(this); |
| } |
| |
| /** |
| * Creates a new UserFolder, inflated from R.layout.user_folder. |
| * |
| * @param context The application's context. |
| * |
| * @return A new UserFolder. |
| */ |
| static Folder fromXml(Context context) { |
| return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); |
| } |
| |
| /** |
| * This method is intended to make the UserFolder to be visually identical in size and position |
| * to its associated FolderIcon. This allows for a seamless transition into the expanded state. |
| */ |
| private void positionAndSizeAsIcon() { |
| if (!(getParent() instanceof CellLayoutChildren)) return; |
| |
| CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams(); |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); |
| |
| if (mMode == PARTIAL_GROW) { |
| setScaleX(0.8f); |
| setScaleY(0.8f); |
| setAlpha(0f); |
| } else { |
| lp.width = iconLp.width; |
| lp.height = iconLp.height; |
| lp.x = iconLp.x; |
| lp.y = iconLp.y; |
| mContent.setAlpha(0); |
| } |
| mState = STATE_SMALL; |
| } |
| |
| public void animateOpen() { |
| if (mState != STATE_SMALL) { |
| positionAndSizeAsIcon(); |
| } |
| if (!(getParent() instanceof CellLayoutChildren)) return; |
| |
| ObjectAnimator oa; |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); |
| |
| centerAboutIcon(); |
| if (mMode == PARTIAL_GROW) { |
| PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); |
| PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); |
| PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); |
| oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); |
| } else { |
| PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width()); |
| PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height()); |
| PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left); |
| PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top); |
| oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); |
| oa.addUpdateListener(new AnimatorUpdateListener() { |
| public void onAnimationUpdate(ValueAnimator animation) { |
| requestLayout(); |
| } |
| }); |
| |
| PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); |
| ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); |
| alphaOa.setDuration(mExpandDuration); |
| alphaOa.setInterpolator(new AccelerateInterpolator(2.0f)); |
| alphaOa.start(); |
| } |
| |
| oa.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| mState = STATE_ANIMATING; |
| } |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mState = STATE_OPEN; |
| } |
| }); |
| oa.setDuration(mExpandDuration); |
| oa.start(); |
| } |
| |
| public void animateClosed() { |
| if (!(getParent() instanceof CellLayoutChildren)) return; |
| |
| CellLayoutChildren clc = (CellLayoutChildren) getParent(); |
| final CellLayout cellLayout = (CellLayout) clc.getParent(); |
| ObjectAnimator oa; |
| |
| if (mMode == PARTIAL_GROW) { |
| PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); |
| PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); |
| PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); |
| oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); |
| } else { |
| CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams(); |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); |
| |
| PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width); |
| PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height); |
| PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x); |
| PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y); |
| oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); |
| oa.addUpdateListener(new AnimatorUpdateListener() { |
| public void onAnimationUpdate(ValueAnimator animation) { |
| requestLayout(); |
| } |
| }); |
| |
| PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); |
| ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); |
| alphaOa.setDuration(mExpandDuration); |
| alphaOa.setInterpolator(new DecelerateInterpolator(2.0f)); |
| alphaOa.start(); |
| } |
| |
| oa.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| onCloseComplete(); |
| cellLayout.removeViewWithoutMarkingCells(Folder.this); |
| mState = STATE_SMALL; |
| } |
| @Override |
| public void onAnimationStart(Animator animation) { |
| mState = STATE_ANIMATING; |
| } |
| }); |
| oa.setDuration(mExpandDuration); |
| oa.start(); |
| } |
| |
| void notifyDataSetChanged() { |
| // recreate all the children if the data set changes under us. We may want to do this more |
| // intelligently (ie just removing the views that should no longer exist) |
| mContent.removeAllViewsInLayout(); |
| bind(mInfo); |
| } |
| |
| public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, |
| DragView dragView, Object dragInfo) { |
| final ItemInfo item = (ItemInfo) dragInfo; |
| final int itemType = item.itemType; |
| return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || |
| itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && |
| !isFull()); |
| } |
| |
| public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, |
| DragView dragView, Object dragInfo) { |
| ShortcutInfo item; |
| if (dragInfo instanceof ApplicationInfo) { |
| // Came from all apps -- make a copy |
| item = ((ApplicationInfo)dragInfo).makeShortcut(); |
| item.spanX = 1; |
| item.spanY = 1; |
| } else { |
| item = (ShortcutInfo)dragInfo; |
| } |
| mInfo.add(item); |
| LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); |
| } |
| |
| protected boolean findAndSetEmptyCells(ShortcutInfo item) { |
| int[] emptyCell = new int[2]; |
| if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { |
| item.cellX = emptyCell[0]; |
| item.cellY = emptyCell[1]; |
| LauncherModel.addOrMoveItemInDatabase( |
| mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| protected void createAndAddShortcut(ShortcutInfo item) { |
| final TextView textView = |
| (TextView) mInflater.inflate(R.layout.application_boxed, this, false); |
| textView.setCompoundDrawablesWithIntrinsicBounds(null, |
| new FastBitmapDrawable(item.getIcon(mIconCache)), null, null); |
| textView.setText(item.title); |
| textView.setTag(item); |
| |
| textView.setOnClickListener(this); |
| textView.setOnLongClickListener(this); |
| |
| CellLayout.LayoutParams lp = |
| new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); |
| boolean insert = false; |
| mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); |
| } |
| |
| public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, |
| DragView dragView, Object dragInfo) { |
| mContent.onDragEnter(); |
| } |
| |
| public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, |
| DragView dragView, Object dragInfo) { |
| float[] r = mapPointFromScreenToContent(x, y, null); |
| mContent.visualizeDropLocation(null, null, (int) r[0], (int) r[1], 1, 1); |
| } |
| |
| public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, |
| DragView dragView, Object dragInfo) { |
| mContent.onDragExit(); |
| } |
| |
| public float[] mapPointFromScreenToContent(int x, int y, float[] r) { |
| if (r == null) { |
| r = new float[2]; |
| } |
| |
| int[] screenLocation = new int[2]; |
| mContent.getLocationOnScreen(screenLocation); |
| |
| r[0] = x - screenLocation[0]; |
| r[1] = y - screenLocation[1]; |
| return r; |
| } |
| |
| public void onDropCompleted(View target, Object dragInfo, boolean success) { |
| } |
| |
| public boolean isDropEnabled() { |
| return true; |
| } |
| |
| public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, |
| DragView dragView, Object dragInfo) { |
| return null; |
| } |
| |
| private void setupContentDimension(int count) { |
| ArrayList<View> list = getItemsInReadingOrder(); |
| |
| int countX = mContent.getCountX(); |
| int countY = mContent.getCountY(); |
| if (countX * countY < count) { |
| // Current grid is too small, expand it |
| if (countX <= countY && countX < mMaxCountX) { |
| countX++; |
| } else if (countY < mMaxCountY) { |
| countY++; |
| } |
| if (countY == 0) countY++; |
| |
| mContent.setGridSize(countX, countY); |
| } else if ((countX - 1) * countY >= count || (countY - 1) * countX >= count) { |
| // Current grid is too big, shrink it |
| if (countX <= countY) { |
| countY--; |
| } else { |
| countX--; |
| } |
| mContent.setGridSize(countX, countY); |
| } |
| arrangeChildren(list); |
| } |
| |
| public boolean isFull() { |
| return getItemCount() >= mMaxCountX * mMaxCountY; |
| } |
| |
| private void centerAboutIcon() { |
| CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams(); |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); |
| |
| int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); |
| int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight(); |
| |
| int centerX = iconLp.x + iconLp.width / 2; |
| int centerY = iconLp.y + iconLp.height / 2; |
| int centeredLeft = centerX - width / 2; |
| int centeredTop = centerY - height / 2; |
| |
| CellLayoutChildren clc = (CellLayoutChildren) getParent(); |
| int parentWidth = 0; |
| int parentHeight = 0; |
| if (clc != null) { |
| parentWidth = clc.getMeasuredWidth(); |
| parentHeight = clc.getMeasuredHeight(); |
| } |
| |
| int left = Math.min(Math.max(0, centeredLeft), parentWidth - width); |
| int top = Math.min(Math.max(0, centeredTop), parentHeight - height); |
| |
| int folderPivotX = width / 2 + (centeredLeft - left); |
| int folderPivotY = height / 2 + (centeredTop - top); |
| setPivotX(folderPivotX); |
| setPivotY(folderPivotY); |
| int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * |
| (1.0f * folderPivotX / width)); |
| int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * |
| (1.0f * folderPivotY / height)); |
| mFolderIcon.setPivotX(folderIconPivotX); |
| mFolderIcon.setPivotY(folderIconPivotY); |
| |
| if (mMode == PARTIAL_GROW) { |
| lp.width = width; |
| lp.height = height; |
| lp.x = left; |
| lp.y = top; |
| } else { |
| mNewSize.set(left, top, left + width, top + height); |
| } |
| } |
| |
| private void setupContentForNumItems(int count) { |
| |
| setupContentDimension(count); |
| |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); |
| if (lp == null) { |
| lp = new CellLayout.LayoutParams(0, 0, -1, -1); |
| lp.isLockedToGrid = false; |
| setLayoutParams(lp); |
| } |
| centerAboutIcon(); |
| } |
| |
| private void arrangeChildren(ArrayList<View> list) { |
| int[] vacant = new int[2]; |
| if (list == null) { |
| list = getItemsInReadingOrder(); |
| } |
| mContent.removeAllViews(); |
| |
| for (int i = 0; i < list.size(); i++) { |
| View v = list.get(i); |
| mContent.getVacantCell(vacant, 1, 1); |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); |
| lp.cellX = vacant[0]; |
| lp.cellY = vacant[1]; |
| ItemInfo info = (ItemInfo) v.getTag(); |
| info.cellX = vacant[0]; |
| info.cellY = vacant[1]; |
| boolean insert = false; |
| mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); |
| } |
| } |
| |
| public void onAdd(ShortcutInfo item) { |
| if (!findAndSetEmptyCells(item)) { |
| // The current layout is full, can we expand it? |
| setupContentForNumItems(getItemCount() + 1); |
| findAndSetEmptyCells(item); |
| } |
| createAndAddShortcut(item); |
| } |
| |
| public int getItemCount() { |
| return mContent.getChildrenLayout().getChildCount(); |
| } |
| |
| public View getItemAt(int index) { |
| return mContent.getChildrenLayout().getChildAt(index); |
| } |
| |
| private ArrayList<View> getItemsInReadingOrder() { |
| ArrayList<View> list = new ArrayList<View>(); |
| for (int j = 0; j < mContent.getCountY(); j++) { |
| for (int i = 0; i < mContent.getCountX(); i++) { |
| View v = mContent.getChildAt(i, j); |
| if (v != null) { |
| list.add(v); |
| } |
| } |
| } |
| return list; |
| } |
| |
| private void onCloseComplete() { |
| if (mRearrangeOnClose) { |
| setupContentForNumItems(getItemCount()); |
| mRearrangeOnClose = false; |
| } |
| } |
| |
| public void onRemove(ShortcutInfo item) { |
| View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]); |
| mContent.removeView(v); |
| if (mState == STATE_ANIMATING) { |
| mRearrangeOnClose = true; |
| } else { |
| setupContentForNumItems(getItemCount()); |
| } |
| } |
| } |