mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-28 15:56:49 +00:00
Updating the virtual preloader UX.
> No click feedback when in preloader mode > No preloader UI when drawn in drag layer > The preloader consists of a background 9 patch image and a circular progress is drawn in the content region of the background. > The preloader is drawn in a slightly larget area than the actual bounds to make the circular progress more prominent compared to the icon. issue: 15835307 Change-Id: Ifec3d93ecf1fac994d1128b517da3797247e7ed6
This commit is contained in:
@@ -1,96 +1,143 @@
|
||||
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 Bitmap sProgressBg, sProgressFill;
|
||||
private static final float ICON_SCALE_FACTOR = 0.5f;
|
||||
private static final int DEFAULT_COLOR = 0xFF009688;
|
||||
|
||||
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 static final Rect sTempRect = new Rect();
|
||||
|
||||
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;
|
||||
|
||||
setBounds(icon.getBounds());
|
||||
mPathChanged = false;
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
if (sProgressBg == null) {
|
||||
sProgressBg = BitmapFactory.decodeResource(res, R.drawable.bg_preloader);
|
||||
}
|
||||
if (sProgressFill == null) {
|
||||
sProgressFill = BitmapFactory.decodeResource(res, R.drawable.bg_preloader_progress);
|
||||
setBounds(icon.getBounds());
|
||||
applyTheme(theme);
|
||||
onLevelChange(0);
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -102,12 +149,6 @@ class PreloadIconDrawable extends Drawable {
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
mIcon.setBounds(bounds);
|
||||
mPathChanged = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
@@ -126,7 +167,6 @@ class PreloadIconDrawable extends Drawable {
|
||||
@Override
|
||||
protected boolean onLevelChange(int level) {
|
||||
mProgress = level;
|
||||
mPathChanged = true;
|
||||
|
||||
// Stop Animation
|
||||
if (mAnimator != null) {
|
||||
@@ -134,6 +174,14 @@ class PreloadIconDrawable extends Drawable {
|
||||
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;
|
||||
@@ -165,4 +213,37 @@ class PreloadIconDrawable extends Drawable {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user