/* * Copyright (C) 2015 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 org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import java.io.IOException; import java.util.Collection; /** * Drawable container with only one child element. */ public abstract class DrawableWrapper extends Drawable implements Drawable.Callback { private DrawableWrapperState mState; private Drawable mDrawable; private boolean mMutated; DrawableWrapper(DrawableWrapperState state, Resources res) { mState = state; updateLocalState(res); } /** * Creates a new wrapper around the specified drawable. * * @param dr the drawable to wrap */ public DrawableWrapper(@Nullable Drawable dr) { mState = null; mDrawable = dr; } /** * Initializes local dynamic properties from state. This should be called * after significant state changes, e.g. from the One True Constructor and * after inflating or applying a theme. */ private void updateLocalState(Resources res) { if (mState != null && mState.mDrawableState != null) { final Drawable dr = mState.mDrawableState.newDrawable(res); setDrawable(dr); } } /** * Sets the wrapped drawable. * * @param dr the wrapped drawable */ public void setDrawable(@Nullable Drawable dr) { if (mDrawable != null) { mDrawable.setCallback(null); } mDrawable = dr; if (dr != null) { dr.setCallback(this); // Only call setters for data that's stored in the base Drawable. dr.setVisible(isVisible(), true); dr.setState(getState()); dr.setLevel(getLevel()); dr.setBounds(getBounds()); dr.setLayoutDirection(getLayoutDirection()); if (mState != null) { mState.mDrawableState = dr.getConstantState(); } } invalidateSelf(); } /** * @return the wrapped drawable */ @Nullable public Drawable getDrawable() { return mDrawable; } void updateStateFromTypedArray(TypedArray a) { final DrawableWrapperState state = mState; if (state == null) { return; } // Account for any configuration changes. state.mChangingConfigurations |= a.getChangingConfigurations(); // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); // TODO: Consider using R.styleable.DrawableWrapper_drawable } @Override public void applyTheme(Resources.Theme t) { super.applyTheme(t); final DrawableWrapperState state = mState; if (state == null) { return; } if (mDrawable != null && mDrawable.canApplyTheme()) { mDrawable.applyTheme(t); } } @Override public boolean canApplyTheme() { return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); } @Override public void invalidateDrawable(Drawable who) { final Callback callback = getCallback(); if (callback != null) { callback.invalidateDrawable(this); } } @Override public void scheduleDrawable(Drawable who, Runnable what, long when) { final Callback callback = getCallback(); if (callback != null) { callback.scheduleDrawable(this, what, when); } } @Override public void unscheduleDrawable(Drawable who, Runnable what) { final Callback callback = getCallback(); if (callback != null) { callback.unscheduleDrawable(this, what); } } @Override public void draw(@NonNull Canvas canvas) { if (mDrawable != null) { mDrawable.draw(canvas); } } @Override public int getChangingConfigurations() { return super.getChangingConfigurations() | (mState != null ? mState.mChangingConfigurations : 0) | mDrawable.getChangingConfigurations(); } @Override public boolean getPadding(@NonNull Rect padding) { return mDrawable != null && mDrawable.getPadding(padding); } /** @hide */ @Override public Insets getOpticalInsets() { return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE; } @Override public void setHotspot(float x, float y) { if (mDrawable != null) { mDrawable.setHotspot(x, y); } } @Override public void setHotspotBounds(int left, int top, int right, int bottom) { if (mDrawable != null) { mDrawable.setHotspotBounds(left, top, right, bottom); } } @Override public void getHotspotBounds(@NonNull Rect outRect) { if (mDrawable != null) { mDrawable.getHotspotBounds(outRect); } else { outRect.set(getBounds()); } } @Override public boolean setVisible(boolean visible, boolean restart) { final boolean superChanged = super.setVisible(visible, restart); final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart); return superChanged | changed; } @Override public void setAlpha(int alpha) { if (mDrawable != null) { mDrawable.setAlpha(alpha); } } @Override public int getAlpha() { return mDrawable != null ? mDrawable.getAlpha() : 255; } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { if (mDrawable != null) { mDrawable.setColorFilter(colorFilter); } } @Override public void setTintList(@Nullable ColorStateList tint) { if (mDrawable != null) { mDrawable.setTintList(tint); } } @Override public void setTintMode(@Nullable PorterDuff.Mode tintMode) { if (mDrawable != null) { mDrawable.setTintMode(tintMode); } } @Override public boolean onLayoutDirectionChange(@View.ResolvedLayoutDir int layoutDirection) { return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection); } @Override public int getOpacity() { return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT; } @Override public boolean isStateful() { return mDrawable != null && mDrawable.isStateful(); } @Override protected boolean onStateChange(int[] state) { if (mDrawable != null) { final boolean changed = mDrawable.setState(state); if (changed) { onBoundsChange(getBounds()); } return changed; } return false; } @Override protected boolean onLevelChange(int level) { return mDrawable != null && mDrawable.setLevel(level); } @Override protected void onBoundsChange(@NonNull Rect bounds) { if (mDrawable != null) { mDrawable.setBounds(bounds); } } @Override public int getIntrinsicWidth() { return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1; } @Override public int getIntrinsicHeight() { return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1; } @Override public void getOutline(@NonNull Outline outline) { if (mDrawable != null) { mDrawable.getOutline(outline); } else { super.getOutline(outline); } } @Override @Nullable public ConstantState getConstantState() { if (mState != null && mState.canConstantState()) { mState.mChangingConfigurations = getChangingConfigurations(); return mState; } return null; } @Override @NonNull public Drawable mutate() { if (!mMutated && super.mutate() == this) { mState = mutateConstantState(); if (mDrawable != null) { mDrawable.mutate(); } if (mState != null) { mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null; } mMutated = true; } return this; } /** * Mutates the constant state and returns the new state. Responsible for * updating any local copy. *
* This method should never call the super implementation; it should always
* mutate and return its own constant state.
*
* @return the new state
*/
DrawableWrapperState mutateConstantState() {
return mState;
}
/**
* @hide Only used by the framework for pre-loading resources.
*/
public void clearMutated() {
super.clearMutated();
if (mDrawable != null) {
mDrawable.clearMutated();
}
mMutated = false;
}
/**
* Called during inflation to inflate the child element.
*/
void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs,
Resources.Theme theme) throws XmlPullParserException, IOException {
// Drawable specified on the root element takes precedence.
if (getDrawable() != null) {
return;
}
// Seek to the first child element.
Drawable dr = null;
int type;
final int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.START_TAG) {
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
break;
}
}
if (dr != null) {
setDrawable(dr);
}
}
abstract static class DrawableWrapperState extends Drawable.ConstantState {
int[] mThemeAttrs;
int mChangingConfigurations;
Drawable.ConstantState mDrawableState;
DrawableWrapperState(DrawableWrapperState orig) {
if (orig != null) {
mThemeAttrs = orig.mThemeAttrs;
mChangingConfigurations = orig.mChangingConfigurations;
mDrawableState = orig.mDrawableState;
}
}
@Override
public boolean canApplyTheme() {
return mThemeAttrs != null
|| (mDrawableState != null && mDrawableState.canApplyTheme())
|| super.canApplyTheme();
}
@Override
public int addAtlasableBitmaps(Collection