blob: 20cd825fe3066bbd43ef47284a63003c4b5790e4 [file] [log] [blame]
/*
* Copyright 2018 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 android.graphics.drawable;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.MathUtils;
/**
* A Drawable that manages a {@link ColorDrawable} to make it stateful and backed by a
* {@link ColorStateList}.
*/
public class ColorStateListDrawable extends Drawable implements Drawable.Callback {
private ColorDrawable mColorDrawable;
private ColorStateListDrawableState mState;
private boolean mMutated = false;
public ColorStateListDrawable() {
mState = new ColorStateListDrawableState();
initializeColorDrawable();
}
public ColorStateListDrawable(@NonNull ColorStateList colorStateList) {
mState = new ColorStateListDrawableState();
initializeColorDrawable();
setColorStateList(colorStateList);
}
@Override
public void draw(@NonNull Canvas canvas) {
mColorDrawable.draw(canvas);
}
@Override
@IntRange(from = 0, to = 255)
public int getAlpha() {
return mColorDrawable.getAlpha();
}
@Override
public boolean isStateful() {
return mState.isStateful();
}
@Override
public boolean hasFocusStateSpecified() {
return mState.hasFocusStateSpecified();
}
@Override
public @NonNull Drawable getCurrent() {
return mColorDrawable;
}
@Override
public void applyTheme(@NonNull Resources.Theme t) {
super.applyTheme(t);
if (mState.mColor != null) {
setColorStateList(mState.mColor.obtainForTheme(t));
}
if (mState.mTint != null) {
setTintList(mState.mTint.obtainForTheme(t));
}
}
@Override
public boolean canApplyTheme() {
return super.canApplyTheme() || mState.canApplyTheme();
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
mState.mAlpha = alpha;
onStateChange(getState());
}
/**
* Remove the alpha override, reverting to the alpha defined on each color in the
* {@link ColorStateList}.
*/
public void clearAlpha() {
mState.mAlpha = -1;
onStateChange(getState());
}
@Override
public void setTintList(@Nullable ColorStateList tint) {
mState.mTint = tint;
mColorDrawable.setTintList(tint);
onStateChange(getState());
}
@Override
public void setTintBlendMode(@NonNull BlendMode blendMode) {
mState.mBlendMode = blendMode;
mColorDrawable.setTintBlendMode(blendMode);
onStateChange(getState());
}
@Override
public @Nullable ColorFilter getColorFilter() {
return mColorDrawable.getColorFilter();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mColorDrawable.setColorFilter(colorFilter);
}
@Override
public @PixelFormat.Opacity int getOpacity() {
return mColorDrawable.getOpacity();
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mColorDrawable.setBounds(bounds);
}
@Override
protected boolean onStateChange(int[] state) {
if (mState.mColor != null) {
int color = mState.mColor.getColorForState(state, mState.mColor.getDefaultColor());
if (mState.mAlpha != -1) {
color = (color & 0xFFFFFF) | MathUtils.constrain(mState.mAlpha, 0, 255) << 24;
}
if (color != mColorDrawable.getColor()) {
mColorDrawable.setColor(color);
mColorDrawable.setState(state);
return true;
} else {
return mColorDrawable.setState(state);
}
} else {
return false;
}
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
if (who == mColorDrawable && getCallback() != null) {
getCallback().invalidateDrawable(this);
}
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
if (who == mColorDrawable && getCallback() != null) {
getCallback().scheduleDrawable(this, what, when);
}
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
if (who == mColorDrawable && getCallback() != null) {
getCallback().unscheduleDrawable(this, what);
}
}
@Override
public @NonNull ConstantState getConstantState() {
mState.mChangingConfigurations = mState.mChangingConfigurations
| (getChangingConfigurations() & ~mState.getChangingConfigurations());
return mState;
}
/**
* Returns the ColorStateList backing this Drawable, or a new ColorStateList of the default
* ColorDrawable color if one hasn't been defined yet.
*
* @return a ColorStateList
*/
public @NonNull ColorStateList getColorStateList() {
if (mState.mColor == null) {
return ColorStateList.valueOf(mColorDrawable.getColor());
} else {
return mState.mColor;
}
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mState.getChangingConfigurations();
}
@Override
public @NonNull Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mState = new ColorStateListDrawableState(mState);
mMutated = true;
}
return this;
}
/**
* @hide
*/
@Override
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
/**
* Replace this Drawable's ColorStateList. It is not copied, so changes will propagate on the
* next call to {@link #setState(int[])}.
*
* @param colorStateList A color state list to attach.
*/
public void setColorStateList(@NonNull ColorStateList colorStateList) {
mState.mColor = colorStateList;
onStateChange(getState());
}
static final class ColorStateListDrawableState extends ConstantState {
ColorStateList mColor = null;
ColorStateList mTint = null;
int mAlpha = -1;
BlendMode mBlendMode = DEFAULT_BLEND_MODE;
@ActivityInfo.Config int mChangingConfigurations = 0;
ColorStateListDrawableState() {
}
ColorStateListDrawableState(ColorStateListDrawableState state) {
mColor = state.mColor;
mTint = state.mTint;
mAlpha = state.mAlpha;
mBlendMode = state.mBlendMode;
mChangingConfigurations = state.mChangingConfigurations;
}
@Override
public Drawable newDrawable() {
return new ColorStateListDrawable(this);
}
@Override
public @ActivityInfo.Config int getChangingConfigurations() {
return mChangingConfigurations
| (mColor != null ? mColor.getChangingConfigurations() : 0)
| (mTint != null ? mTint.getChangingConfigurations() : 0);
}
public boolean isStateful() {
return (mColor != null && mColor.isStateful())
|| (mTint != null && mTint.isStateful());
}
public boolean hasFocusStateSpecified() {
return (mColor != null && mColor.hasFocusStateSpecified())
|| (mTint != null && mTint.hasFocusStateSpecified());
}
@Override
public boolean canApplyTheme() {
return (mColor != null && mColor.canApplyTheme())
|| (mTint != null && mTint.canApplyTheme());
}
}
private ColorStateListDrawable(@NonNull ColorStateListDrawableState state) {
mState = state;
initializeColorDrawable();
}
private void initializeColorDrawable() {
mColorDrawable = new ColorDrawable();
mColorDrawable.setCallback(this);
if (mState.mTint != null) {
mColorDrawable.setTintList(mState.mTint);
}
if (mState.mBlendMode != DEFAULT_BLEND_MODE) {
mColorDrawable.setTintBlendMode(mState.mBlendMode);
}
}
}