Files
lawnchair/src/com/android/launcher3/FastBitmapDrawable.java
Sunny Goyal 828b11e5a9 Setting the callback for previewItems to folderIcon. This allows the FolderIcon to get updated
without going through the child'draw pass.
Also simplifying the draw code for the FolderIcon to remove any cycling invalidate calls

Bug: 62900800
Change-Id: I17009a5347a1c3c35426313ac759e0240ce6a395
2017-06-22 10:25:43 -07:00

314 lines
10 KiB
Java

/*
* 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.launcher3;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.util.SparseArray;
import com.android.launcher3.graphics.IconPalette;
public class FastBitmapDrawable extends Drawable {
private static final float PRESSED_BRIGHTNESS = 100f / 255f;
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
@Override
public float getInterpolation(float input) {
if (input < 0.05f) {
return input / 0.05f;
} else if (input < 0.3f){
return 1;
} else {
return (1 - input) / 0.7f;
}
}
};
public static final int CLICK_FEEDBACK_DURATION = 2000;
// Since we don't need 256^2 values for combinations of both the brightness and saturation, we
// reduce the value space to a smaller value V, which reduces the number of cached
// ColorMatrixColorFilters that we need to keep to V^2
private static final int REDUCED_FILTER_VALUE_SPACE = 48;
// A cache of ColorFilters for optimizing brightness and saturation animations
private static final SparseArray<ColorFilter> sCachedFilter = new SparseArray<>();
// Temporary matrices used for calculation
private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
private final Bitmap mBitmap;
private boolean mIsPressed;
private boolean mIsDisabled;
private IconPalette mIconPalette;
private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
= new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
@Override
public Float get(FastBitmapDrawable fastBitmapDrawable) {
return fastBitmapDrawable.getBrightness();
}
@Override
public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
fastBitmapDrawable.setBrightness(value);
}
};
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
private int mDesaturation = 0;
private int mBrightness = 0;
private int mAlpha = 255;
private int mPrevUpdateKey = Integer.MAX_VALUE;
// Animators for the fast bitmap drawable's brightness
private ObjectAnimator mBrightnessAnimator;
public FastBitmapDrawable(Bitmap b) {
mBitmap = b;
setFilterBitmap(true);
}
@Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
}
public IconPalette getIconPalette() {
if (mIconPalette == null) {
mIconPalette = IconPalette.fromDominantColor(Utilities
.findDominantColorByHue(mBitmap, 20), true /* desaturateBackground */);
}
return mIconPalette;
}
@Override
public void setColorFilter(ColorFilter cf) {
// No op
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
mPaint.setAlpha(alpha);
}
@Override
public void setFilterBitmap(boolean filterBitmap) {
mPaint.setFilterBitmap(filterBitmap);
mPaint.setAntiAlias(filterBitmap);
}
public int getAlpha() {
return mAlpha;
}
@Override
public int getIntrinsicWidth() {
return mBitmap.getWidth();
}
@Override
public int getIntrinsicHeight() {
return mBitmap.getHeight();
}
@Override
public int getMinimumWidth() {
return getBounds().width();
}
@Override
public int getMinimumHeight() {
return getBounds().height();
}
public Bitmap getBitmap() {
return mBitmap;
}
@Override
public boolean isStateful() {
return true;
}
@Override
public ColorFilter getColorFilter() {
return mPaint.getColorFilter();
}
@Override
protected boolean onStateChange(int[] state) {
boolean isPressed = false;
for (int s : state) {
if (s == android.R.attr.state_pressed) {
isPressed = true;
break;
}
}
if (mIsPressed != isPressed) {
mIsPressed = isPressed;
if (mBrightnessAnimator != null) {
mBrightnessAnimator.cancel();
}
if (mIsPressed) {
// Animate when going to pressed state
mBrightnessAnimator = ObjectAnimator.ofFloat(
this, BRIGHTNESS, getExpectedBrightness());
mBrightnessAnimator.setDuration(CLICK_FEEDBACK_DURATION);
mBrightnessAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
mBrightnessAnimator.start();
} else {
setBrightness(getExpectedBrightness());
}
return true;
}
return false;
}
private void invalidateDesaturationAndBrightness() {
setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
setBrightness(getExpectedBrightness());
}
private float getExpectedBrightness() {
return mIsDisabled ? DISABLED_BRIGHTNESS :
(mIsPressed ? PRESSED_BRIGHTNESS : 0);
}
public void setIsDisabled(boolean isDisabled) {
if (mIsDisabled != isDisabled) {
mIsDisabled = isDisabled;
invalidateDesaturationAndBrightness();
}
}
/**
* Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
*/
private void setDesaturation(float desaturation) {
int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
if (mDesaturation != newDesaturation) {
mDesaturation = newDesaturation;
updateFilter();
}
}
public float getDesaturation() {
return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
}
/**
* Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
*/
private void setBrightness(float brightness) {
int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
if (mBrightness != newBrightness) {
mBrightness = newBrightness;
updateFilter();
}
}
private float getBrightness() {
return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
}
/**
* Updates the paint to reflect the current brightness and saturation.
*/
private void updateFilter() {
boolean usePorterDuffFilter = false;
int key = -1;
if (mDesaturation > 0) {
key = (mDesaturation << 16) | mBrightness;
} else if (mBrightness > 0) {
// Compose a key with a fully saturated icon if we are just animating brightness
key = (1 << 16) | mBrightness;
// We found that in L, ColorFilters cause drawing artifacts with shadows baked into
// icons, so just use a PorterDuff filter when we aren't animating saturation
usePorterDuffFilter = true;
}
// Debounce multiple updates on the same frame
if (key == mPrevUpdateKey) {
return;
}
mPrevUpdateKey = key;
if (key != -1) {
ColorFilter filter = sCachedFilter.get(key);
if (filter == null) {
float brightnessF = getBrightness();
int brightnessI = (int) (255 * brightnessF);
if (usePorterDuffFilter) {
filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
PorterDuff.Mode.SRC_ATOP);
} else {
float saturationF = 1f - getDesaturation();
sTempFilterMatrix.setSaturation(saturationF);
if (mBrightness > 0) {
// Brightness: C-new = C-old*(1-amount) + amount
float scale = 1f - brightnessF;
float[] mat = sTempBrightnessMatrix.getArray();
mat[0] = scale;
mat[6] = scale;
mat[12] = scale;
mat[4] = brightnessI;
mat[9] = brightnessI;
mat[14] = brightnessI;
sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
}
filter = new ColorMatrixColorFilter(sTempFilterMatrix);
}
sCachedFilter.append(key, filter);
}
mPaint.setColorFilter(filter);
} else {
mPaint.setColorFilter(null);
}
invalidateSelf();
}
}