/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.drawee.drawable;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
/**
* A forwarding drawable class - the goal is to forward (delegate) drawable functionality to an
* inner drawable instance. ForwardingDrawable intercepts the public (and protected) methods of
* {@link Drawable}, maintains local state if needed.
* <p>
* Design note: It would have been very helpful to re-use Android library classes
* like DrawableContainer, LevelListDrawable etc. DrawableContainer is not directly subclassable,
* and the others don't allow changing the member drawables.
*/
public abstract class ForwardingDrawable extends Drawable
implements Drawable.Callback, TransformCallback, TransformAwareDrawable {
/** The current drawable to be drawn by this drawable when drawing is needed */
private Drawable mCurrentDelegate;
private final DrawableProperties mDrawableProperties = new DrawableProperties();
protected TransformCallback mTransformCallback;
/**
* Matrix used to store temporary transform. Drawables should be accessed on UI thread only, and
* this matrix is used only as a temporary variable so it's safe to be static.
*/
private static final Matrix sTempTransform = new Matrix();
/**
* Constructs a new forwarding drawable.
* @param drawable drawable that this forwarding drawable will forward to
*/
public ForwardingDrawable(Drawable drawable) {
mCurrentDelegate = drawable;
DrawableUtils.setCallbacks(mCurrentDelegate, this, this);
}
/**
* Sets a new drawable to be the delegate, and returns the old one (or null).
*
* <p>This method will cause the drawable to be invalidated.
* @param newDelegate
* @return the previous delegate
*/
public Drawable setCurrent(Drawable newDelegate) {
Drawable previousDelegate = setCurrentWithoutInvalidate(newDelegate);
invalidateSelf();
return previousDelegate;
}
/**
* As {@code setCurrent}, but without invalidating a drawable. Subclasses are responsible to call
* {@code invalidateSelf} on their own.
* @param newDelegate
* @return the previous delegate
*/
protected Drawable setCurrentWithoutInvalidate(Drawable newDelegate) {
Drawable previousDelegate = mCurrentDelegate;
DrawableUtils.setCallbacks(previousDelegate, null, null);
DrawableUtils.setCallbacks(newDelegate, null, null);
DrawableUtils.setDrawableProperties(newDelegate, mDrawableProperties);
DrawableUtils.copyProperties(newDelegate, previousDelegate);
DrawableUtils.setCallbacks(newDelegate, this, this);
mCurrentDelegate = newDelegate;
return previousDelegate;
}
@Override
public int getOpacity() {
return mCurrentDelegate.getOpacity();
}
@Override
public void setAlpha(int alpha) {
mDrawableProperties.setAlpha(alpha);
mCurrentDelegate.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mDrawableProperties.setColorFilter(colorFilter);
mCurrentDelegate.setColorFilter(colorFilter);
}
@Override
public void setDither(boolean dither) {
mDrawableProperties.setDither(dither);
mCurrentDelegate.setDither(dither);
}
@Override
public void setFilterBitmap(boolean filterBitmap) {
mDrawableProperties.setFilterBitmap(filterBitmap);
mCurrentDelegate.setFilterBitmap(filterBitmap);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
return mCurrentDelegate.setVisible(visible, restart);
}
@Override
protected void onBoundsChange(Rect bounds) {
mCurrentDelegate.setBounds(bounds);
}
@Override
public boolean isStateful() {
return mCurrentDelegate.isStateful();
}
@Override
protected boolean onStateChange(int[] state) {
return mCurrentDelegate.setState(state);
}
@Override
protected boolean onLevelChange(int level) {
return mCurrentDelegate.setLevel(level);
}
@Override
public void draw(Canvas canvas) {
mCurrentDelegate.draw(canvas);
}
@Override
public int getIntrinsicWidth() {
return mCurrentDelegate.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mCurrentDelegate.getIntrinsicHeight();
}
@Override
public boolean getPadding(Rect padding) {
return mCurrentDelegate.getPadding(padding);
}
@Override
public Drawable mutate() {
mCurrentDelegate.mutate();
return this;
}
@Override
public Drawable getCurrent() {
return mCurrentDelegate;
}
/**
* Drawable.Callback methods
*/
@Override
public void invalidateDrawable(Drawable who) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
unscheduleSelf(what);
}
/**
* TransformationCallbackSetter method
*/
@Override
public void setTransformCallback(TransformCallback transformCallback) {
mTransformCallback = transformCallback;
}
/**
* TransformationCallback methods
*/
protected void getParentTransform(Matrix transform) {
if (mTransformCallback != null) {
mTransformCallback.getTransform(transform);
} else {
transform.reset();
}
}
@Override
public void getTransform(Matrix transform) {
getParentTransform(transform);
}
@Override
public void getRootBounds(RectF bounds) {
if (mTransformCallback != null) {
mTransformCallback.getRootBounds(bounds);
} else {
bounds.set(getBounds());
}
}
/**
* Gets the transformed bounds of this drawable.
* Note: bounds are not cropped (otherwise they would likely be the same as drawable's bounds).
* @param outBounds rect to fill with bounds
*/
public void getTransformedBounds(RectF outBounds) {
getParentTransform(sTempTransform);
// IMPORTANT: {@code getBounds} should be called after {@code getParentTransform},
// because the parent may have to change our bounds.
outBounds.set(getBounds());
sTempTransform.mapRect(outBounds);
}
}