444 lines
12 KiB
Java
444 lines
12 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|