/* * 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 javax.annotation.Nullable; 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; private final Rect mBounds = new Rect(); private int mLevel; private int[] mState; private boolean mIsVisible; /** * 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 */ @Nullable public Drawable getDrawable(int index) { return mLayers[index]; } /** Sets a new drawable at the specified index. */ public void setDrawable(int index, @Nullable Drawable drawable) { Preconditions.checkArgument(index >= 0); Preconditions.checkArgument(index < mLayers.length); if (drawable != mLayers[index]) { if (drawable != null && mIsMutated) { drawable = drawable.mutate(); } DrawableUtils.setCallbacks(mLayers[index], null, null); DrawableUtils.setCallbacks(drawable, null, null); DrawableUtils.setDrawableProperties(drawable, mDrawableProperties); if (drawable != null) { drawable.setBounds(mBounds); drawable.setLevel(mLevel); drawable.setState(mState); drawable.setVisible(mIsVisible, /* restart */ false); } DrawableUtils.setCallbacks(drawable, this, this); mIsStatefulCalculated = false; mLayers[index] = drawable; invalidateSelf(); } } @Override public int getIntrinsicWidth() { int width = -1; for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { width = Math.max(width, drawable.getIntrinsicWidth()); } } return width > 0 ? width : -1; } @Override public int getIntrinsicHeight() { int height = -1; for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { height = Math.max(height, drawable.getIntrinsicHeight()); } } return height > 0 ? height : -1; } @Override protected void onBoundsChange(Rect bounds) { mBounds.set(bounds); for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.setBounds(bounds); } } } @Override public boolean isStateful() { if (!mIsStatefulCalculated) { mIsStateful = false; for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; mIsStateful |= drawable != null && drawable.isStateful(); } mIsStatefulCalculated = true; } return mIsStateful; } @Override protected boolean onStateChange(int[] state) { mState = state; boolean stateChanged = false; for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null && drawable.setState(state)) { stateChanged = true; } } return stateChanged; } @Override protected boolean onLevelChange(int level) { mLevel = level; boolean levelChanged = false; for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null && drawable.setLevel(level)) { levelChanged = true; } } return levelChanged; } @Override public void draw(Canvas canvas) { for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.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++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.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++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.mutate(); } } mIsMutated = true; return this; } @Override public int getOpacity() { if (mLayers.length == 0) { return PixelFormat.TRANSPARENT; } int opacity = PixelFormat.OPAQUE; for (int i = 1; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { opacity = Drawable.resolveOpacity(opacity, drawable.getOpacity()); } } return opacity; } @Override public void setAlpha(int alpha) { mDrawableProperties.setAlpha(alpha); for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.setAlpha(alpha); } } } @Override public void setColorFilter(ColorFilter colorFilter) { mDrawableProperties.setColorFilter(colorFilter); for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.setColorFilter(colorFilter); } } } @Override public void setDither(boolean dither) { mDrawableProperties.setDither(dither); for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.setDither(dither); } } } @Override public void setFilterBitmap(boolean filterBitmap) { mDrawableProperties.setFilterBitmap(filterBitmap); for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.setFilterBitmap(filterBitmap); } } } @Override public boolean setVisible(boolean visible, boolean restart) { mIsVisible = visible; boolean changed = super.setVisible(visible, restart); for (int i = 0; i < mLayers.length; i++) { Drawable drawable = mLayers[i]; if (drawable != null) { drawable.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()); } } }