/*
* 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.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import com.facebook.common.internal.Preconditions;
/**
* A Drawable that contains an array of other Drawables (layers). These are drawn in array order,
* so the element with the largest index will be drawn on top.
*
* <p>Similar to android's LayerDrawable but it doesn't support adding/removing layers dynamically.
*/
public class ArrayDrawable extends Drawable
implements Drawable.Callback, TransformCallback, TransformAwareDrawable {
private TransformCallback mTransformCallback;
private final DrawableProperties mDrawableProperties = new DrawableProperties();
// layers
private final Drawable[] mLayers;
// temp rect to avoid allocations
private final Rect mTmpRect = new Rect();
// Whether the drawable is stateful or not
private boolean mIsStateful = false;
private boolean mIsStatefulCalculated = false;
private boolean mIsMutated = false;
/**
* Constructs a new layer drawable.
* @param layers the layers that this drawable displays
*/
public ArrayDrawable(Drawable[] layers) {
Preconditions.checkNotNull(layers);
mLayers = layers;
for (int i = 0; i < mLayers.length; i++) {
DrawableUtils.setCallbacks(mLayers[i], this, this);
}
}
/**
* Gets the number of layers.
* @return number of layers
*/
public int getNumberOfLayers() {
return mLayers.length;
}
/**
* Gets the drawable at the specified index.
* @param index index of drawable to get
* @return drawable at the specified index
*/
public Drawable getDrawable(int index) {
return mLayers[index];
}
/** Sets a new drawable at the specified index. */
public void setDrawable(int index, Drawable drawable) {
Preconditions.checkArgument(index >= 0);
Preconditions.checkArgument(index < mLayers.length);
if (drawable != mLayers[index]) {
if (mIsMutated) {
drawable = drawable.mutate();
}
DrawableUtils.setCallbacks(mLayers[index], null, null);
DrawableUtils.setCallbacks(drawable, null, null);
DrawableUtils.setDrawableProperties(drawable, mDrawableProperties);
DrawableUtils.copyProperties(drawable, mLayers[index]);
DrawableUtils.setCallbacks(drawable, this, this);
mIsStatefulCalculated = false;
mLayers[index] = drawable;
invalidateSelf();
}
}
@Override
public int getIntrinsicWidth() {
int width = 0;
for (int i = 0; i < mLayers.length; i++) {
width = Math.max(width, mLayers[i].getIntrinsicWidth());
}
return width;
}
@Override
public int getIntrinsicHeight() {
int height = 0;
for (int i = 0; i < mLayers.length; i++) {
height = Math.max(height, mLayers[i].getIntrinsicHeight());
}
return height;
}
@Override
protected void onBoundsChange(Rect bounds) {
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].setBounds(bounds);
}
}
@Override
public boolean isStateful() {
if (!mIsStatefulCalculated) {
mIsStateful = false;
for (int i = 0; i < mLayers.length; i++) {
mIsStateful |= mLayers[i].isStateful();
}
mIsStatefulCalculated = true;
}
return mIsStateful;
}
@Override
protected boolean onStateChange(int[] state) {
boolean stateChanged = false;
for (int i = 0; i < mLayers.length; i++) {
if (mLayers[i].setState(state)) {
stateChanged = true;
}
}
return stateChanged;
}
@Override
protected boolean onLevelChange(int level) {
boolean levelChanged = false;
for (int i = 0; i < mLayers.length; i++) {
if (mLayers[i].setLevel(level)) {
levelChanged = true;
}
}
return levelChanged;
}
@Override
public void draw(Canvas canvas) {
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].draw(canvas);
}
}
@Override
public boolean getPadding(Rect padding) {
padding.left = 0;
padding.top = 0;
padding.right = 0;
padding.bottom = 0;
final Rect rect = mTmpRect;
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].getPadding(rect);
padding.left = Math.max(padding.left, rect.left);
padding.top = Math.max(padding.top, rect.top);
padding.right = Math.max(padding.right, rect.right);
padding.bottom = Math.max(padding.bottom, rect.bottom);
}
return true;
}
@Override
public Drawable mutate() {
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].mutate();
}
mIsMutated = true;
return this;
}
@Override
public int getOpacity() {
if (mLayers.length == 0) {
return PixelFormat.TRANSPARENT;
}
int opacity = mLayers[0].getOpacity();
for (int i = 1; i < mLayers.length; i++) {
opacity = Drawable.resolveOpacity(opacity, mLayers[i].getOpacity());
}
return opacity;
}
@Override
public void setAlpha(int alpha) {
mDrawableProperties.setAlpha(alpha);
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].setAlpha(alpha);
}
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mDrawableProperties.setColorFilter(colorFilter);
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].setColorFilter(colorFilter);
}
}
@Override
public void setDither(boolean dither) {
mDrawableProperties.setDither(dither);
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].setDither(dither);
}
}
@Override
public void setFilterBitmap(boolean filterBitmap) {
mDrawableProperties.setFilterBitmap(filterBitmap);
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].setFilterBitmap(filterBitmap);
}
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
for (int i = 0; i < mLayers.length; i++) {
mLayers[i].setVisible(visible, restart);
}
return changed;
}
/**
* 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
*/
@Override
public void getTransform(Matrix transform) {
if (mTransformCallback != null) {
mTransformCallback.getTransform(transform);
} else {
transform.reset();
}
}
@Override
public void getRootBounds(RectF bounds) {
if (mTransformCallback != null) {
mTransformCallback.getRootBounds(bounds);
} else {
bounds.set(getBounds());
}
}
}