/* * 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.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.FrameLayout; import android.widget.TextView; import com.android.launcher.R; import com.android.launcher2.FolderInfo.FolderListener; /** * An icon that can appear on in the workspace representing an {@link UserFolder}. */ public class FolderIcon extends FrameLayout implements DropTarget, FolderListener { private Launcher mLauncher; Folder mFolder; FolderInfo mInfo; // The number of icons to display in the private static final int NUM_ITEMS_IN_PREVIEW = 4; 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 inner ring is scaled in its natural state private static final float INNER_RING_BASELINE_SCALE = 1.0f; // The degree to which the outer ring grows when accepting drop private static final float OUTER_RING_BASELINE_SCALE = 0.7f; // The degree to which the outer ring is scaled in its natural state private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; // The amount of vertical spread between items in the stack [0...1] private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f; // 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.3f; // The percentage of the FolderIcons view that will be dedicated to the items preview private static final float SPACE_PERCENTAGE_FOR_ICONS = 0.8f; public static Drawable sFolderOuterRingDrawable = null; public static Drawable sFolderInnerRingDrawable = null; private int mOriginalWidth = -1; private int mOriginalHeight = -1; private int mFolderLocX; private int mFolderLocY; private float mOuterRingScale; private float mInnerRingScale; 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); final Resources resources = launcher.getResources(); Drawable d = iconCache.getFullResIcon(resources, R.drawable.portal_ring_inner_holo); icon.setBackgroundDrawable(d); 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; folderInfo.addListener(icon); if (sFolderOuterRingDrawable == null) { sFolderOuterRingDrawable = launcher.getResources().getDrawable(R.drawable.portal_ring_outer_holo); } if (sFolderInnerRingDrawable == null) { sFolderInnerRingDrawable = launcher.getResources().getDrawable(R.drawable.portal_ring_inner_holo); } return icon; } 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(DragObject d) { final ItemInfo item = (ItemInfo) d.dragInfo; return willAcceptItem(item); } public void addItem(ShortcutInfo item) { mInfo.add(item); LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); } public void onDrop(DragObject d) { ShortcutInfo item; if (d.dragInfo instanceof ApplicationInfo) { // Came from all apps -- make a copy item = ((ApplicationInfo) d.dragInfo).makeShortcut(); } else { item = (ShortcutInfo) d.dragInfo; } item.cellX = -1; item.cellY = -1; addItem(item); } void saveState(CellLayout.LayoutParams lp) { mOriginalWidth = lp.width; mOriginalHeight = lp.height; } private void animateToAcceptState() { ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); va.setDuration(CONSUMPTION_ANIMATION_DURATION); va.addUpdateListener(new AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { final float percent = (Float) animation.getAnimatedValue(); mOuterRingScale = OUTER_RING_BASELINE_SCALE + percent * OUTER_RING_GROWTH_FACTOR; mInnerRingScale = INNER_RING_BASELINE_SCALE + percent * INNER_RING_GROWTH_FACTOR; mLauncher.getWorkspace().invalidate(); invalidate(); } }); va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // Instead of setting the background drawable to null, we set the color to // transparent. Setting the background drawable to null results in onDraw // not getting called. setBackgroundColor(Color.TRANSPARENT); requestLayout(); } }); va.start(); } private void animateToNaturalState() { ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); va.setDuration(CONSUMPTION_ANIMATION_DURATION); va.addUpdateListener(new AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { final float percent = (Float) animation.getAnimatedValue(); mOuterRingScale = OUTER_RING_BASELINE_SCALE + OUTER_RING_GROWTH_FACTOR - percent * OUTER_RING_GROWTH_FACTOR; mInnerRingScale = INNER_RING_BASELINE_SCALE + INNER_RING_GROWTH_FACTOR - percent * INNER_RING_GROWTH_FACTOR; mLauncher.getWorkspace().invalidate(); invalidate(); } }); va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { setBackgroundDrawable(sFolderInnerRingDrawable); mLauncher.getWorkspace().hideFolderAccept(FolderIcon.this); } }); va.start(); } private void determineFolderLocationInWorkspace() { int tvLocation[] = new int[2]; int wsLocation[] = new int[2]; getLocationOnScreen(tvLocation); mLauncher.getWorkspace().getLocationOnScreen(wsLocation); mFolderLocX = tvLocation[0] - wsLocation[0] + getMeasuredWidth() / 2; mFolderLocY = tvLocation[1] - wsLocation[1] + getMeasuredHeight() / 2; } public void onDragEnter(DragObject d) { if (!willAcceptItem((ItemInfo) d.dragInfo)) return; determineFolderLocationInWorkspace(); mLauncher.getWorkspace().showFolderAccept(this); animateToAcceptState(); } public void onDragOver(DragObject d) { } public void onDragExit(DragObject d) { if (!willAcceptItem((ItemInfo) d.dragInfo)) return; animateToNaturalState(); } public DropTarget getDropTargetDelegate(DragObject d) { return null; } public void getFolderLocation(int[] loc) { loc[0] = mFolderLocX; loc[1] = mFolderLocY; } public float getOuterRingScale() { return mOuterRingScale; } public float getInnerRingScale() { return mInnerRingScale; } @Override protected void onDraw(Canvas canvas) { if (mFolder == null) return; if (mFolder.getItemCount() == 0) return; canvas.save(); TextView v = (TextView) mFolder.getItemAt(0); Drawable d = v.getCompoundDrawables()[1]; if (mOriginalWidth < 0 || mOriginalHeight < 0) { mOriginalWidth = getMeasuredWidth(); mOriginalHeight = getMeasuredHeight(); } int unscaledHeight = (int) (d.getIntrinsicHeight() * (1 + PERSPECTIVE_SHIFT_FACTOR)); float baselineIconScale = SPACE_PERCENTAGE_FOR_ICONS / (unscaledHeight / (mOriginalHeight * 1.0f)); int baselineHeight = (int) (d.getIntrinsicHeight() * baselineIconScale); int totalStackHeight = (int) (baselineHeight * (1 + PERSPECTIVE_SHIFT_FACTOR)); int baselineWidth = (int) (d.getIntrinsicWidth() * baselineIconScale); float maxPerpectiveShift = baselineHeight * PERSPECTIVE_SHIFT_FACTOR; ArrayList items = mFolder.getItemsInReadingOrder(); int firstItemIndex = Math.max(0, items.size() - NUM_ITEMS_IN_PREVIEW); int xShift = (int) (mOriginalWidth - baselineWidth) / 2; int yShift = (int) (mOriginalHeight - totalStackHeight) / 2; 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 transY = r * maxPerpectiveShift; float transX = (1 - scale) * baselineWidth / 2.0f; 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, d.getIntrinsicWidth(), d.getIntrinsicHeight()); d.setColorFilter(Color.argb(overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP); d.draw(canvas); d.clearColorFilter(); } canvas.restore(); } canvas.restore(); } public void onAdd(ShortcutInfo item) { invalidate(); requestLayout(); } public void onRemove(ShortcutInfo item) { invalidate(); requestLayout(); } }