c088049113
- This CL fixes an old assumption we had about the height of rows in AllApps, and ensures that we account for the difference in height between the predictive icons and the normal icons. - In addition, we refactor FastBitmapDrawable to have multiple states, which it manages in drawing itself, including the press state and fast scroll focus states. And we also refactor some of the fast scroll logic in the all apps recycler view out to its own class. Change-Id: I1988159b2767df733bbbfc7dc601859cde6c9943
359 lines
12 KiB
Java
359 lines
12 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.AnimatorSet;
|
|
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.SparseArray;
|
|
import android.view.animation.DecelerateInterpolator;
|
|
|
|
public class FastBitmapDrawable extends Drawable {
|
|
|
|
/**
|
|
* The possible states that a FastBitmapDrawable can be in.
|
|
*/
|
|
public enum State {
|
|
|
|
NORMAL (0f, 0f, 1f, new DecelerateInterpolator()),
|
|
PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
|
|
FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.1f, new DecelerateInterpolator()),
|
|
FAST_SCROLL_UNHIGHLIGHTED (0.8f, 0.35f, 1f, new DecelerateInterpolator()),
|
|
DISABLED (1f, 0.5f, 1f, new DecelerateInterpolator());
|
|
|
|
public final float desaturation;
|
|
public final float brightness;
|
|
/**
|
|
* Used specifically by the view drawing this FastBitmapDrawable.
|
|
*/
|
|
public final float viewScale;
|
|
public final TimeInterpolator interpolator;
|
|
|
|
State(float desaturation, float brightness, float viewScale, TimeInterpolator interpolator) {
|
|
this.desaturation = desaturation;
|
|
this.brightness = brightness;
|
|
this.viewScale = viewScale;
|
|
this.interpolator = interpolator;
|
|
}
|
|
}
|
|
|
|
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;
|
|
public static final int FAST_SCROLL_HIGHLIGHT_DURATION = 225;
|
|
public static final int FAST_SCROLL_UNHIGHLIGHT_DURATION = 150;
|
|
public static final int FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION = 225;
|
|
public static final int FAST_SCROLL_INACTIVE_DURATION = 275;
|
|
|
|
// 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();
|
|
|
|
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
|
private final Bitmap mBitmap;
|
|
private State mState = State.NORMAL;
|
|
|
|
// 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 properties
|
|
private AnimatorSet mPropertyAnimator;
|
|
|
|
public FastBitmapDrawable(Bitmap b) {
|
|
mBitmap = b;
|
|
setBounds(0, 0, b.getWidth(), b.getHeight());
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
/**
|
|
* Animates this drawable to a new state.
|
|
*
|
|
* @return whether the state has changed.
|
|
*/
|
|
public boolean animateState(State newState) {
|
|
State prevState = mState;
|
|
if (mState != newState) {
|
|
mState = newState;
|
|
|
|
mPropertyAnimator = cancelAnimator(mPropertyAnimator);
|
|
mPropertyAnimator = new AnimatorSet();
|
|
mPropertyAnimator.playTogether(
|
|
ObjectAnimator
|
|
.ofFloat(this, "desaturation", newState.desaturation),
|
|
ObjectAnimator
|
|
.ofFloat(this, "brightness", newState.brightness));
|
|
mPropertyAnimator.setInterpolator(newState.interpolator);
|
|
mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
|
|
mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
|
|
mPropertyAnimator.start();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Immediately sets this drawable to a new state.
|
|
*
|
|
* @return whether the state has changed.
|
|
*/
|
|
public boolean setState(State newState) {
|
|
if (mState != newState) {
|
|
mState = newState;
|
|
|
|
mPropertyAnimator = cancelAnimator(mPropertyAnimator);
|
|
|
|
setDesaturation(newState.desaturation);
|
|
setBrightness(newState.brightness);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the current state.
|
|
*/
|
|
public State getCurrentState() {
|
|
return mState;
|
|
}
|
|
|
|
/**
|
|
* Returns the duration for the state change animation.
|
|
*/
|
|
public static int getDurationForStateChange(State fromState, State toState) {
|
|
switch (toState) {
|
|
case NORMAL:
|
|
switch (fromState) {
|
|
case PRESSED:
|
|
return 0;
|
|
case FAST_SCROLL_HIGHLIGHTED:
|
|
case FAST_SCROLL_UNHIGHLIGHTED:
|
|
return FAST_SCROLL_INACTIVE_DURATION;
|
|
}
|
|
case PRESSED:
|
|
return CLICK_FEEDBACK_DURATION;
|
|
case FAST_SCROLL_HIGHLIGHTED:
|
|
return FAST_SCROLL_HIGHLIGHT_DURATION;
|
|
case FAST_SCROLL_UNHIGHLIGHTED:
|
|
switch (fromState) {
|
|
case NORMAL:
|
|
// When animating from normal state, take a little longer
|
|
return FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION;
|
|
default:
|
|
return FAST_SCROLL_UNHIGHLIGHT_DURATION;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the start delay when animating between certain fast scroll states.
|
|
*/
|
|
public static int getStartDelayForStateChange(State fromState, State toState) {
|
|
switch (toState) {
|
|
case FAST_SCROLL_UNHIGHLIGHTED:
|
|
switch (fromState) {
|
|
case NORMAL:
|
|
return FAST_SCROLL_UNHIGHLIGHT_DURATION / 4;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
|
|
*/
|
|
public 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]
|
|
*/
|
|
public void setBrightness(float brightness) {
|
|
int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
|
|
if (mBrightness != newBrightness) {
|
|
mBrightness = newBrightness;
|
|
updateFilter();
|
|
}
|
|
}
|
|
|
|
public 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();
|
|
}
|
|
|
|
private AnimatorSet cancelAnimator(AnimatorSet animator) {
|
|
if (animator != null) {
|
|
animator.removeAllListeners();
|
|
animator.cancel();
|
|
}
|
|
return null;
|
|
}
|
|
}
|