| /* |
| * 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. |
| * <p> |
| * 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<Bitmap> atlasList) { |
| final Drawable.ConstantState state = mDrawableState; |
| if (state != null) { |
| return state.addAtlasableBitmaps(atlasList); |
| } |
| return 0; |
| } |
| |
| @Override |
| public Drawable newDrawable() { |
| return newDrawable(null); |
| } |
| |
| @Override |
| public abstract Drawable newDrawable(Resources res); |
| |
| @Override |
| public int getChangingConfigurations() { |
| return mChangingConfigurations; |
| } |
| |
| public boolean canConstantState() { |
| return mDrawableState != null; |
| } |
| } |
| } |