Updating the PreloadIconDrawable

> The drawable gets the path from MaskIconDrawable path, instead of
  using a circle
> The progress changes are animated as well

Bug: 34831873
Change-Id: I4e7f0b610f4fd94de8e0cfcf8b179b775cf0b4d8
This commit is contained in:
Sunny Goyal 2017-02-02 16:37:21 -08:00
parent e1fa0145d3
commit 96ac68a481
24 changed files with 443 additions and 350 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<maskable-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:color="#FFE0E0E0"/>
<foreground>
<com.android.launcher3.graphics.FixedScaleDrawable />
</foreground>
</maskable-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -72,12 +72,6 @@
<attr name="folderItems" 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>
<declare-styleable name="InsettableFrameLayout_Layout">
<attr name="layout_ignoreInsets" format="boolean" />
</declare-styleable>

View File

@ -52,5 +52,4 @@
<color name="fallback_secondary_color">#FF37474F</color>
<color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
<color name="legacy_icon_background">#FFE0E0E0</color>
</resources>

View File

@ -148,17 +148,4 @@
</style>
<style name="DropTargetButton" parent="DropTargetButtonBase" />
<!-- Virtual preloaders -->
<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>
</resources>

View File

@ -19,7 +19,6 @@ package com.android.launcher3;
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;
@ -27,7 +26,6 @@ import android.graphics.Paint;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@ -45,6 +43,7 @@ import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.HolographicOutlineHelper;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.PackageItemInfo;
import java.text.NumberFormat;
@ -57,8 +56,6 @@ import java.text.NumberFormat;
public class BubbleTextView extends TextView
implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView, ItemInfoUpdateReceiver {
private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
// Dimensions in DP
private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
private static final float KEY_SHADOW_RADIUS = 1f;
@ -423,10 +420,6 @@ public class BubbleTextView extends TextView
super.onAttachedToWindow();
if (mBackground != null) mBackground.setCallback(this);
if (mIcon instanceof PreloadIconDrawable) {
((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
}
mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@ -495,7 +488,8 @@ public class BubbleTextView extends TextView
if (mIcon instanceof PreloadIconDrawable) {
preloadDrawable = (PreloadIconDrawable) mIcon;
} else {
preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
preloadDrawable = DrawableFactory.get(getContext())
.newPendingIcon(info.iconBitmap, getContext());
setIcon(preloadDrawable);
}
@ -520,20 +514,6 @@ public class BubbleTextView extends TextView
: null;
}
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;
}
/**
* Sets the icon for this view based on the layout direction.
*/

View File

@ -120,7 +120,7 @@ public class FastBitmapDrawable extends Drawable {
public FastBitmapDrawable(Bitmap b) {
mBitmap = b;
setBounds(0, 0, b.getWidth(), b.getHeight());
setFilterBitmap(true);
}
public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {

View File

@ -17,7 +17,6 @@
package com.android.launcher3;
import android.content.Context;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -33,8 +32,8 @@ import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.View.OnClickListener;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.model.PackageItemInfo;
public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
@ -42,8 +41,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
private static final float MIN_SATUNATION = 0.7f;
private static Theme sPreloaderTheme;
private final Rect mRect = new Rect();
private View mDefaultView;
private OnClickListener mClickListener;
@ -149,13 +146,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
updateSettingColor();
} else {
if (sPreloaderTheme == null) {
sPreloaderTheme = getResources().newTheme();
sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
}
FastBitmapDrawable drawable = drawableFactory.newIcon(mIcon, mInfo);
mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
mCenterDrawable = DrawableFactory.get(getContext())
.newPendingIcon(mIcon, getContext());
mCenterDrawable.setCallback(this);
mSettingIconDrawable = null;
applyState();
@ -226,13 +218,10 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
if (mSettingIconDrawable == null) {
int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
int maxSize = grid.iconSizePx + 2 * outset;
int maxSize = grid.iconSizePx;
int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
mRect.set(0, 0, size, size);
mRect.inset(outset, outset);
mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
mCenterDrawable.setBounds(mRect);
} else {

View File

@ -1,252 +0,0 @@
package com.android.launcher3;
import android.animation.ObjectAnimator;
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.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
public 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 MIN_SATURATION = 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 final Rect sTempRect = new Rect();
private final RectF mIndicatorRect = new RectF();
private boolean mIndicatorRectDirty;
private final Paint mPaint;
public 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 float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
private ObjectAnimator mAnimator;
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());
applyPreloaderTheme(theme);
onLevelChange(0);
}
public void applyPreloaderTheme(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 = 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));
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;
mBgDrawable.setAlpha(255);
mBgDrawable.draw(canvas);
if (mProgress >= 100) {
canvas.drawOval(mIndicatorRect, mPaint);
} else if (mProgress > 0) {
canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
}
} else {
iconScale = 1;
}
canvas.save();
canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
mIcon.draw(canvas);
canvas.restore();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
mIcon.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mIcon.setColorFilter(cf);
}
@Override
protected boolean onLevelChange(int level) {
mProgress = level;
// Stop Animation
if (mAnimator != null) {
mAnimator.cancel();
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).setIsDisabled(level < 100);
}
invalidateSelf();
return true;
}
/**
* Runs the finish animation if it is has not been run after last level change.
*/
public void maybePerformFinishedAnimation() {
if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
return;
}
if (mAnimator != null) {
mAnimator.cancel();
}
setAnimationProgress(ANIMATION_PROGRESS_STARTED);
mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
mAnimator.start();
}
public void setAnimationProgress(float progress) {
if (progress != mAnimationProgress) {
mAnimationProgress = progress;
invalidateSelf();
}
}
public float getAnimationProgress() {
return mAnimationProgress;
}
public boolean hasNotCompleted() {
return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
}
@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_SATURATION) {
mIndicatorColor = DEFAULT_COLOR;
return mIndicatorColor;
}
hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
mIndicatorColor = Color.HSVToColor(hsv);
return mIndicatorColor;
}
}

View File

@ -72,6 +72,7 @@ import com.android.launcher3.dragndrop.SpringLoadedDragController;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;

View File

@ -53,13 +53,11 @@ import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.FolderInfo.FolderListener;
import com.android.launcher3.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.PreloadIconDrawable;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.SimpleOnStylusPressListener;
@ -245,7 +243,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
};
public Drawable prepareCreate(final View destView) {
Drawable animateDrawable = getTopDrawable((TextView) destView);
Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
return animateDrawable;
@ -270,7 +268,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
Drawable animateDrawable = getTopDrawable((TextView) finalView);
Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
finalView.getMeasuredWidth());
@ -771,11 +769,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
}
}
private Drawable getTopDrawable(TextView v) {
Drawable d = v.getCompoundDrawables()[1];
return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
}
class FolderPreviewItemAnim {
ValueAnimator mValueAnimator;
float finalScale;
@ -892,7 +885,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
for (int i = 0; i < mDrawingParams.size(); i++) {
PreviewItemDrawingParams p = mDrawingParams.get(i);
p.drawable = getTopDrawable((TextView) items.get(i));
p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1];
if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
computePreviewItemDrawingParams(i, nItemsInPreview, p);

View File

@ -27,7 +27,6 @@ import android.widget.TextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetHostView;
import com.android.launcher3.PreloadIconDrawable;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.ProviderConfig;
@ -185,10 +184,6 @@ public class DragPreviewProvider {
} else {
bounds.offsetTo(0, 0);
}
if (d instanceof PreloadIconDrawable) {
int inset = -((PreloadIconDrawable) d).getOutset();
bounds.inset(inset, inset);
}
return bounds;
}

View File

@ -21,12 +21,14 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.UserHandle;
import android.support.annotation.UiThread;
import android.util.Log;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.ItemInfo;
@ -41,9 +43,13 @@ import java.util.HashMap;
*/
public class DrawableFactory {
private static final String TAG = "DrawableFactory";
private static DrawableFactory sInstance;
private static final Object LOCK = new Object();
private Path mPreloadProgressPath;
public static DrawableFactory get(Context context) {
synchronized (LOCK) {
if (sInstance == null) {
@ -61,9 +67,38 @@ public class DrawableFactory {
* Returns a FastBitmapDrawable with the icon.
*/
public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
FastBitmapDrawable d = new FastBitmapDrawable(icon);
d.setFilterBitmap(true);
return d;
return new FastBitmapDrawable(icon);
}
/**
* Returns a FastBitmapDrawable with the icon.
*/
public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) {
if (mPreloadProgressPath == null) {
mPreloadProgressPath = getPreloadProgressPath(context);
}
return new PreloadIconDrawable(icon, mPreloadProgressPath);
}
protected Path getPreloadProgressPath(Context context) {
if (Utilities.isAtLeastO()) {
try {
// Try to load the path from Mask Icon
Drawable maskIcon = context.getDrawable(R.drawable.mask_drawable_wrapper);
maskIcon.setBounds(0, 0,
PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
return (Path) maskIcon.getClass().getMethod("getIconMask").invoke(maskIcon);
} catch (Exception e) {
Log.e(TAG, "Error loading mask icon", e);
}
}
// Create a circle static from top center and going clockwise.
Path p = new Path();
p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
return p;
}
public AllAppsBackgroundDrawable getAllAppsBackground(Context context) {

View File

@ -0,0 +1,41 @@
package com.android.launcher3.graphics;
import android.annotation.TargetApi;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.DrawableWrapper;
import android.os.Build;
import android.util.AttributeSet;
import org.xmlpull.v1.XmlPullParser;
/**
* Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
*/
@TargetApi(Build.VERSION_CODES.N)
public class FixedScaleDrawable extends DrawableWrapper {
// TODO b/33553066 use the constant defined in MaskableIconDrawable
private static final float LEGACY_ICON_SCALE = .7f * .6667f;
public FixedScaleDrawable() {
super(new ColorDrawable());
}
@Override
public void draw(Canvas canvas) {
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE,
getBounds().exactCenterX(), getBounds().exactCenterY());
super.draw(canvas);
canvas.restoreToCount(saveCount);
}
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
}

View File

@ -32,16 +32,42 @@ public class IconPalette {
private static final boolean DEBUG = false;
private static final String TAG = "IconPalette";
public int backgroundColor;
public int textColor;
public int secondaryColor;
private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
private static final int DEFAULT_PRELOAD_COLOR = 0xFF009688;
public final int dominantColor;
public final int backgroundColor;
public final int textColor;
public final int secondaryColor;
private IconPalette(int color) {
dominantColor = color;
backgroundColor = getMutedColor(dominantColor);
textColor = getTextColorForBackground(backgroundColor);
secondaryColor = getLowContrastColor(backgroundColor);
}
/**
* Returns a color suitable for the progress bar color of preload icon.
*/
public int getPreloadProgressColor() {
int result = dominantColor;
// Make sure that the dominant color has enough saturation to be visible properly.
float[] hsv = new float[3];
Color.colorToHSV(result, hsv);
if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
result = DEFAULT_PRELOAD_COLOR;
} else {
hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
result = Color.HSVToColor(hsv);
}
return result;
}
public static IconPalette fromDominantColor(int dominantColor) {
IconPalette palette = new IconPalette();
palette.backgroundColor = getMutedColor(dominantColor);
palette.textColor = getTextColorForBackground(palette.backgroundColor);
palette.secondaryColor = getLowContrastColor(palette.backgroundColor);
return palette;
return new IconPalette(dominantColor);
}
/**

View File

@ -29,13 +29,10 @@ import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.ScaleDrawable;
import android.os.Process;
import android.os.UserHandle;
import android.view.Gravity;
import com.android.launcher3.AppInfo;
import com.android.launcher3.IconCache;
@ -52,8 +49,6 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat;
* Helper methods for generating various launcher icons
*/
public class LauncherIcons {
// TODO b/33553066 use the constant defined in MaskableIconDrawable
private static final float LEGACY_ICON_SCALE = .7f * .6667f;
private static final Rect sOldBounds = new Rect();
private static final Canvas sCanvas = new Canvas();
@ -236,17 +231,16 @@ public class LauncherIcons {
if (!(ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isAtLeastO())) {
return drawable;
}
int color = context.getResources().getColor(R.color.legacy_icon_background);
ColorDrawable colorDrawable = new ColorDrawable(color);
ScaleDrawable scaleDrawable = new ScaleDrawable(drawable,
Gravity.CENTER, LEGACY_ICON_SCALE, LEGACY_ICON_SCALE);
scaleDrawable.setLevel(1);
try {
Class clazz = Class.forName("android.graphics.drawable.MaskableIconDrawable");
if (!clazz.isAssignableFrom(drawable.getClass())){
if (!clazz.isAssignableFrom(drawable.getClass())) {
Drawable maskWrapper =
context.getDrawable(R.drawable.mask_drawable_wrapper).mutate();
((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(maskWrapper))
.setDrawable(drawable);
return (Drawable) clazz.getConstructor(Drawable.class, Drawable.class)
.newInstance(colorDrawable, scaleDrawable);
return maskWrapper;
}
} catch (Exception e) {
return drawable;

View File

@ -0,0 +1,289 @@
/*
* Copyright (C) 2017 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.launcher3.graphics;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Rect;
import android.util.Property;
import android.util.SparseArray;
import android.view.animation.LinearInterpolator;
import com.android.launcher3.FastBitmapDrawable;
import java.lang.ref.WeakReference;
/**
* Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
*/
public class PreloadIconDrawable extends FastBitmapDrawable {
private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
@Override
public Float get(PreloadIconDrawable object) {
return object.mInternalStateProgress;
}
@Override
public void set(PreloadIconDrawable object, Float value) {
object.setInternalProgress(value);
}
};
public static final int PATH_SIZE = 100;
private static final float PROGRESS_WIDTH = 7;
private static final float PROGRESS_GAP = 2;
private static final int MAX_PAINT_ALPHA = 255;
private static final long DURATION_SCALE = 500;
// The smaller the number, the faster the animation would be.
// Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
private static final float COMPLETE_ANIM_FRACTION = 0.3f;
private static final int COLOR_TRACK = 0x77EEEEEE;
private static final int COLOR_SHADOW = 0x55000000;
private static final float SMALL_SCALE = 0.75f;
private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
private final Matrix mTmpMatrix = new Matrix();
private final PathMeasure mPathMeasure = new PathMeasure();
// Path in [0, 100] bounds.
private final Path mProgressPath;
private final Path mScaledTrackPath;
private final Path mScaledProgressPath;
private final Paint mProgressPaint;
private Bitmap mShadowBitmap;
private int mIndicatorColor = 0;
private int mTrackAlpha;
private float mTrackLength;
private float mIconScale;
private boolean mRanFinishAnimation;
// Progress of the internal state. [0, 1] indicates the fraction of completed progress,
// [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
private float mInternalStateProgress;
private ObjectAnimator mCurrentAnim;
/**
* @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
*/
public PreloadIconDrawable(Bitmap b, Path progressPath) {
super(b);
mProgressPath = progressPath;
mScaledTrackPath = new Path();
mScaledProgressPath = new Path();
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
setInternalProgress(0);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mTmpMatrix.setScale(
(bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
(bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
mTmpMatrix.postTranslate(
bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP,
bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP);
mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
float scale = bounds.width() / PATH_SIZE;
mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
(PROGRESS_GAP ) * scale);
mPathMeasure.setPath(mScaledTrackPath, true);
mTrackLength = mPathMeasure.getLength();
setInternalProgress(mInternalStateProgress);
}
private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
int key = (width << 16) | height;
WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
if (shadow != null) {
return shadow;
}
shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(shadow);
mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
mProgressPaint.setColor(COLOR_TRACK);
mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
c.drawPath(mScaledTrackPath, mProgressPaint);
mProgressPaint.clearShadowLayer();
c.setBitmap(null);
sShadowCache.put(key, new WeakReference<>(shadow));
return shadow;
}
@Override
public void draw(Canvas canvas) {
if (mRanFinishAnimation) {
super.draw(canvas);
return;
}
// Draw track.
mProgressPaint.setColor(mIndicatorColor);
mProgressPaint.setAlpha(mTrackAlpha);
if (mShadowBitmap != null) {
canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
}
canvas.drawPath(mScaledProgressPath, mProgressPaint);
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
Rect bounds = getBounds();
canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
drawInternal(canvas);
canvas.restoreToCount(saveCount);
}
/**
* Updates the install progress based on the level
*/
@Override
protected boolean onLevelChange(int level) {
// Run the animation if we have already been bound.
updateInternalState(level * 0.01f, getBounds().width() > 0, false);
return true;
}
/**
* Runs the finish animation if it is has not been run after last call to
* {@link #onLevelChange}
*/
public void maybePerformFinishedAnimation() {
// If the drawable was recently initialized, skip the progress animation.
if (mInternalStateProgress == 0) {
mInternalStateProgress = 1;
}
updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
}
public boolean hasNotCompleted() {
return !mRanFinishAnimation;
}
private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
if (mCurrentAnim != null) {
mCurrentAnim.cancel();
mCurrentAnim = null;
}
if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
return;
}
if (!shouldAnimate || mRanFinishAnimation) {
setInternalProgress(finalProgress);
} else {
mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
mCurrentAnim.setDuration(
(long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
mCurrentAnim.setInterpolator(new LinearInterpolator());
if (isFinish) {
mCurrentAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRanFinishAnimation = true;
}
});
}
mCurrentAnim.start();
}
}
/**
* Sets the internal progress and updates the UI accordingly
* for progress <= 0:
* - icon in the small scale and disabled state
* - progress track is visible
* - progress bar is not visible
* for 0 < progress < 1
* - icon in the small scale and disabled state
* - progress track is visible
* - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
* {@link #mScaledTrackPath}.
* @see PathMeasure#getSegment(float, float, Path, boolean)
* for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
* - we calculate fraction of progress in the above range
* - progress track is drawn with alpha based on fraction
* - progress bar is drawn at 100% with alpha based on fraction
* - icon is scaled up based on fraction and is drawn in enabled state
* for progress >= (1 + COMPLETE_ANIM_FRACTION)
* - only icon is drawn in normal state
*/
private void setInternalProgress(float progress) {
mInternalStateProgress = progress;
if (progress <= 0) {
mIconScale = SMALL_SCALE;
mScaledTrackPath.reset();
mTrackAlpha = MAX_PAINT_ALPHA;
setIsDisabled(true);
} else if (mIndicatorColor == 0) {
// Update the indicator color
mIndicatorColor = getIconPalette().getPreloadProgressColor();
}
if (progress < 1 && progress > 0) {
mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
mIconScale = SMALL_SCALE;
mTrackAlpha = MAX_PAINT_ALPHA;
setIsDisabled(true);
} else if (progress >= 1) {
setIsDisabled(false);
mScaledTrackPath.set(mScaledProgressPath);
float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
if (fraction >= 1) {
// Animation has completed
mIconScale = 1;
mTrackAlpha = 0;
} else {
mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction;
}
}
invalidateSelf();
}
}