<attr name="workspace" format="reference" />
</declare-styleable>
+ <declare-styleable name="PreloadIconDrawable">
+ <attr name="background" format="reference" />
+ <attr name="ringOutset" format="dimension" />
+ <attr name="indicatorSize" format="dimension" />
+ </declare-styleable>
+
<!-- Only used in the device overlays -->
<declare-styleable name="CustomClingTitleText">
</declare-styleable>
<item name="android:shadowRadius">2.0</item>
</style>
+ <style name="PreloadIcon">
+ <item name="background">@drawable/virtual_preload</item>
+ <item name="indicatorSize">4dp</item>
+ <item name="ringOutset">4dp</item>
+ </style>
+
+ <style name="PreloadIcon.Folder">
+ <item name="background">@drawable/virtual_preload_folder</item>
+ <item name="indicatorSize">4dp</item>
+ <item name="ringOutset">4dp</item>
+ </style>
+
<!-- Overridden in device overlays -->
<style name="CustomClingTitleText">
</style>
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
* too aggressive.
*/
public class BubbleTextView extends TextView {
+
+ private static SparseArray<Theme> sPreloaderThemes = new SparseArray<>(2);
+
static final float SHADOW_LARGE_RADIUS = 4.0f;
static final float SHADOW_SMALL_RADIUS = 1.75f;
static final float SHADOW_Y_OFFSET = 2.0f;
LauncherAppState app = LauncherAppState.getInstance();
FastBitmapDrawable iconDrawable = Utilities.createIconDrawable(b);
- if (info.isDisabled) {
- iconDrawable.setSaturation(0);
- iconDrawable.setBrightness(20);
- }
+ iconDrawable.setGhostModeEnabled(info.isDisabled);
setCompoundDrawables(null, iconDrawable, null, null);
if (setDefaultPadding) {
}
void setCellLayoutPressedOrFocusedIcon() {
- if (getParent() instanceof ShortcutAndWidgetContainer) {
+ // Disable pressed state when the icon is in preloader state.
+ if ((getParent() instanceof ShortcutAndWidgetContainer) &&
+ !(getCompoundDrawables()[1] instanceof PreloadIconDrawable)){
ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent();
if (parent != null) {
CellLayout layout = (CellLayout) parent.getParent();
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+
if (mBackground != null) mBackground.setCallback(this);
+ Drawable top = getCompoundDrawables()[1];
+
+ if (top instanceof PreloadIconDrawable) {
+ ((PreloadIconDrawable) top).applyTheme(getPreloaderTheme());
+ }
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
if (top instanceof PreloadIconDrawable) {
preloadDrawable = (PreloadIconDrawable) top;
} else {
- preloadDrawable = new PreloadIconDrawable(top, getResources());
+ preloadDrawable = new PreloadIconDrawable(top, getPreloaderTheme());
setCompoundDrawables(drawables[0], preloadDrawable, drawables[2], drawables[3]);
}
}
}
}
+
+ private Theme getPreloaderTheme() {
+ Object tag = getTag();
+ int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
+ (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder
+ : R.style.PreloadIcon;
+ Theme theme = sPreloaderThemes.get(style);
+ if (theme == null) {
+ theme = getResources().newTheme();
+ theme.applyStyle(style, true);
+ sPreloaderThemes.put(style, theme);
+ }
+ return theme;
+ }
}
// the drag view about the scaled child view.
toY += Math.round(toScale * tv.getPaddingTop());
toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
+ if (dragView.getDragVisualizeOffset() != null) {
+ toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y);
+ }
+
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
} else if (child instanceof FolderIcon) {
// Account for holographic blur padding on the drag view
class FastBitmapDrawable extends Drawable {
- private static final ColorMatrix sTempSaturationMatrix = new ColorMatrix();
- private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
+ private static ColorMatrix sGhostModeMatrix;
+ private static final ColorMatrix sTempMatrix = new ColorMatrix();
+
+ private static final int GHOST_MODE_MIN_COLOR_RANGE = 130;
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
private Bitmap mBitmap;
private int mAlpha;
- private float mSatutation = 1;
private int mBrightness = 0;
+ private boolean mGhostModeEnabled = false;
FastBitmapDrawable(Bitmap b) {
mAlpha = 255;
return mBitmap;
}
- public float getSaturation() {
- return mSatutation;
+ /**
+ * When enabled, the icon is grayed out and the contrast is increased to give it a 'ghost'
+ * appearance.
+ */
+ public void setGhostModeEnabled(boolean enabled) {
+ if (mGhostModeEnabled != enabled) {
+ mGhostModeEnabled = enabled;
+ updateFilter();
+ }
}
- public void setSaturation(float saturation) {
- mSatutation = saturation;
- updateFilter();
+ public boolean isGhostModeEnabled() {
+ return mGhostModeEnabled;
}
public int getBrightness() {
}
public void addBrightness(int amount) {
- mBrightness += amount;
- updateFilter();
+ setBrightness(mBrightness + amount);
}
public void setBrightness(int brightness) {
- mBrightness = brightness;
- updateFilter();
+ if (mBrightness != brightness) {
+ mBrightness = brightness;
+ updateFilter();
+ }
}
private void updateFilter() {
- if (mSatutation != 1 || mBrightness != 0) {
- sTempSaturationMatrix.setSaturation(mSatutation);
-
- if (mBrightness != 0) {
- // Brightness: C-new = C-old*(1-amount) + amount
- float scale = 1 - mBrightness / 255.0f;
- sTempBrightnessMatrix.setScale(scale, scale, scale, 1);
- float[] array = sTempBrightnessMatrix.getArray();
-
- // Add the amount to RGB components of the matrix, as per the above formula.
- // Fifth elements in the array correspond to the constant being added to
- // red, blue, green, and alpha channel respectively.
- array[4] = mBrightness;
- array[9] = mBrightness;
- array[14] = mBrightness;
- sTempSaturationMatrix.preConcat(sTempBrightnessMatrix);
+ if (mGhostModeEnabled) {
+ if (sGhostModeMatrix == null) {
+ sGhostModeMatrix = new ColorMatrix();
+ sGhostModeMatrix.setSaturation(0);
+
+ // For ghost mode, set the color range to [GHOST_MODE_MIN_COLOR_RANGE, 255]
+ float range = (255 - GHOST_MODE_MIN_COLOR_RANGE) / 255.0f;
+ sTempMatrix.set(new float[] {
+ range, 0, 0, 0, GHOST_MODE_MIN_COLOR_RANGE,
+ 0, range, 0, 0, GHOST_MODE_MIN_COLOR_RANGE,
+ 0, 0, range, 0, GHOST_MODE_MIN_COLOR_RANGE,
+ 0, 0, 0, 1, 0 });
+ sGhostModeMatrix.preConcat(sTempMatrix);
+ }
+
+ if (mBrightness == 0) {
+ mPaint.setColorFilter(new ColorMatrixColorFilter(sGhostModeMatrix));
+ } else {
+ setBrightnessMatrix(sTempMatrix, mBrightness);
+ sTempMatrix.postConcat(sGhostModeMatrix);
+ mPaint.setColorFilter(new ColorMatrixColorFilter(sTempMatrix));
}
- mPaint.setColorFilter(new ColorMatrixColorFilter(sTempSaturationMatrix));
+ } else if (mBrightness != 0) {
+ setBrightnessMatrix(sTempMatrix, mBrightness);
+ mPaint.setColorFilter(new ColorMatrixColorFilter(sTempMatrix));
} else {
mPaint.setColorFilter(null);
}
}
+
+ private static void setBrightnessMatrix(ColorMatrix matrix, int brightness) {
+ // Brightness: C-new = C-old*(1-amount) + amount
+ float scale = 1 - brightness / 255.0f;
+ matrix.setScale(scale, scale, scale, 1);
+ float[] array = matrix.getArray();
+
+ // Add the amount to RGB components of the matrix, as per the above formula.
+ // Fifth elements in the array correspond to the constant being added to
+ // red, blue, green, and alpha channel respectively.
+ array[4] = brightness;
+ array[9] = brightness;
+ array[14] = brightness;
+ }
}
if (mIsExternalDrag) {
si.cellX = mEmptyCell[0];
si.cellY = mEmptyCell[1];
+
+ // Actually move the item in the database if it was an external drag. Call this
+ // before creating the view, so that ShortcutInfo is updated appropriately.
+ LauncherModel.addOrMoveItemInDatabase(
+ mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
+
+ // We only need to update the locations if it doesn't get handled in #onDropCompleted.
+ if (d.dragSource != this) {
+ updateItemLocationsInDatabaseBatch();
+ }
+ mIsExternalDrag = false;
+
currentDragView = createAndAddShortcut(si);
} else {
currentDragView = mCurrentDragView;
mItemsInvalidated = true;
setupContentDimensions(getItemCount());
- // Actually move the item in the database if it was an external drag.
- if (mIsExternalDrag) {
- LauncherModel.addOrMoveItemInDatabase(
- mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
-
- // We only need to update the locations if it doesn't get handled in #onDropCompleted.
- if (d.dragSource != this) {
- updateItemLocationsInDatabaseBatch();
- }
- mIsExternalDrag = false;
- }
-
// Temporarily suppress the listener, as we did all the work already here.
mSuppressOnAdd = true;
mInfo.add(si);
float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
// These correspond two the drawable and view that the icon was dropped _onto_
- Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
+ Drawable animateDrawable = getTopDrawable((TextView) destView);
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
}
public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
- Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
+ Drawable animateDrawable = getTopDrawable((TextView) finalView);
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
finalView.getMeasuredWidth());
package com.android.launcher3;
import android.animation.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
-import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
class PreloadIconDrawable extends Drawable {
+
private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
private static final float ANIMATION_PROGRESS_STARTED = 0f;
private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
- private static final float ICON_SCALE_FACTOR = 0.6f;
+ private static final float MIN_SATUNATION = 0.2f;
+ private static final float MIN_LIGHTNESS = 0.6f;
+
+ private static final float ICON_SCALE_FACTOR = 0.5f;
+ private static final int DEFAULT_COLOR = 0xFF009688;
- private static Bitmap sProgressBg, sProgressFill;
+ private static final Rect sTempRect = new Rect();
- private final Rect mCanvasClipRect = new Rect();
- private final RectF mRect = new RectF();
- private final Path mProgressPath = new Path();
- private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ private final RectF mIndicatorRect = new RectF();
+ private boolean mIndicatorRectDirty;
+ private final Paint mPaint;
final Drawable mIcon;
+ private Drawable mBgDrawable;
+ private int mRingOutset;
+
+ private int mIndicatorColor = 0;
+
/**
* Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
* is shown with no progress bar.
*/
private int mProgress = 0;
- private boolean mPathChanged;
private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
private ObjectAnimator mAnimator;
- public PreloadIconDrawable(Drawable icon, Resources res) {
+ public PreloadIconDrawable(Drawable icon, Theme theme) {
mIcon = icon;
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+
setBounds(icon.getBounds());
- mPathChanged = false;
+ applyTheme(theme);
+ onLevelChange(0);
+ }
- if (sProgressBg == null) {
- sProgressBg = BitmapFactory.decodeResource(res, R.drawable.bg_preloader);
- }
- if (sProgressFill == null) {
- sProgressFill = BitmapFactory.decodeResource(res, R.drawable.bg_preloader_progress);
+ @Override
+ public void applyTheme(Theme t) {
+ TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
+ mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
+ mBgDrawable.setFilterBitmap(true);
+ mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
+ mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
+ ta.recycle();
+ onBoundsChange(getBounds());
+ invalidateSelf();
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ mIcon.setBounds(bounds);
+ if (mBgDrawable != null) {
+ sTempRect.set(bounds);
+ sTempRect.inset(-mRingOutset, -mRingOutset);
+ mBgDrawable.setBounds(sTempRect);
}
+ mIndicatorRectDirty = true;
+ }
+
+ public int getOutset() {
+ return mRingOutset;
+ }
+
+ /**
+ * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
+ * half the stroke size to accommodate the indicator.
+ */
+ private void initIndicatorRect() {
+ Drawable d = mBgDrawable;
+ Rect bounds = d.getBounds();
+
+ d.getPadding(sTempRect);
+ // Amount by which padding has to be scaled
+ float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
+ float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
+ mIndicatorRect.set(
+ bounds.left + sTempRect.left * paddingScaleX,
+ bounds.top + sTempRect.top * paddingScaleY,
+ bounds.right - sTempRect.right * paddingScaleX,
+ bounds.bottom - sTempRect.bottom * paddingScaleY);
+
+ float inset = mPaint.getStrokeWidth() / 2;
+ mIndicatorRect.inset(inset, inset);
+ mIndicatorRectDirty = false;
}
@Override
public void draw(Canvas canvas) {
- final Rect r = getBounds();
- if (canvas.getClipBounds(mCanvasClipRect) && !Rect.intersects(mCanvasClipRect, r)) {
+ final Rect r = new Rect(getBounds());
+ if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
// The draw region has been clipped.
return;
}
+ if (mIndicatorRectDirty) {
+ initIndicatorRect();
+ }
final float iconScale;
if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
&& (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
- canvas.drawBitmap(sProgressBg, null, r, mPaint);
- canvas.drawBitmap(sProgressFill, null, r, mPaint);
- iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
+ mBgDrawable.setAlpha(mPaint.getAlpha());
+ mBgDrawable.draw(canvas);
+ canvas.drawOval(mIndicatorRect, mPaint);
+ iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
} else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
mPaint.setAlpha(255);
iconScale = ICON_SCALE_FACTOR;
- canvas.drawBitmap(sProgressBg, null, r, mPaint);
+ mBgDrawable.setAlpha(255);
+ mBgDrawable.draw(canvas);
if (mProgress >= 100) {
- canvas.drawBitmap(sProgressFill, null, r, mPaint);
+ canvas.drawOval(mIndicatorRect, mPaint);
} else if (mProgress > 0) {
- if (mPathChanged) {
- mProgressPath.reset();
- mProgressPath.moveTo(r.exactCenterX(), r.centerY());
-
- mRect.set(r);
- mProgressPath.arcTo(mRect, -90, mProgress * 3.6f);
- mProgressPath.close();
- mPathChanged = false;
- }
-
- canvas.save();
- canvas.clipPath(mProgressPath);
- canvas.drawBitmap(sProgressFill, null, r, mPaint);
- canvas.restore();
+ canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
}
} else {
iconScale = 1;
}
@Override
- protected void onBoundsChange(Rect bounds) {
- mIcon.setBounds(bounds);
- mPathChanged = true;
- }
-
- @Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
protected boolean onLevelChange(int level) {
mProgress = level;
- mPathChanged = true;
// Stop Animation
if (mAnimator != null) {
mAnimator = null;
}
mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
+ if (level > 0) {
+ // Set the paint color only when the level changes, so that the dominant color
+ // is only calculated when needed.
+ mPaint.setColor(getIndicatorColor());
+ }
+ if (mIcon instanceof FastBitmapDrawable) {
+ ((FastBitmapDrawable) mIcon).setGhostModeEnabled(level <= 0);
+ }
invalidateSelf();
return true;
public float getAnimationProgress() {
return mAnimationProgress;
}
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIcon.getIntrinsicHeight();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIcon.getIntrinsicWidth();
+ }
+
+ private int getIndicatorColor() {
+ if (mIndicatorColor != 0) {
+ return mIndicatorColor;
+ }
+ if (!(mIcon instanceof FastBitmapDrawable)) {
+ mIndicatorColor = DEFAULT_COLOR;
+ return mIndicatorColor;
+ }
+ mIndicatorColor = Utilities.findDominantColorByHue(
+ ((FastBitmapDrawable) mIcon).getBitmap(), 20);
+
+ // Make sure that the dominant color has enough saturation to be visible properly.
+ float[] hsv = new float[3];
+ Color.colorToHSV(mIndicatorColor, hsv);
+ if (hsv[1] < MIN_SATUNATION) {
+ mIndicatorColor = DEFAULT_COLOR;
+ return mIndicatorColor;
+ }
+ hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
+ mIndicatorColor = Color.HSVToColor(hsv);
+ return mIndicatorColor;
+ }
}
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import android.widget.Toast;
return false;
}
}
+
+ /**
+ * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+ * @param bitmap The bitmap to scan
+ * @param samples The approximate max number of samples to use.
+ */
+ static int findDominantColorByHue(Bitmap bitmap, int samples) {
+ final int height = bitmap.getHeight();
+ final int width = bitmap.getWidth();
+ int sampleStride = (int) Math.sqrt((height * width) / samples);
+ if (sampleStride < 1) {
+ sampleStride = 1;
+ }
+
+ // This is an out-param, for getting the hsv values for an rgb
+ float[] hsv = new float[3];
+
+ // First get the best hue, by creating a histogram over 360 hue buckets,
+ // where each pixel contributes a score weighted by saturation, value, and alpha.
+ float[] hueScoreHistogram = new float[360];
+ float highScore = -1;
+ int bestHue = -1;
+
+ for (int y = 0; y < height; y += sampleStride) {
+ for (int x = 0; x < width; x += sampleStride) {
+ int argb = bitmap.getPixel(x, y);
+ int alpha = 0xFF & (argb >> 24);
+ if (alpha < 0x80) {
+ // Drop mostly-transparent pixels.
+ continue;
+ }
+ // Remove the alpha channel.
+ int rgb = argb | 0xFF000000;
+ Color.colorToHSV(rgb, hsv);
+ // Bucket colors by the 360 integer hues.
+ int hue = (int) hsv[0];
+ if (hue < 0 || hue >= hueScoreHistogram.length) {
+ // Defensively avoid array bounds violations.
+ continue;
+ }
+ float score = hsv[1] * hsv[2];
+ hueScoreHistogram[hue] += score;
+ if (hueScoreHistogram[hue] > highScore) {
+ highScore = hueScoreHistogram[hue];
+ bestHue = hue;
+ }
+ }
+ }
+
+ SparseArray<Float> rgbScores = new SparseArray<Float>();
+ int bestColor = 0xff000000;
+ highScore = -1;
+ // Go back over the RGB colors that match the winning hue,
+ // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
+ // The highest-scoring RGB color wins.
+ for (int y = 0; y < height; y += sampleStride) {
+ for (int x = 0; x < width; x += sampleStride) {
+ int rgb = bitmap.getPixel(x, y) | 0xff000000;
+ Color.colorToHSV(rgb, hsv);
+ int hue = (int) hsv[0];
+ if (hue == bestHue) {
+ float s = hsv[1];
+ float v = hsv[2];
+ int bucket = (int) (s * 100) + (int) (v * 10000);
+ // Score by cumulative saturation * value.
+ float score = s * v;
+ Float oldTotal = rgbScores.get(bucket);
+ float newTotal = oldTotal == null ? score : oldTotal + score;
+ rgbScores.put(bucket, newTotal);
+ if (newTotal > highScore) {
+ highScore = newTotal;
+ // All the colors in the winning bucket are very similar. Last in wins.
+ bestColor = rgb;
+ }
+ }
+ }
+ }
+ return bestColor;
+ }
}
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* The workspace is a wide area with a wallpaper and a finite number of pages.
d.copyBounds(bounds);
if (bounds.width() == 0 || bounds.height() == 0) {
bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ } else {
+ bounds.offsetTo(0, 0);
+ }
+ if (d instanceof PreloadIconDrawable) {
+ int inset = -((PreloadIconDrawable) d).getOutset();
+ bounds.inset(inset, inset);
}
return bounds;
}
Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
- drawDragView(v, c, 0, true);
+ drawDragView(v, c, 0);
c.setBitmap(null);
// The outline is used to visualize where the item will land if dropped
* @param destCanvas the canvas to draw on
* @param padding the horizontal and vertical padding to use when drawing
*/
- private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
+ private void drawDragView(View v, Canvas destCanvas, int padding) {
final Rect clipRect = mTempRect;
v.getDrawingRect(clipRect);
boolean textVisible = false;
destCanvas.save();
- if (v instanceof TextView && pruneToDrawable) {
+ if (v instanceof TextView) {
Drawable d = ((TextView) v).getCompoundDrawables()[1];
Rect bounds = getDrawableBounds(d);
clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
- destCanvas.translate(padding / 2, padding / 2);
+ destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
d.draw(destCanvas);
} else {
if (v instanceof FolderIcon) {
((FolderIcon) v).setTextVisible(false);
textVisible = true;
}
- } else if (v instanceof BubbleTextView) {
- final BubbleTextView tv = (BubbleTextView) v;
- clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
- tv.getLayout().getLineTop(0);
- } else if (v instanceof TextView) {
- final TextView tv = (TextView) v;
- clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
- tv.getLayout().getLineTop(0);
}
destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
destCanvas.clipRect(clipRect, Op.REPLACE);
/**
* Returns a new bitmap to show when the given View is being dragged around.
* Responsibility for the bitmap is transferred to the caller.
+ * @param expectedPadding padding to add to the drag view. If a different padding was used
+ * its value will be changed
*/
- public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+ public Bitmap createDragBitmap(View v, Canvas canvas, AtomicInteger expectedPadding) {
Bitmap b;
+ int padding = expectedPadding.get();
if (v instanceof TextView) {
Drawable d = ((TextView) v).getCompoundDrawables()[1];
Rect bounds = getDrawableBounds(d);
b = Bitmap.createBitmap(bounds.width() + padding,
bounds.height() + padding, Bitmap.Config.ARGB_8888);
+ expectedPadding.set(padding - bounds.left - bounds.top);
} else {
b = Bitmap.createBitmap(
v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
}
canvas.setBitmap(b);
- drawDragView(v, canvas, padding, true);
+ drawDragView(v, canvas, padding);
canvas.setBitmap(null);
return b;
v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
canvas.setBitmap(b);
- drawDragView(v, canvas, padding, true);
+ drawDragView(v, canvas, padding);
mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
canvas.setBitmap(null);
return b;
public void beginDragShared(View child, DragSource source) {
mLauncher.onDragStarted(child);
// The drag bitmap follows the touch point around on the screen
- final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
+ AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
+ final Bitmap b = createDragBitmap(child, new Canvas(), padding);
final int bmpWidth = b.getWidth();
final int bmpHeight = b.getHeight();
float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
- - DRAG_BITMAP_PADDING / 2);
+ - padding.get() / 2);
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
dragLayerY += top;
// Note: The drag region is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
- dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
+ dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
dragRect = new Rect(left, top, right, bottom);
} else if (child instanceof FolderIcon) {
int previewSize = grid.folderIconSizePx;
mLauncher.onDragStarted(child);
// Compose a new drag bitmap that is of the icon size
- final Bitmap tmpB = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
+ AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
+ final Bitmap tmpB = createDragBitmap(child, new Canvas(), padding);
Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
Paint p = new Paint();
p.setFilterBitmap(true);
// Note: The drag region is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
- Point dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
+ Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
Rect dragRect = new Rect(0, 0, iconSize, iconSize);
if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
} else {
cellLayout.findCellForSpan(mTargetCell, 1, 1);
}
+ // Add the item to DB before adding to screen ensures that the container and other
+ // values of the info is properly updated.
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
+ mTargetCell[0], mTargetCell[1]);
+
addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
info.spanY, insertAtFirst);
cellLayout.onDropChild(view);
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
cellLayout.getShortcutsAndWidgets().measureChild(view);
- LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
- lp.cellX, lp.cellY);
-
if (d.dragView != null) {
// We wrap the animation call in the temporary set and reset of the current
// cellLayout to its final transform -- this means we animate the drag view to