| /* |
| * 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 android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ValueAnimator; |
| import android.animation.ValueAnimator.AnimatorUpdateListener; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.PorterDuff; |
| import android.graphics.drawable.Drawable; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| |
| import com.android.launcher.R; |
| import com.android.launcher2.DropTarget.DragObject; |
| import com.android.launcher2.FolderInfo.FolderListener; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * An icon that can appear on in the workspace representing an {@link UserFolder}. |
| */ |
| public class FolderIcon extends LinearLayout implements FolderListener { |
| private Launcher mLauncher; |
| Folder mFolder; |
| FolderInfo mInfo; |
| |
| // The number of icons to display in the |
| private static final int NUM_ITEMS_IN_PREVIEW = 3; |
| private static final int CONSUMPTION_ANIMATION_DURATION = 100; |
| |
| // The degree to which the inner ring grows when accepting drop |
| private static final float INNER_RING_GROWTH_FACTOR = 0.1f; |
| |
| // The degree to which the outer ring is scaled in its natural state |
| private static final float OUTER_RING_GROWTH_FACTOR = 0.4f; |
| |
| // The amount of vertical spread between items in the stack [0...1] |
| private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f; |
| |
| // The degree to which the item in the back of the stack is scaled [0...1] |
| // (0 means it's not scaled at all, 1 means it's scaled to nothing) |
| private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; |
| |
| private int mOriginalWidth = -1; |
| private int mOriginalHeight = -1; |
| private ImageView mPreviewBackground; |
| private BubbleTextView mFolderName; |
| |
| FolderRingAnimator mFolderRingAnimator = null; |
| |
| public FolderIcon(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| public FolderIcon(Context context) { |
| super(context); |
| } |
| |
| public boolean isDropEnabled() { |
| final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); |
| final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); |
| final Workspace workspace = (Workspace) cellLayout.getParent(); |
| return !workspace.isSmall(); |
| } |
| |
| static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, |
| FolderInfo folderInfo, IconCache iconCache) { |
| |
| FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); |
| |
| icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_name); |
| icon.mFolderName.setText(folderInfo.title); |
| icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); |
| |
| icon.setTag(folderInfo); |
| icon.setOnClickListener(launcher); |
| icon.mInfo = folderInfo; |
| icon.mLauncher = launcher; |
| |
| Folder folder = Folder.fromXml(launcher); |
| folder.setDragController(launcher.getDragController()); |
| folder.setLauncher(launcher); |
| folder.setFolderIcon(icon); |
| folder.bind(folderInfo); |
| icon.mFolder = folder; |
| icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); |
| folderInfo.addListener(icon); |
| |
| return icon; |
| } |
| |
| public static class FolderRingAnimator { |
| public int mFolderLocX; |
| public int mFolderLocY; |
| public float mOuterRingSize; |
| public float mInnerRingSize; |
| public FolderIcon mFolderIcon = null; |
| private Launcher mLauncher; |
| public Drawable mOuterRingDrawable = null; |
| public Drawable mInnerRingDrawable = null; |
| public static Drawable sSharedOuterRingDrawable = null; |
| public static Drawable sSharedInnerRingDrawable = null; |
| public static int sPreviewSize = -1; |
| public static int sPreviewPadding = -1; |
| |
| private ValueAnimator mAcceptAnimator; |
| private ValueAnimator mNeutralAnimator; |
| |
| public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { |
| mLauncher = launcher; |
| mFolderIcon = folderIcon; |
| Resources res = launcher.getResources(); |
| mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); |
| mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo); |
| |
| if (sPreviewSize < 0 || sPreviewPadding < 0) { |
| sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size); |
| sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); |
| } |
| if (sSharedOuterRingDrawable == null) { |
| sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo); |
| } |
| if (sSharedInnerRingDrawable == null) { |
| sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo); |
| } |
| } |
| |
| // Location is expressed in window coordinates |
| public void setLocation(int x, int y) { |
| mFolderLocX = x; |
| mFolderLocY = y; |
| } |
| |
| public void animateToAcceptState() { |
| if (mNeutralAnimator != null) { |
| mNeutralAnimator.cancel(); |
| } |
| mAcceptAnimator = ValueAnimator.ofFloat(0f, 1f); |
| mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); |
| mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { |
| public void onAnimationUpdate(ValueAnimator animation) { |
| final float percent = (Float) animation.getAnimatedValue(); |
| mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * sPreviewSize; |
| mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * sPreviewSize; |
| mLauncher.getWorkspace().invalidate(); |
| if (mFolderIcon != null) { |
| mFolderIcon.invalidate(); |
| } |
| } |
| }); |
| mAcceptAnimator.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| if (mFolderIcon != null) { |
| mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); |
| } |
| } |
| }); |
| mAcceptAnimator.start(); |
| } |
| |
| public void animateToNaturalState() { |
| if (mAcceptAnimator != null) { |
| mAcceptAnimator.cancel(); |
| } |
| mNeutralAnimator = ValueAnimator.ofFloat(0f, 1f); |
| mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); |
| mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { |
| public void onAnimationUpdate(ValueAnimator animation) { |
| final float percent = (Float) animation.getAnimatedValue(); |
| mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * sPreviewSize; |
| mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * sPreviewSize; |
| mLauncher.getWorkspace().invalidate(); |
| if (mFolderIcon != null) { |
| mFolderIcon.invalidate(); |
| } |
| } |
| }); |
| mNeutralAnimator.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| if (mFolderIcon != null) { |
| mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); |
| } |
| mLauncher.getWorkspace().hideFolderAccept(FolderRingAnimator.this); |
| } |
| }); |
| mNeutralAnimator.start(); |
| } |
| |
| // Location is expressed in window coordinates |
| public void getLocation(int[] loc) { |
| loc[0] = mFolderLocX; |
| loc[1] = mFolderLocY; |
| } |
| |
| public float getOuterRingSize() { |
| return mOuterRingSize; |
| } |
| |
| public float getInnerRingSize() { |
| return mInnerRingSize; |
| } |
| } |
| |
| private boolean willAcceptItem(ItemInfo item) { |
| final int itemType = item.itemType; |
| return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || |
| itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && |
| !mFolder.isFull() && item != mInfo); |
| } |
| |
| public boolean acceptDrop(Object dragInfo) { |
| final ItemInfo item = (ItemInfo) dragInfo; |
| return willAcceptItem(item); |
| } |
| |
| public void addItem(ShortcutInfo item) { |
| mInfo.add(item); |
| LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); |
| } |
| |
| void saveState(CellLayout.LayoutParams lp) { |
| mOriginalWidth = lp.width; |
| mOriginalHeight = lp.height; |
| } |
| |
| private void determineFolderLocationInWorkspace() { |
| int tvLocation[] = new int[2]; |
| int wsLocation[] = new int[2]; |
| getLocationInWindow(tvLocation); |
| mLauncher.getWorkspace().getLocationInWindow(wsLocation); |
| |
| int x = tvLocation[0] - wsLocation[0] + getMeasuredWidth() / 2; |
| int y = tvLocation[1] - wsLocation[1] + FolderRingAnimator.sPreviewSize / 2; |
| mFolderRingAnimator.setLocation(x, y); |
| } |
| |
| public void onDragEnter(Object dragInfo) { |
| if (!willAcceptItem((ItemInfo) dragInfo)) return; |
| determineFolderLocationInWorkspace(); |
| mLauncher.getWorkspace().showFolderAccept(mFolderRingAnimator); |
| mFolderRingAnimator.animateToAcceptState(); |
| } |
| |
| public void onDragOver(Object dragInfo) { |
| } |
| |
| public void onDragExit(Object dragInfo) { |
| if (!willAcceptItem((ItemInfo) dragInfo)) return; |
| mFolderRingAnimator.animateToNaturalState(); |
| } |
| |
| public void onDrop(Object dragInfo) { |
| ShortcutInfo item; |
| if (dragInfo instanceof ApplicationInfo) { |
| // Came from all apps -- make a copy |
| item = ((ApplicationInfo) dragInfo).makeShortcut(); |
| } else { |
| item = (ShortcutInfo) dragInfo; |
| } |
| item.cellX = -1; |
| item.cellY = -1; |
| addItem(item); |
| } |
| |
| public DropTarget getDropTargetDelegate(DragObject d) { |
| return null; |
| } |
| |
| @Override |
| protected void dispatchDraw(Canvas canvas) { |
| super.dispatchDraw(canvas); |
| |
| if (mFolder == null) return; |
| if (mFolder.getItemCount() == 0) return; |
| |
| canvas.save(); |
| TextView v = (TextView) mFolder.getItemAt(0); |
| Drawable d = v.getCompoundDrawables()[1]; |
| int intrinsicIconSize = d.getIntrinsicHeight(); |
| |
| if (mOriginalWidth < 0 || mOriginalHeight < 0) { |
| mOriginalWidth = getMeasuredWidth(); |
| mOriginalHeight = getMeasuredHeight(); |
| } |
| final int previewSize = FolderRingAnimator.sPreviewSize; |
| final int previewPadding = FolderRingAnimator.sPreviewPadding; |
| |
| int halfAvailableSpace = (previewSize - 2 * previewPadding) / 2; |
| // cos(45) = 0.707 + ~= 0.1) |
| int availableSpace = (int) (halfAvailableSpace * (1 + 0.8f)); |
| |
| int unscaledHeight = (int) (intrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); |
| float baselineIconScale = (1.0f * availableSpace / unscaledHeight); |
| |
| int baselineSize = (int) (intrinsicIconSize * baselineIconScale); |
| float maxPerspectiveShift = baselineSize * PERSPECTIVE_SHIFT_FACTOR; |
| |
| ArrayList<View> items = mFolder.getItemsInReadingOrder(false); |
| int firstItemIndex = Math.max(0, items.size() - NUM_ITEMS_IN_PREVIEW); |
| |
| int xShift = (mOriginalWidth - 2 * halfAvailableSpace) / 2; |
| int yShift = previewPadding; |
| canvas.translate(xShift, yShift); |
| for (int i = firstItemIndex; i < items.size(); i++) { |
| int index = i - firstItemIndex; |
| index += Math.max(0, NUM_ITEMS_IN_PREVIEW - items.size()); |
| |
| float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); |
| float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); |
| |
| //r = (float) Math.pow(r, 2); |
| |
| float offset = (1 - r) * maxPerspectiveShift; |
| float scaledSize = scale * baselineSize; |
| float scaleOffsetCorrection = (1 - scale) * baselineSize; |
| |
| // We want to imagine our coordinates from the bottom left, growing up and to the |
| // right. This is natural for the x-axis, but for the y-axis, we have to invert things. |
| float transY = 2 * halfAvailableSpace - (offset + scaledSize + scaleOffsetCorrection); |
| float transX = offset + scaleOffsetCorrection; |
| |
| v = (TextView) items.get(i); |
| d = v.getCompoundDrawables()[1]; |
| |
| canvas.save(); |
| canvas.translate(transX, transY); |
| canvas.scale(baselineIconScale * scale, baselineIconScale * scale); |
| |
| int overlayAlpha = (int) (80 * (1 - r)); |
| if (d != null) { |
| d.setBounds(0, 0, intrinsicIconSize, intrinsicIconSize); |
| d.setFilterBitmap(true); |
| d.setColorFilter(Color.argb(overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP); |
| d.draw(canvas); |
| d.clearColorFilter(); |
| d.setFilterBitmap(false); |
| } |
| canvas.restore(); |
| } |
| canvas.restore(); |
| } |
| |
| public void onItemsChanged() { |
| invalidate(); |
| requestLayout(); |
| } |
| |
| public void onAdd(ShortcutInfo item) { |
| invalidate(); |
| requestLayout(); |
| } |
| |
| public void onRemove(ShortcutInfo item) { |
| invalidate(); |
| requestLayout(); |
| } |
| |
| public void onTitleChanged(CharSequence title) { |
| mFolderName.setText(title); |
| mFolderName.invalidate(); |
| mFolderName.requestLayout(); |
| } |
| } |