本片水文
文章目录
- 资料
- Drawable关系
- BitmapDrawable
- ShapeDrawable
- LayerDrawable
- StateListDrawable
- LevelListDrawable
- TransitionDrawable
- InsetDrawable
- ScaleDrawable
- ClipDrawable
- 源码
- Drawable关系
- BitmapDrawable
- ShapeDrawable
- LayerDrawable
- StateListDrawable
- LevelListDrawable
- TransitionDrawable
- InsetDrawable
- ScaleDrawable
资料
Android开发艺术探索
Drawable关系
BitmapDrawable
ShapeDrawable
LayerDrawable
StateListDrawable
LevelListDrawable
TransitionDrawable
InsetDrawable
ScaleDrawable
ClipDrawable
源码
贴上来,就不用本地去翻了
Drawable关系
/*
* Copyright (C) 2006 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 android.annotation.AttrRes;
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Xfermode;
import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Arrays;
/**
* A Drawable is a general abstraction for "something that can be drawn." Most
* often you will deal with Drawable as the type of resource retrieved for
* drawing things to the screen; the Drawable class provides a generic API for
* dealing with an underlying visual resource that may take a variety of forms.
* Unlike a {@link android.view.View}, a Drawable does not have any facility to
* receive events or otherwise interact with the user.
*
* <p>In addition to simple drawing, Drawable provides a number of generic
* mechanisms for its client to interact with what is being drawn:
*
* <ul>
* <li> The {@link #setBounds} method <var>must</var> be called to tell the
* Drawable where it is drawn and how large it should be. All Drawables
* should respect the requested size, often simply by scaling their
* imagery. A client can find the preferred size for some Drawables with
* the {@link #getIntrinsicHeight} and {@link #getIntrinsicWidth} methods.
*
* <li> The {@link #getPadding} method can return from some Drawables
* information about how to frame content that is placed inside of them.
* For example, a Drawable that is intended to be the frame for a button
* widget would need to return padding that correctly places the label
* inside of itself.
*
* <li> The {@link #setState} method allows the client to tell the Drawable
* in which state it is to be drawn, such as "focused", "selected", etc.
* Some drawables may modify their imagery based on the selected state.
*
* <li> The {@link #setLevel} method allows the client to supply a single
* continuous controller that can modify the Drawable is displayed, such as
* a battery level or progress level. Some drawables may modify their
* imagery based on the current level.
*
* <li> A Drawable can perform animations by calling back to its client
* through the {@link Callback} interface. All clients should support this
* interface (via {@link #setCallback}) so that animations will work. A
* simple way to do this is through the system facilities such as
* {@link android.view.View#setBackground(Drawable)} and
* {@link android.widget.ImageView}.
* </ul>
*
* Though usually not visible to the application, Drawables may take a variety
* of forms:
*
* <ul>
* <li> <b>Bitmap</b>: the simplest Drawable, a PNG or JPEG image.
* <li> <b>Nine Patch</b>: an extension to the PNG format allows it to
* specify information about how to stretch it and place things inside of
* it.
* <li><b>Vector</b>: a drawable defined in an XML file as a set of points,
* lines, and curves along with its associated color information. This type
* of drawable can be scaled without loss of display quality.
* <li> <b>Shape</b>: contains simple drawing commands instead of a raw
* bitmap, allowing it to resize better in some cases.
* <li> <b>Layers</b>: a compound drawable, which draws multiple underlying
* drawables on top of each other.
* <li> <b>States</b>: a compound drawable that selects one of a set of
* drawables based on its state.
* <li> <b>Levels</b>: a compound drawable that selects one of a set of
* drawables based on its level.
* <li> <b>Scale</b>: a compound drawable with a single child drawable,
* whose overall size is modified based on the current level.
* </ul>
*
* <a name="Custom"></a>
* <h3>Custom drawables</h3>
*
* <p>
* All versions of Android allow the Drawable class to be extended and used at
* run time in place of framework-provided drawable classes. Starting in
* {@link android.os.Build.VERSION_CODES#N API 24}, custom drawables classes
* may also be used in XML.
* <p>
* <strong>Note:</strong> Custom drawable classes are only accessible from
* within your application package. Other applications will not be able to load
* them.
* <p>
* At a minimum, custom drawable classes must implement the abstract methods on
* Drawable and should override the {@link Drawable#draw(Canvas)} method to
* draw content.
* <p>
* Custom drawables classes may be used in XML in multiple ways:
* <ul>
* <li>
* Using the fully-qualified class name as the XML element name. For
* this method, the custom drawable class must be a public top-level
* class.
* <pre>
* <com.myapp.MyCustomDrawable xmlns:android="http://schemas.android.com/apk/res/android"
* android:color="#ffff0000" />
* </pre>
* </li>
* <li>
* Using <em>drawable</em> as the XML element name and specifying the
* fully-qualified class name from the <em>class</em> attribute. This
* method may be used for both public top-level classes and public
* static inner classes.
* <pre>
* <drawable xmlns:android="http://schemas.android.com/apk/res/android"
* class="com.myapp.MyTopLevelClass$InnerCustomDrawable"
* android:color="#ffff0000" />
* </pre>
* </li>
* </ul>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about how to use drawables, read the
* <a href="{@docRoot}guide/topics/graphics/2d-graphics.html">Canvas and Drawables</a> developer
* guide. For information and examples of creating drawable resources (XML or bitmap files that
* can be loaded in code), read the
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>
* document.</p></div>
*/
public abstract class Drawable {
private static final Rect ZERO_BOUNDS_RECT = new Rect();
static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_IN;
private int[] mStateSet = StateSet.WILD_CARD;
private int mLevel = 0;
private @Config int mChangingConfigurations = 0;
private Rect mBounds = ZERO_BOUNDS_RECT; // lazily becomes a new Rect()
@UnsupportedAppUsage
private WeakReference<Callback> mCallback = null;
private boolean mVisible = true;
private int mLayoutDirection;
/**
* The source density to use when looking up resources using
* {@link Resources#getDrawableForDensity(int, int, Theme)}. A value of 0 means there is no
* override and the system density will be used.
*
* NOTE(adamlesinski): This is transient state used to get around the public API that does not
* account for source density overrides. Custom drawables implemented by developers do not need
* to be aware of the source density override, as it is only used by Launcher to load higher
* resolution icons from external Resources packages, which do not execute custom code.
* This is all to support the {@link Resources#getDrawableForDensity(int, int, Theme)} API.
*
* @hide
*/
@UnsupportedAppUsage
protected int mSrcDensityOverride = 0;
/**
* Flag used to break the recursive loop between setTintBlendMode(PorterDuff.Mode) and
* setTintBlendMode(BlendMode) as each default implementation invokes the other in order to
* support new use cases that utilize the new blending modes as well as support the legacy
* use cases. This flag tracks that {@link #setTintBlendMode(BlendMode)} is only invoked once
* per invocation.
*/
private boolean mSetBlendModeInvoked = false;
/**
* Flag used to break the recursive loop between setTintBlendMode(PorterDuff.Mode) and
* setTintBlendMode(BlendMode) as each default implementation invokes the other in order to
* support new use cases that utilize the new blending modes as well as support the legacy
* use cases. This flag tracks that {@link #setTintMode(Mode)} is only invoked once
* per invocation;
*/
private boolean mSetTintModeInvoked = false;
/**
* 比如:在View中被setBackround调用
* Draw in its bounds (set via setBounds) respecting optional effects such
* as alpha (set via setAlpha) and color filter (set via setColorFilter).
*
* @param canvas The canvas to draw into
*/
public abstract void draw(@NonNull Canvas canvas);
/**
* Specify a bounding rectangle for the Drawable. This is where the drawable
* will draw when its draw() method is called.
*/
public void setBounds(int left, int top, int right, int bottom) {
Rect oldBounds = mBounds;
if (oldBounds == ZERO_BOUNDS_RECT) {
oldBounds = mBounds = new Rect();
}
if (oldBounds.left != left || oldBounds.top != top ||
oldBounds.right != right || oldBounds.bottom != bottom) {
if (!oldBounds.isEmpty()) {
// first invalidate the previous bounds
invalidateSelf();
}
mBounds.set(left, top, right, bottom);
onBoundsChange(mBounds);
}
}
/**
* Specify a bounding rectangle for the Drawable. This is where the drawable
* will draw when its draw() method is called.
*/
public void setBounds(@NonNull Rect bounds) {
setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
/**
* Return a copy of the drawable's bounds in the specified Rect (allocated
* by the caller). The bounds specify where this will draw when its draw()
* method is called.
*
* @param bounds Rect to receive the drawable's bounds (allocated by the
* caller).
*/
public final void copyBounds(@NonNull Rect bounds) {
bounds.set(mBounds);
}
/**
* Return a copy of the drawable's bounds in a new Rect. This returns the
* same values as getBounds(), but the returned object is guaranteed to not
* be changed later by the drawable (i.e. it retains no reference to this
* rect). If the caller already has a Rect allocated, call copyBounds(rect).
*
* @return A copy of the drawable's bounds
*/
@NonNull
public final Rect copyBounds() {
return new Rect(mBounds);
}
/**
* Return the drawable's bounds Rect. Note: for efficiency, the returned
* object may be the same object stored in the drawable (though this is not
* guaranteed), so if a persistent copy of the bounds is needed, call
* copyBounds(rect) instead.
* You should also not change the object returned by this method as it may
* be the same object stored in the drawable.
*
* @return The bounds of the drawable (which may change later, so caller
* beware). DO NOT ALTER the returned object as it may change the
* stored bounds of this drawable.
*
* @see #copyBounds()
* @see #copyBounds(android.graphics.Rect)
*/
@NonNull
public final Rect getBounds() {
if (mBounds == ZERO_BOUNDS_RECT) {
mBounds = new Rect();
}
return mBounds;
}
/**
* Return the drawable's dirty bounds Rect. Note: for efficiency, the
* returned object may be the same object stored in the drawable (though
* this is not guaranteed).
* <p>
* By default, this returns the full drawable bounds. Custom drawables may
* override this method to perform more precise invalidation.
*
* @return The dirty bounds of this drawable
*/
@NonNull
public Rect getDirtyBounds() {
return getBounds();
}
/**
* Set a mask of the configuration parameters for which this drawable
* may change, requiring that it be re-created.
*
* @param configs A mask of the changing configuration parameters, as
* defined by {@link android.content.pm.ActivityInfo}.
*
* @see android.content.pm.ActivityInfo
*/
public void setChangingConfigurations(@Config int configs) {
mChangingConfigurations = configs;
}
/**
* Return a mask of the configuration parameters for which this drawable
* may change, requiring that it be re-created. The default implementation
* returns whatever was provided through
* {@link #setChangingConfigurations(int)} or 0 by default. Subclasses
* may extend this to or in the changing configurations of any other
* drawables they hold.
*
* @return Returns a mask of the changing configuration parameters, as
* defined by {@link android.content.pm.ActivityInfo}.
*
* @see android.content.pm.ActivityInfo
*/
public @Config int getChangingConfigurations() {
return mChangingConfigurations;
}
/**
* Set to true to have the drawable dither its colors when drawn to a
* device with fewer than 8-bits per color component.
*
* @see android.graphics.Paint#setDither(boolean);
* @deprecated This property is ignored.
*/
@Deprecated
public void setDither(boolean dither) {}
/**
* Set to true to have the drawable filter its bitmaps with bilinear
* sampling when they are scaled or rotated.
*
* <p>This can improve appearance when bitmaps are rotated. If the drawable
* does not use bitmaps, this call is ignored.</p>
*
* @see #isFilterBitmap()
* @see android.graphics.Paint#setFilterBitmap(boolean);
*/
public void setFilterBitmap(boolean filter) {}
/**
* @return whether this drawable filters its bitmaps
* @see #setFilterBitmap(boolean)
*/
public boolean isFilterBitmap() {
return false;
}
/**
* Implement this interface if you want to create an animated drawable that
* extends {@link android.graphics.drawable.Drawable Drawable}.
* Upon retrieving a drawable, use
* {@link Drawable#setCallback(android.graphics.drawable.Drawable.Callback)}
* to supply your implementation of the interface to the drawable; it uses
* this interface to schedule and execute animation changes.
*/
public interface Callback {
/**
* Called when the drawable needs to be redrawn. A view at this point
* should invalidate itself (or at least the part of itself where the
* drawable appears).
*
* @param who The drawable that is requesting the update.
*/
void invalidateDrawable(@NonNull Drawable who);
/**
* A Drawable can call this to schedule the next frame of its
* animation. An implementation can generally simply call
* {@link android.os.Handler#postAtTime(Runnable, Object, long)} with
* the parameters <var>(what, who, when)</var> to perform the
* scheduling.
*
* @param who The drawable being scheduled.
* @param what The action to execute.
* @param when The time (in milliseconds) to run. The timebase is
* {@link android.os.SystemClock#uptimeMillis}
*/
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
/**
* A Drawable can call this to unschedule an action previously
* scheduled with {@link #scheduleDrawable}. An implementation can
* generally simply call
* {@link android.os.Handler#removeCallbacks(Runnable, Object)} with
* the parameters <var>(what, who)</var> to unschedule the drawable.
*
* @param who The drawable being unscheduled.
* @param what The action being unscheduled.
*/
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
}
/**
* Bind a {@link Callback} object to this Drawable. Required for clients
* that want to support animated drawables.
*
* @param cb The client's Callback implementation.
*
* @see #getCallback()
*/
public final void setCallback(@Nullable Callback cb) {
mCallback = cb != null ? new WeakReference<>(cb) : null;
}
/**
* Return the current {@link Callback} implementation attached to this
* Drawable.
*
* @return A {@link Callback} instance or null if no callback was set.
*
* @see #setCallback(android.graphics.drawable.Drawable.Callback)
*/
@Nullable
public Callback getCallback() {
return mCallback != null ? mCallback.get() : null;
}
/**
* Use the current {@link Callback} implementation to have this Drawable
* redrawn. Does nothing if there is no Callback attached to the
* Drawable.
*
* @see Callback#invalidateDrawable
* @see #getCallback()
* @see #setCallback(android.graphics.drawable.Drawable.Callback)
*/
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
/**
* Use the current {@link Callback} implementation to have this Drawable
* scheduled. Does nothing if there is no Callback attached to the
* Drawable.
*
* @param what The action being scheduled.
* @param when The time (in milliseconds) to run.
*
* @see Callback#scheduleDrawable
*/
public void scheduleSelf(@NonNull Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
/**
* Use the current {@link Callback} implementation to have this Drawable
* unscheduled. Does nothing if there is no Callback attached to the
* Drawable.
*
* @param what The runnable that you no longer want called.
*
* @see Callback#unscheduleDrawable
*/
public void unscheduleSelf(@NonNull Runnable what) {
final Callback callback = getCallback();
if (callback != null) {
callback.unscheduleDrawable(this, what);
}
}
/**
* Returns the resolved layout direction for this Drawable.
*
* @return One of {@link android.view.View#LAYOUT_DIRECTION_LTR},
* {@link android.view.View#LAYOUT_DIRECTION_RTL}
* @see #setLayoutDirection(int)
*/
public @View.ResolvedLayoutDir int getLayoutDirection() {
return mLayoutDirection;
}
/**
* Set the layout direction for this drawable. Should be a resolved
* layout direction, as the Drawable has no capacity to do the resolution on
* its own.
*
* @param layoutDirection the resolved layout direction for the drawable,
* either {@link android.view.View#LAYOUT_DIRECTION_LTR}
* or {@link android.view.View#LAYOUT_DIRECTION_RTL}
* @return {@code true} if the layout direction change has caused the
* appearance of the drawable to change such that it needs to be
* re-drawn, {@code false} otherwise
* @see #getLayoutDirection()
*/
public final boolean setLayoutDirection(@View.ResolvedLayoutDir int layoutDirection) {
if (mLayoutDirection != layoutDirection) {
mLayoutDirection = layoutDirection;
return onLayoutDirectionChanged(layoutDirection);
}
return false;
}
/**
* Called when the drawable's resolved layout direction changes.
*
* @param layoutDirection the new resolved layout direction
* @return {@code true} if the layout direction change has caused the
* appearance of the drawable to change such that it needs to be
* re-drawn, {@code false} otherwise
* @see #setLayoutDirection(int)
*/
public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
return false;
}
/**
* Specify an alpha value for the drawable. 0 means fully transparent, and
* 255 means fully opaque.
*/
public abstract void setAlpha(@IntRange(from=0,to=255) int alpha);
/**
* Gets the current alpha value for the drawable. 0 means fully transparent,
* 255 means fully opaque. This method is implemented by
* Drawable subclasses and the value returned is specific to how that class treats alpha.
* The default return value is 255 if the class does not override this method to return a value
* specific to its use of alpha.
*/
@IntRange(from=0,to=255)
public int getAlpha() {
return 0xFF;
}
/**
* @hide
*
* Internal-only method for setting xfermode on certain supported drawables.
*
* Should not be made public since the layers and drawing area with which
* Drawables draw is private implementation detail, and not something apps
* should rely upon.
*/
public void setXfermode(@Nullable Xfermode mode) {
// Base implementation drops it on the floor for compatibility. Whee!
}
/**
* Specify an optional color filter for the drawable.
* <p>
* If a Drawable has a ColorFilter, each output pixel of the Drawable's
* drawing contents will be modified by the color filter before it is
* blended onto the render target of a Canvas.
* </p>
* <p>
* Pass {@code null} to remove any existing color filter.
* </p>
* <p class="note"><strong>Note:</strong> Setting a non-{@code null} color
* filter disables {@link #setTintList(ColorStateList) tint}.
* </p>
*
* @param colorFilter The color filter to apply, or {@code null} to remove the
* existing color filter
*/
public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
/**
* Specify a color and Porter-Duff mode to be the color filter for this
* drawable.
* <p>
* Convenience for {@link #setColorFilter(ColorFilter)} which constructs a
* {@link PorterDuffColorFilter}.
* </p>
* <p class="note"><strong>Note:</strong> Setting a color filter disables
* {@link #setTintList(ColorStateList) tint}.
* </p>
*
* @see #setColorFilter(ColorFilter)
* @deprecated use {@link #setColorFilter(ColorFilter)} with an instance
* of {@link android.graphics.BlendModeColorFilter}
*/
@Deprecated
public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
if (getColorFilter() instanceof PorterDuffColorFilter) {
PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
if (existing.getColor() == color && existing.getMode() == mode) {
return;
}
}
setColorFilter(new PorterDuffColorFilter(color, mode));
}
/**
* Specifies tint color for this drawable.
* <p>
* A Drawable's drawing content will be blended together with its tint
* before it is drawn to the screen. This functions similarly to
* {@link #setColorFilter(int, PorterDuff.Mode)}.
* </p>
* <p>
* To clear the tint, pass {@code null} to
* {@link #setTintList(ColorStateList)}.
* </p>
* <p class="note"><strong>Note:</strong> Setting a color filter via
* {@link #setColorFilter(ColorFilter)} or
* {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
* </p>
*
* @param tintColor Color to use for tinting this drawable
* @see #setTintList(ColorStateList)
* @see #setTintMode(PorterDuff.Mode)
* @see #setTintBlendMode(BlendMode)
*/
public void setTint(@ColorInt int tintColor) {
setTintList(ColorStateList.valueOf(tintColor));
}
/**
* Specifies tint color for this drawable as a color state list.
* <p>
* A Drawable's drawing content will be blended together with its tint
* before it is drawn to the screen. This functions similarly to
* {@link #setColorFilter(int, PorterDuff.Mode)}.
* </p>
* <p class="note"><strong>Note:</strong> Setting a color filter via
* {@link #setColorFilter(ColorFilter)} or
* {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
* </p>
*
* @param tint Color state list to use for tinting this drawable, or
* {@code null} to clear the tint
* @see #setTint(int)
* @see #setTintMode(PorterDuff.Mode)
* @see #setTintBlendMode(BlendMode)
*/
public void setTintList(@Nullable ColorStateList tint) {}
/**
* Specifies a tint blending mode for this drawable.
* <p>
* Defines how this drawable's tint color should be blended into the drawable
* before it is drawn to screen. Default tint mode is {@link PorterDuff.Mode#SRC_IN}.
* </p>
* <p class="note"><strong>Note:</strong> Setting a color filter via
* {@link #setColorFilter(ColorFilter)} or
* {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
* </p>
*
* @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets
* the default Porter-Diff blending mode value
* of {@link PorterDuff.Mode#SRC_IN}
* @see #setTint(int)
* @see #setTintList(ColorStateList)
*/
public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
if (!mSetTintModeInvoked) {
mSetTintModeInvoked = true;
BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null;
setTintBlendMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE);
mSetTintModeInvoked = false;
}
}
/**
* Specifies a tint blending mode for this drawable.
* <p>
* Defines how this drawable's tint color should be blended into the drawable
* before it is drawn to screen. Default tint mode is {@link BlendMode#SRC_IN}.
* </p>
* <p class="note"><strong>Note:</strong> Setting a color filter via
* {@link #setColorFilter(ColorFilter)}
* </p>
*
* @param blendMode BlendMode to apply to the drawable, a value of null sets the default
* blend mode value of {@link BlendMode#SRC_IN}
* @see #setTint(int)
* @see #setTintList(ColorStateList)
*/
public void setTintBlendMode(@Nullable BlendMode blendMode) {
if (!mSetBlendModeInvoked) {
mSetBlendModeInvoked = true;
PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode);
setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE);
mSetBlendModeInvoked = false;
}
}
/**
* Returns the current color filter, or {@code null} if none set.
*
* @return the current color filter, or {@code null} if none set
*/
public @Nullable ColorFilter getColorFilter() {
return null;
}
/**
* Removes the color filter for this drawable.
*/
public void clearColorFilter() {
setColorFilter(null);
}
/**
* Specifies the hotspot's location within the drawable.
*
* @param x The X coordinate of the center of the hotspot
* @param y The Y coordinate of the center of the hotspot
*/
public void setHotspot(float x, float y) {}
/**
* Sets the bounds to which the hotspot is constrained, if they should be
* different from the drawable bounds.
*
* @param left position in pixels of the left bound
* @param top position in pixels of the top bound
* @param right position in pixels of the right bound
* @param bottom position in pixels of the bottom bound
* @see #getHotspotBounds(android.graphics.Rect)
*/
public void setHotspotBounds(int left, int top, int right, int bottom) {}
/**
* Populates {@code outRect} with the hotspot bounds.
*
* @param outRect the rect to populate with the hotspot bounds
* @see #setHotspotBounds(int, int, int, int)
*/
public void getHotspotBounds(@NonNull Rect outRect) {
outRect.set(getBounds());
}
/**
* Whether this drawable requests projection. Indicates that the
* {@link android.graphics.RenderNode} this Drawable will draw into should be drawn immediately
* after the closest ancestor RenderNode containing a projection receiver.
*
* @see android.graphics.RenderNode#setProjectBackwards(boolean)
*/
public boolean isProjected() {
return false;
}
/**
* Indicates whether this drawable will change its appearance based on
* state. Clients can use this to determine whether it is necessary to
* calculate their state and call setState.
*
* @return True if this drawable changes its appearance based on state,
* false otherwise.
* @see #setState(int[])
*/
public boolean isStateful() {
return false;
}
/**
* Indicates whether this drawable has at least one state spec explicitly
* specifying {@link android.R.attr#state_focused}.
*
* <p>Note: A View uses a {@link Drawable} instance as its background and it
* changes its appearance based on a state. On keyboard devices, it should
* specify its {@link android.R.attr#state_focused} to make sure the user
* knows which view is holding the focus.</p>
*
* @return {@code true} if {@link android.R.attr#state_focused} is specified
* for this drawable.
*/
public boolean hasFocusStateSpecified() {
return false;
}
/**
* Specify a set of states for the drawable. These are use-case specific,
* so see the relevant documentation. As an example, the background for
* widgets like Button understand the following states:
* [{@link android.R.attr#state_focused},
* {@link android.R.attr#state_pressed}].
*
* <p>If the new state you are supplying causes the appearance of the
* Drawable to change, then it is responsible for calling
* {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
* true will be returned from this function.
*
* <p>Note: The Drawable holds a reference on to <var>stateSet</var>
* until a new state array is given to it, so you must not modify this
* array during that time.</p>
*
* @param stateSet The new set of states to be displayed.
*
* @return Returns true if this change in state has caused the appearance
* of the Drawable to change (hence requiring an invalidate), otherwise
* returns false.
*/
public boolean setState(@NonNull final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
/**
* Describes the current state, as a union of primitve states, such as
* {@link android.R.attr#state_focused},
* {@link android.R.attr#state_selected}, etc.
* Some drawables may modify their imagery based on the selected state.
* @return An array of resource Ids describing the current state.
*/
public @NonNull int[] getState() {
return mStateSet;
}
/**
* If this Drawable does transition animations between states, ask that
* it immediately jump to the current state and skip any active animations.
*/
public void jumpToCurrentState() {
}
/**
* @return The current drawable that will be used by this drawable. For simple drawables, this
* is just the drawable itself. For drawables that change state like
* {@link StateListDrawable} and {@link LevelListDrawable} this will be the child drawable
* currently in use.
*/
public @NonNull Drawable getCurrent() {
return this;
}
/**
* Specify the level for the drawable. This allows a drawable to vary its
* imagery based on a continuous controller, for example to show progress
* or volume level.
*
* <p>If the new level you are supplying causes the appearance of the
* Drawable to change, then it is responsible for calling
* {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
* true will be returned from this function.
*
* @param level The new level, from 0 (minimum) to 10000 (maximum).
*
* @return Returns true if this change in level has caused the appearance
* of the Drawable to change (hence requiring an invalidate), otherwise
* returns false.
*/
public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
if (mLevel != level) {
mLevel = level;
return onLevelChange(level);
}
return false;
}
/**
* Retrieve the current level.
*
* @return int Current level, from 0 (minimum) to 10000 (maximum).
*/
public final @IntRange(from=0,to=10000) int getLevel() {
return mLevel;
}
/**
* Set whether this Drawable is visible. This generally does not impact
* the Drawable's behavior, but is a hint that can be used by some
* Drawables, for example, to decide whether run animations.
*
* @param visible Set to true if visible, false if not.
* @param restart You can supply true here to force the drawable to behave
* as if it has just become visible, even if it had last
* been set visible. Used for example to force animations
* to restart.
*
* @return boolean Returns true if the new visibility is different than
* its previous state.
*/
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = mVisible != visible;
if (changed) {
mVisible = visible;
invalidateSelf();
}
return changed;
}
public final boolean isVisible() {
return mVisible;
}
/**
* Set whether this Drawable is automatically mirrored when its layout direction is RTL
* (right-to left). See {@link android.util.LayoutDirection}.
*
* @param mirrored Set to true if the Drawable should be mirrored, false if not.
*/
public void setAutoMirrored(boolean mirrored) {
}
/**
* Tells if this Drawable will be automatically mirrored when its layout direction is RTL
* right-to-left. See {@link android.util.LayoutDirection}.
*
* @return boolean Returns true if this Drawable will be automatically mirrored.
*/
public boolean isAutoMirrored() {
return false;
}
/**
* Applies the specified theme to this Drawable and its children.
*
* @param t the theme to apply
*/
public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) {
}
public boolean canApplyTheme() {
return false;
}
/**
* Return the opacity/transparency of this Drawable. The returned value is
* one of the abstract format constants in
* {@link android.graphics.PixelFormat}:
* {@link android.graphics.PixelFormat#UNKNOWN},
* {@link android.graphics.PixelFormat#TRANSLUCENT},
* {@link android.graphics.PixelFormat#TRANSPARENT}, or
* {@link android.graphics.PixelFormat#OPAQUE}.
*
* <p>An OPAQUE drawable is one that draws all all content within its bounds, completely
* covering anything behind the drawable. A TRANSPARENT drawable is one that draws nothing
* within its bounds, allowing everything behind it to show through. A TRANSLUCENT drawable
* is a drawable in any other state, where the drawable will draw some, but not all,
* of the content within its bounds and at least some content behind the drawable will
* be visible. If the visibility of the drawable's contents cannot be determined, the
* safest/best return value is TRANSLUCENT.
*
* <p>Generally a Drawable should be as conservative as possible with the
* value it returns. For example, if it contains multiple child drawables
* and only shows one of them at a time, if only one of the children is
* TRANSLUCENT and the others are OPAQUE then TRANSLUCENT should be
* returned. You can use the method {@link #resolveOpacity} to perform a
* standard reduction of two opacities to the appropriate single output.
*
* <p>Note that the returned value does not necessarily take into account a
* custom alpha or color filter that has been applied by the client through
* the {@link #setAlpha} or {@link #setColorFilter} methods. Some subclasses,
* such as {@link BitmapDrawable}, {@link ColorDrawable}, and {@link GradientDrawable},
* do account for the value of {@link #setAlpha}, but the general behavior is dependent
* upon the implementation of the subclass.
*
* @deprecated This method is no longer used in graphics optimizations
*
* @return int The opacity class of the Drawable.
*
* @see android.graphics.PixelFormat
*/
@Deprecated public abstract @PixelFormat.Opacity int getOpacity();
/**
* Return the appropriate opacity value for two source opacities. If
* either is UNKNOWN, that is returned; else, if either is TRANSLUCENT,
* that is returned; else, if either is TRANSPARENT, that is returned;
* else, OPAQUE is returned.
*
* <p>This is to help in implementing {@link #getOpacity}.
*
* @param op1 One opacity value.
* @param op2 Another opacity value.
*
* @return int The combined opacity value.
*
* @see #getOpacity
*/
public static @PixelFormat.Opacity int resolveOpacity(@PixelFormat.Opacity int op1,
@PixelFormat.Opacity int op2) {
if (op1 == op2) {
return op1;
}
if (op1 == PixelFormat.UNKNOWN || op2 == PixelFormat.UNKNOWN) {
return PixelFormat.UNKNOWN;
}
if (op1 == PixelFormat.TRANSLUCENT || op2 == PixelFormat.TRANSLUCENT) {
return PixelFormat.TRANSLUCENT;
}
if (op1 == PixelFormat.TRANSPARENT || op2 == PixelFormat.TRANSPARENT) {
return PixelFormat.TRANSPARENT;
}
return PixelFormat.OPAQUE;
}
/**
* Returns a Region representing the part of the Drawable that is completely
* transparent. This can be used to perform drawing operations, identifying
* which parts of the target will not change when rendering the Drawable.
* The default implementation returns null, indicating no transparent
* region; subclasses can optionally override this to return an actual
* Region if they want to supply this optimization information, but it is
* not required that they do so.
*
* @return Returns null if the Drawables has no transparent region to
* report, else a Region holding the parts of the Drawable's bounds that
* are transparent.
*/
public @Nullable Region getTransparentRegion() {
return null;
}
/**
* Override this in your subclass to change appearance if you recognize the
* specified state.
*
* @return Returns true if the state change has caused the appearance of
* the Drawable to change (that is, it needs to be drawn), else false
* if it looks the same and there is no need to redraw it since its
* last state.
*/
protected boolean onStateChange(int[] state) {
return false;
}
/** Override this in your subclass to change appearance if you vary based
* on level.
* @return Returns true if the level change has caused the appearance of
* the Drawable to change (that is, it needs to be drawn), else false
* if it looks the same and there is no need to redraw it since its
* last level.
*/
protected boolean onLevelChange(int level) {
return false;
}
/**
* Override this in your subclass to change appearance if you vary based on
* the bounds.
*/
protected void onBoundsChange(Rect bounds) {
// Stub method.
}
/**
* Returns the drawable's intrinsic width.
* <p>
* Intrinsic width is the width at which the drawable would like to be laid
* out, including any inherent padding. If the drawable has no intrinsic
* width, such as a solid color, this method returns -1.
*
* @return the intrinsic width, or -1 if no intrinsic width
*/
public int getIntrinsicWidth() {
return -1;
}
/**
* Returns the drawable's intrinsic height.
* <p>
* Intrinsic height is the height at which the drawable would like to be
* laid out, including any inherent padding. If the drawable has no
* intrinsic height, such as a solid color, this method returns -1.
*
* @return the intrinsic height, or -1 if no intrinsic height
*/
public int getIntrinsicHeight() {
return -1;
}
/**
* Returns the minimum width suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its width. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum width suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum width, 0 is returned.
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
/**
* Returns the minimum height suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its height. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum height suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum height, 0 is returned.
*/
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
/**
* Return in padding the insets suggested by this Drawable for placing
* content inside the drawable's bounds. Positive values move toward the
* center of the Drawable (set Rect.inset).
*
* @return true if this drawable actually has a padding, else false. When false is returned,
* the padding is always set to 0.
*/
public boolean getPadding(@NonNull Rect padding) {
padding.set(0, 0, 0, 0);
return false;
}
/**
* Return in insets the layout insets suggested by this Drawable for use with alignment
* operations during layout.
*
*/
public @NonNull Insets getOpticalInsets() {
return Insets.NONE;
}
/**
* Called to get the drawable to populate the Outline that defines its drawing area.
* <p>
* This method is called by the default {@link android.view.ViewOutlineProvider} to define
* the outline of the View.
* <p>
* The default behavior defines the outline to be the bounding rectangle of 0 alpha.
* Subclasses that wish to convey a different shape or alpha value must override this method.
*
* @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
*/
public void getOutline(@NonNull Outline outline) {
outline.setRect(getBounds());
outline.setAlpha(0);
}
/**
* Make this drawable mutable. This operation cannot be reversed. A mutable
* drawable is guaranteed to not share its state with any other drawable.
* This is especially useful when you need to modify properties of drawables
* loaded from resources. By default, all drawables instances loaded from
* the same resource share a common state; if you modify the state of one
* instance, all the other instances will receive the same modification.
*
* Calling this method on a mutable Drawable will have no effect.
*
* @return This drawable.
* @see ConstantState
* @see #getConstantState()
*/
public @NonNull Drawable mutate() {
return this;
}
/**
* Clears the mutated state, allowing this drawable to be cached and
* mutated again.
* <p>
* This is hidden because only framework drawables can be cached, so
* custom drawables don't need to support constant state, mutate(), or
* clearMutated().
*
* @hide
*/
public void clearMutated() {
// Default implementation is no-op.
}
/**
* Create a drawable from an inputstream
*/
public static Drawable createFromStream(InputStream is, String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(null, null, is, srcName);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
*/
public static Drawable createFromResourceStream(Resources res, TypedValue value,
InputStream is, String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(res, value, is, srcName, null);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
*
* @deprecated Prefer the version without an Options object.
*/
@Nullable
public static Drawable createFromResourceStream(@Nullable Resources res,
@Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
@Nullable BitmapFactory.Options opts) {
if (is == null) {
return null;
}
if (opts == null) {
return getBitmapDrawable(res, value, is);
}
/* ugh. The decodeStream contract is that we have already allocated
the pad rect, but if the bitmap does not had a ninepatch chunk,
then the pad will be ignored. If we could change this to lazily
alloc/assign the rect, we could avoid the GC churn of making new
Rects only to drop them on the floor.
*/
Rect pad = new Rect();
// Special stuff for compatibility mode: if the target density is not
// the same as the display density, but the resource -is- the same as
// the display density, then don't scale it down to the target density.
// This allows us to load the system's density-correct resources into
// an application in compatibility mode, without scaling those down
// to the compatibility density only to have them scaled back up when
// drawn to the screen.
opts.inScreenDensity = Drawable.resolveDensity(res, 0);
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
if (bm != null) {
byte[] np = bm.getNinePatchChunk();
if (np == null || !NinePatch.isNinePatchChunk(np)) {
np = null;
pad = null;
}
final Rect opticalInsets = new Rect();
bm.getOpticalInsets(opticalInsets);
return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
}
return null;
}
private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
try {
ImageDecoder.Source source = null;
if (value != null) {
int density = Bitmap.DENSITY_NONE;
if (value.density == TypedValue.DENSITY_DEFAULT) {
density = DisplayMetrics.DENSITY_DEFAULT;
} else if (value.density != TypedValue.DENSITY_NONE) {
density = value.density;
}
source = ImageDecoder.createSource(res, is, density);
} else {
source = ImageDecoder.createSource(res, is);
}
return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
decoder.setOnPartialImageListener((e) -> {
return e.getError() == ImageDecoder.DecodeException.SOURCE_INCOMPLETE;
});
});
} catch (IOException e) {
/* do nothing.
If the exception happened on decode, the drawable will be null.
*/
Log.e("Drawable", "Unable to decode stream: " + e);
}
return null;
}
/**
* Create a drawable from an XML document. For more information on how to
* create resources in XML, see
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
*/
@NonNull
public static Drawable createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser)
throws XmlPullParserException, IOException {
return createFromXml(r, parser, null);
}
/**
* Create a drawable from an XML document using an optional {@link Theme}.
* For more information on how to create resources in XML, see
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
*/
@NonNull
public static Drawable createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
@Nullable Theme theme) throws XmlPullParserException, IOException {
return createFromXmlForDensity(r, parser, 0, theme);
}
/**
* Version of {@link #createFromXml(Resources, XmlPullParser, Theme)} that accepts a density
* override.
* @hide
*/
@NonNull
public static Drawable createFromXmlForDensity(@NonNull Resources r,
@NonNull XmlPullParser parser, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
//noinspection StatementWithEmptyBody
while ((type=parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme);
if (drawable == null) {
throw new RuntimeException("Unknown initial tag: " + parser.getName());
}
return drawable;
}
/**
* Create from inside an XML document. Called on a parser positioned at
* a tag in an XML document, tries to create a Drawable from that tag.
* Returns null if the tag is not a valid drawable.
*/
@NonNull
public static Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
return createFromXmlInner(r, parser, attrs, null);
}
/**
* Create a drawable from inside an XML document using an optional
* {@link Theme}. Called on a parser positioned at a tag in an XML
* document, tries to create a Drawable from that tag. Returns {@code null}
* if the tag is not a valid drawable.
*/
@NonNull
public static Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
return createFromXmlInnerForDensity(r, parser, attrs, 0, theme);
}
/**
* Version of {@link #createFromXmlInner(Resources, XmlPullParser, AttributeSet, Theme)} that
* accepts an override density.
*/
@NonNull
static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
@Nullable Theme theme) throws XmlPullParserException, IOException {
return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
density, theme);
}
/**
* Create a drawable from file path name.
*/
@Nullable
public static Drawable createFromPath(String pathName) {
if (pathName == null) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
try (FileInputStream stream = new FileInputStream(pathName)) {
return getBitmapDrawable(null, null, stream);
} catch(IOException e) {
// Do nothing; we will just return null if the FileInputStream had an error
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
return null;
}
/**
* Inflate this Drawable from an XML resource. Does not apply a theme.
*
* @see #inflate(Resources, XmlPullParser, AttributeSet, Theme)
*/
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
inflate(r, parser, attrs, null);
}
/**
* Inflate this Drawable from an XML resource optionally styled by a theme.
* This can't be called more than once for each Drawable. Note that framework may have called
* this once to create the Drawable instance from XML resource.
*
* @param r Resources used to resolve attribute values
* @param parser XML parser from which to inflate this Drawable
* @param attrs Base set of attribute values
* @param theme Theme to apply, may be null
* @throws XmlPullParserException
* @throws IOException
*/
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
a.recycle();
}
/**
* Inflate a Drawable from an XML resource.
*
* @throws XmlPullParserException
* @throws IOException
*/
@UnsupportedAppUsage
void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r,
@NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs,
@AttrRes int visibleAttr) throws XmlPullParserException, IOException {
mVisible = attrs.getBoolean(visibleAttr, mVisible);
}
/**
* Sets the source override density for this Drawable. If non-zero, this density is to be used
* for any calls to {@link Resources#getDrawableForDensity(int, int, Theme)} or
* {@link Resources#getValueForDensity(int, int, TypedValue, boolean)}.
* @hide
*/
final void setSrcDensityOverride(int density) {
mSrcDensityOverride = density;
}
/**
* This abstract class is used by {@link Drawable}s to store shared constant state and data
* between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
* share a unique bitmap stored in their ConstantState.
*
* <p>
* {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
* from this ConstantState.
* </p>
*
* Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
* {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
* Drawable.
*/
public static abstract class ConstantState {
/**
* Creates a new Drawable instance from its constant state.
* <p>
* <strong>Note:</strong> Using this method means density-dependent
* properties, such as pixel dimensions or bitmap images, will not be
* updated to match the density of the target display. To ensure
* correct scaling, use {@link #newDrawable(Resources)} instead to
* provide an appropriate Resources object.
*
* @return a new drawable object based on this constant state
* @see #newDrawable(Resources)
*/
public abstract @NonNull Drawable newDrawable();
/**
* Creates a new Drawable instance from its constant state using the
* specified resources. This method should be implemented for drawables
* that have density-dependent properties.
* <p>
* The default implementation for this method calls through to
* {@link #newDrawable()}.
*
* @param res the resources of the context in which the drawable will
* be displayed
* @return a new drawable object based on this constant state
*/
public @NonNull Drawable newDrawable(@Nullable Resources res) {
return newDrawable();
}
/**
* Creates a new Drawable instance from its constant state using the
* specified resources and theme. This method should be implemented for
* drawables that have theme-dependent properties.
* <p>
* The default implementation for this method calls through to
* {@link #newDrawable(Resources)}.
*
* @param res the resources of the context in which the drawable will
* be displayed
* @param theme the theme of the context in which the drawable will be
* displayed
* @return a new drawable object based on this constant state
*/
public @NonNull Drawable newDrawable(@Nullable Resources res,
@Nullable @SuppressWarnings("unused") Theme theme) {
return newDrawable(res);
}
/**
* Return a bit mask of configuration changes that will impact
* this drawable (and thus require completely reloading it).
*/
public abstract @Config int getChangingConfigurations();
/**
* Return whether this constant state can have a theme applied.
*/
public boolean canApplyTheme() {
return false;
}
}
/**
* Return a {@link ConstantState} instance that holds the shared state of this Drawable.
*
* @return The ConstantState associated to that Drawable.
* @see ConstantState
* @see Drawable#mutate()
*/
public @Nullable ConstantState getConstantState() {
return null;
}
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
Rect pad, Rect layoutBounds, String srcName) {
if (np != null) {
return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
}
return new BitmapDrawable(res, bm);
}
/**
* Ensures the tint filter is consistent with the current tint color and
* mode.
*/
@UnsupportedAppUsage
@Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
@Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) {
if (tint == null || tintMode == null) {
return null;
}
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
if (tintFilter == null || tintFilter.getColor() != color
|| tintFilter.getMode() != tintMode) {
return new PorterDuffColorFilter(color, tintMode);
}
return tintFilter;
}
@Nullable BlendModeColorFilter updateBlendModeFilter(@Nullable BlendModeColorFilter blendFilter,
@Nullable ColorStateList tint, @Nullable BlendMode blendMode) {
if (tint == null || blendMode == null) {
return null;
}
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
if (blendFilter == null || blendFilter.getColor() != color
|| blendFilter.getMode() != blendMode) {
return new BlendModeColorFilter(color, blendMode);
}
return blendFilter;
}
/**
* Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null.
* @hide
*/
protected static @NonNull TypedArray obtainAttributes(@NonNull Resources res,
@Nullable Theme theme, @NonNull AttributeSet set, @NonNull int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
/**
* Scales a floating-point pixel value from the source density to the
* target density.
*
* @param pixels the pixel value for use in source density
* @param sourceDensity the source density
* @param targetDensity the target density
* @return the scaled pixel value for use in target density
*/
static float scaleFromDensity(float pixels, int sourceDensity, int targetDensity) {
return pixels * targetDensity / sourceDensity;
}
/**
* Scales a pixel value from the source density to the target density,
* optionally handling the resulting pixel value as a size rather than an
* offset.
* <p>
* A size conversion involves rounding the base value and ensuring that
* a non-zero base value is at least one pixel in size.
* <p>
* An offset conversion involves simply truncating the base value to an
* integer.
*
* @param pixels the pixel value for use in source density
* @param sourceDensity the source density
* @param targetDensity the target density
* @param isSize {@code true} to handle the resulting scaled value as a
* size, or {@code false} to handle it as an offset
* @return the scaled pixel value for use in target density
*/
static int scaleFromDensity(
int pixels, int sourceDensity, int targetDensity, boolean isSize) {
if (pixels == 0 || sourceDensity == targetDensity) {
return pixels;
}
final float result = pixels * targetDensity / (float) sourceDensity;
if (!isSize) {
return (int) result;
}
final int rounded = Math.round(result);
if (rounded != 0) {
return rounded;
} else if (pixels > 0) {
return 1;
} else {
return -1;
}
}
static int resolveDensity(@Nullable Resources r, int parentDensity) {
final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
}
/**
* Re-throws an exception as a {@link RuntimeException} with an empty stack
* trace to avoid cluttering the log. The original exception's stack trace
* will still be included.
*
* @param cause the exception to re-throw
* @throws RuntimeException
*/
static void rethrowAsRuntimeException(@NonNull Exception cause) throws RuntimeException {
final RuntimeException e = new RuntimeException(cause);
e.setStackTrace(new StackTraceElement[0]);
throw e;
}
/**
* Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
* attribute's enum value.
*
* @hide
*/
@UnsupportedAppUsage
public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
switch (value) {
case 3: return Mode.SRC_OVER;
case 5: return Mode.SRC_IN;
case 9: return Mode.SRC_ATOP;
case 14: return Mode.MULTIPLY;
case 15: return Mode.SCREEN;
case 16: return Mode.ADD;
default: return defaultMode;
}
}
/**
* Parses a {@link android.graphics.BlendMode} from a tintMode
* attribute's enum value.
*
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static BlendMode parseBlendMode(int value, BlendMode defaultMode) {
switch (value) {
case 3: return BlendMode.SRC_OVER;
case 5: return BlendMode.SRC_IN;
case 9: return BlendMode.SRC_ATOP;
// b/73224934 PorterDuff Multiply maps to Skia Modulate so actually
// return BlendMode.MODULATE here
case 14: return BlendMode.MODULATE;
case 15: return BlendMode.SCREEN;
case 16: return BlendMode.PLUS;
default: return defaultMode;
}
}
}
BitmapDrawable
/*
* Copyright (C) 2006 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.TypedValue;
import android.view.Gravity;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
* BitmapDrawable from a file path, an input stream, through XML inflation, or from
* a {@link android.graphics.Bitmap} object.
* <p>It can be defined in an XML file with the <code><bitmap></code> element. For more
* information, see the guide to <a
* href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
* <p>
* Also see the {@link android.graphics.Bitmap} class, which handles the management and
* transformation of raw bitmap graphics, and should be used when drawing to a
* {@link android.graphics.Canvas}.
* </p>
*
* @attr ref android.R.styleable#BitmapDrawable_src
* @attr ref android.R.styleable#BitmapDrawable_antialias
* @attr ref android.R.styleable#BitmapDrawable_filter
* @attr ref android.R.styleable#BitmapDrawable_dither
* @attr ref android.R.styleable#BitmapDrawable_gravity
* @attr ref android.R.styleable#BitmapDrawable_mipMap
* @attr ref android.R.styleable#BitmapDrawable_tileMode
*/
public class BitmapDrawable extends Drawable {
private static final int DEFAULT_PAINT_FLAGS =
Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
// Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
private static final int TILE_MODE_UNDEFINED = -2;
private static final int TILE_MODE_DISABLED = -1;
private static final int TILE_MODE_CLAMP = 0;
private static final int TILE_MODE_REPEAT = 1;
private static final int TILE_MODE_MIRROR = 2;
private final Rect mDstRect = new Rect(); // #updateDstRectAndInsetsIfDirty() sets this
@UnsupportedAppUsage
private BitmapState mBitmapState;
private BlendModeColorFilter mBlendModeFilter;
private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
private boolean mDstRectAndInsetsDirty = true;
private boolean mMutated;
// These are scaled to match the target density.
private int mBitmapWidth;
private int mBitmapHeight;
/** Optical insets due to gravity. */
private Insets mOpticalInsets = Insets.NONE;
// Mirroring matrix for using with Shaders
private Matrix mMirrorMatrix;
/**
* Create an empty drawable, not dealing with density.
* @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
* instead to specify a bitmap to draw with and ensure the correct density is set.
*/
@Deprecated
public BitmapDrawable() {
init(new BitmapState((Bitmap) null), null);
}
/**
* Create an empty drawable, setting initial target density based on
* the display metrics of the resources.
*
* @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
* instead to specify a bitmap to draw with.
*/
@SuppressWarnings("unused")
@Deprecated
public BitmapDrawable(Resources res) {
init(new BitmapState((Bitmap) null), res);
}
/**
* Create drawable from a bitmap, not dealing with density.
* @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure
* that the drawable has correctly set its target density.
*/
@Deprecated
public BitmapDrawable(Bitmap bitmap) {
init(new BitmapState(bitmap), null);
}
/**
* Create drawable from a bitmap, setting initial target density based on
* the display metrics of the resources.
*/
public BitmapDrawable(Resources res, Bitmap bitmap) {
init(new BitmapState(bitmap), res);
}
/**
* Create a drawable by opening a given file path and decoding the bitmap.
* @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure
* that the drawable has correctly set its target density.
*/
@Deprecated
public BitmapDrawable(String filepath) {
this(null, filepath);
}
/**
* Create a drawable by opening a given file path and decoding the bitmap.
*/
@SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
public BitmapDrawable(Resources res, String filepath) {
Bitmap bitmap = null;
try (FileInputStream stream = new FileInputStream(filepath)) {
bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
(decoder, info, src) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (Exception e) {
/* do nothing. This matches the behavior of BitmapFactory.decodeFile()
If the exception happened on decode, mBitmapState.mBitmap will be null.
*/
} finally {
init(new BitmapState(bitmap), res);
if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
}
}
}
/**
* Create a drawable by decoding a bitmap from the given input stream.
* @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure
* that the drawable has correctly set its target density.
*/
@Deprecated
public BitmapDrawable(java.io.InputStream is) {
this(null, is);
}
/**
* Create a drawable by decoding a bitmap from the given input stream.
*/
@SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
public BitmapDrawable(Resources res, java.io.InputStream is) {
Bitmap bitmap = null;
try {
bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
(decoder, info, src) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (Exception e) {
/* do nothing. This matches the behavior of BitmapFactory.decodeStream()
If the exception happened on decode, mBitmapState.mBitmap will be null.
*/
} finally {
init(new BitmapState(bitmap), res);
if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
}
}
}
/**
* Returns the paint used to render this drawable.
*/
public final Paint getPaint() {
return mBitmapState.mPaint;
}
/**
* Returns the bitmap used by this drawable to render. May be null.
*/
public final Bitmap getBitmap() {
return mBitmapState.mBitmap;
}
private void computeBitmapSize() {
final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap != null) {
mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
} else {
mBitmapWidth = mBitmapHeight = -1;
}
}
/**
* Switch to a new Bitmap object.
*/
public void setBitmap(@Nullable Bitmap bitmap) {
if (mBitmapState.mBitmap != bitmap) {
mBitmapState.mBitmap = bitmap;
computeBitmapSize();
invalidateSelf();
}
}
/**
* Set the density scale at which this drawable will be rendered. This
* method assumes the drawable will be rendered at the same density as the
* specified canvas.
*
* @param canvas The Canvas from which the density scale must be obtained.
*
* @see android.graphics.Bitmap#setDensity(int)
* @see android.graphics.Bitmap#getDensity()
*/
public void setTargetDensity(Canvas canvas) {
setTargetDensity(canvas.getDensity());
}
/**
* Set the density scale at which this drawable will be rendered.
*
* @param metrics The DisplayMetrics indicating the density scale for this drawable.
*
* @see android.graphics.Bitmap#setDensity(int)
* @see android.graphics.Bitmap#getDensity()
*/
public void setTargetDensity(DisplayMetrics metrics) {
setTargetDensity(metrics.densityDpi);
}
/**
* Set the density at which this drawable will be rendered.
*
* @param density The density scale for this drawable.
*
* @see android.graphics.Bitmap#setDensity(int)
* @see android.graphics.Bitmap#getDensity()
*/
public void setTargetDensity(int density) {
if (mTargetDensity != density) {
mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
if (mBitmapState.mBitmap != null) {
computeBitmapSize();
}
invalidateSelf();
}
}
/** Get the gravity used to position/stretch the bitmap within its bounds.
* See android.view.Gravity
* @return the gravity applied to the bitmap
*/
public int getGravity() {
return mBitmapState.mGravity;
}
/** Set the gravity used to position/stretch the bitmap within its bounds.
See android.view.Gravity
* @param gravity the gravity
*/
public void setGravity(int gravity) {
if (mBitmapState.mGravity != gravity) {
mBitmapState.mGravity = gravity;
mDstRectAndInsetsDirty = true;
invalidateSelf();
}
}
/**
* Enables or disables the mipmap hint for this drawable's bitmap.
* See {@link Bitmap#setHasMipMap(boolean)} for more information.
*
* If the bitmap is null calling this method has no effect.
*
* @param mipMap True if the bitmap should use mipmaps, false otherwise.
*
* @see #hasMipMap()
*/
public void setMipMap(boolean mipMap) {
if (mBitmapState.mBitmap != null) {
mBitmapState.mBitmap.setHasMipMap(mipMap);
invalidateSelf();
}
}
/**
* Indicates whether the mipmap hint is enabled on this drawable's bitmap.
*
* @return True if the mipmap hint is set, false otherwise. If the bitmap
* is null, this method always returns false.
*
* @see #setMipMap(boolean)
* @attr ref android.R.styleable#BitmapDrawable_mipMap
*/
public boolean hasMipMap() {
return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
}
/**
* Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
* the edges of the bitmap only so it applies only when the drawable is rotated.
*
* @param aa True if the bitmap should be anti-aliased, false otherwise.
*
* @see #hasAntiAlias()
*/
public void setAntiAlias(boolean aa) {
mBitmapState.mPaint.setAntiAlias(aa);
invalidateSelf();
}
/**
* Indicates whether anti-aliasing is enabled for this drawable.
*
* @return True if anti-aliasing is enabled, false otherwise.
*
* @see #setAntiAlias(boolean)
*/
public boolean hasAntiAlias() {
return mBitmapState.mPaint.isAntiAlias();
}
@Override
public void setFilterBitmap(boolean filter) {
mBitmapState.mPaint.setFilterBitmap(filter);
invalidateSelf();
}
@Override
public boolean isFilterBitmap() {
return mBitmapState.mPaint.isFilterBitmap();
}
@Override
public void setDither(boolean dither) {
mBitmapState.mPaint.setDither(dither);
invalidateSelf();
}
/**
* Indicates the repeat behavior of this drawable on the X axis.
*
* @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
* {@link android.graphics.Shader.TileMode#REPEAT} or
* {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
*/
public Shader.TileMode getTileModeX() {
return mBitmapState.mTileModeX;
}
/**
* Indicates the repeat behavior of this drawable on the Y axis.
*
* @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
* {@link android.graphics.Shader.TileMode#REPEAT} or
* {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
*/
public Shader.TileMode getTileModeY() {
return mBitmapState.mTileModeY;
}
/**
* Sets the repeat behavior of this drawable on the X axis. By default, the drawable
* does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
* {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
* if the bitmap is smaller than this drawable.
*
* @param mode The repeat mode for this drawable.
*
* @see #setTileModeY(android.graphics.Shader.TileMode)
* @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
* @attr ref android.R.styleable#BitmapDrawable_tileModeX
*/
public void setTileModeX(Shader.TileMode mode) {
setTileModeXY(mode, mBitmapState.mTileModeY);
}
/**
* Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
* does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
* {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
* if the bitmap is smaller than this drawable.
*
* @param mode The repeat mode for this drawable.
*
* @see #setTileModeX(android.graphics.Shader.TileMode)
* @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
* @attr ref android.R.styleable#BitmapDrawable_tileModeY
*/
public final void setTileModeY(Shader.TileMode mode) {
setTileModeXY(mBitmapState.mTileModeX, mode);
}
/**
* Sets the repeat behavior of this drawable on both axis. By default, the drawable
* does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
* {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
* if the bitmap is smaller than this drawable.
*
* @param xmode The X repeat mode for this drawable.
* @param ymode The Y repeat mode for this drawable.
*
* @see #setTileModeX(android.graphics.Shader.TileMode)
* @see #setTileModeY(android.graphics.Shader.TileMode)
*/
public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
final BitmapState state = mBitmapState;
if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
state.mTileModeX = xmode;
state.mTileModeY = ymode;
state.mRebuildShader = true;
mDstRectAndInsetsDirty = true;
invalidateSelf();
}
}
@Override
public void setAutoMirrored(boolean mirrored) {
if (mBitmapState.mAutoMirrored != mirrored) {
mBitmapState.mAutoMirrored = mirrored;
invalidateSelf();
}
}
@Override
public final boolean isAutoMirrored() {
return mBitmapState.mAutoMirrored;
}
@Override
public @Config int getChangingConfigurations() {
return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
}
private boolean needMirroring() {
return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
}
@Override
protected void onBoundsChange(Rect bounds) {
mDstRectAndInsetsDirty = true;
final Bitmap bitmap = mBitmapState.mBitmap;
final Shader shader = mBitmapState.mPaint.getShader();
if (bitmap != null && shader != null) {
updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring());
}
}
@Override
public void draw(Canvas canvas) {
final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap == null) {
return;
}
final BitmapState state = mBitmapState;
final Paint paint = state.mPaint;
if (state.mRebuildShader) {
final Shader.TileMode tmx = state.mTileModeX;
final Shader.TileMode tmy = state.mTileModeY;
if (tmx == null && tmy == null) {
paint.setShader(null);
} else {
paint.setShader(new BitmapShader(bitmap,
tmx == null ? Shader.TileMode.CLAMP : tmx,
tmy == null ? Shader.TileMode.CLAMP : tmy));
}
state.mRebuildShader = false;
}
final int restoreAlpha;
if (state.mBaseAlpha != 1.0f) {
final Paint p = getPaint();
restoreAlpha = p.getAlpha();
p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
} else {
restoreAlpha = -1;
}
final boolean clearColorFilter;
if (mBlendModeFilter != null && paint.getColorFilter() == null) {
paint.setColorFilter(mBlendModeFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
updateDstRectAndInsetsIfDirty();
final Shader shader = paint.getShader();
final boolean needMirroring = needMirroring();
if (shader == null) {
if (needMirroring) {
canvas.save();
// Mirror the bitmap
canvas.translate(mDstRect.right - mDstRect.left, 0);
canvas.scale(-1.0f, 1.0f);
}
canvas.drawBitmap(bitmap, null, mDstRect, paint);
if (needMirroring) {
canvas.restore();
}
} else {
updateShaderMatrix(bitmap, paint, shader, needMirroring);
canvas.drawRect(mDstRect, paint);
}
if (clearColorFilter) {
paint.setColorFilter(null);
}
if (restoreAlpha >= 0) {
paint.setAlpha(restoreAlpha);
}
}
/**
* Updates the {@code paint}'s shader matrix to be consistent with the
* destination size and layout direction.
*
* @param bitmap the bitmap to be drawn
* @param paint the paint used to draw the bitmap
* @param shader the shader to set on the paint
* @param needMirroring whether the bitmap should be mirrored
*/
private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint,
@NonNull Shader shader, boolean needMirroring) {
final int sourceDensity = bitmap.getDensity();
final int targetDensity = mTargetDensity;
final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity;
if (needScaling || needMirroring) {
final Matrix matrix = getOrCreateMirrorMatrix();
matrix.reset();
if (needMirroring) {
final int dx = mDstRect.right - mDstRect.left;
matrix.setTranslate(dx, 0);
matrix.setScale(-1, 1);
}
if (needScaling) {
final float densityScale = targetDensity / (float) sourceDensity;
matrix.postScale(densityScale, densityScale);
}
shader.setLocalMatrix(matrix);
} else {
mMirrorMatrix = null;
shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
}
paint.setShader(shader);
}
private Matrix getOrCreateMirrorMatrix() {
if (mMirrorMatrix == null) {
mMirrorMatrix = new Matrix();
}
return mMirrorMatrix;
}
private void updateDstRectAndInsetsIfDirty() {
if (mDstRectAndInsetsDirty) {
if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
final Rect bounds = getBounds();
final int layoutDirection = getLayoutDirection();
Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
bounds, mDstRect, layoutDirection);
final int left = mDstRect.left - bounds.left;
final int top = mDstRect.top - bounds.top;
final int right = bounds.right - mDstRect.right;
final int bottom = bounds.bottom - mDstRect.bottom;
mOpticalInsets = Insets.of(left, top, right, bottom);
} else {
copyBounds(mDstRect);
mOpticalInsets = Insets.NONE;
}
}
mDstRectAndInsetsDirty = false;
}
@Override
public Insets getOpticalInsets() {
updateDstRectAndInsetsIfDirty();
return mOpticalInsets;
}
@Override
public void getOutline(@NonNull Outline outline) {
updateDstRectAndInsetsIfDirty();
outline.setRect(mDstRect);
// Only opaque Bitmaps can report a non-0 alpha,
// since only they are guaranteed to fill their bounds
boolean opaqueOverShape = mBitmapState.mBitmap != null
&& !mBitmapState.mBitmap.hasAlpha();
outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
}
@Override
public void setAlpha(int alpha) {
final int oldAlpha = mBitmapState.mPaint.getAlpha();
if (alpha != oldAlpha) {
mBitmapState.mPaint.setAlpha(alpha);
invalidateSelf();
}
}
@Override
public int getAlpha() {
return mBitmapState.mPaint.getAlpha();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mBitmapState.mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public ColorFilter getColorFilter() {
return mBitmapState.mPaint.getColorFilter();
}
@Override
public void setTintList(ColorStateList tint) {
final BitmapState state = mBitmapState;
if (state.mTint != tint) {
state.mTint = tint;
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
mBitmapState.mBlendMode);
invalidateSelf();
}
}
@Override
public void setTintBlendMode(@NonNull BlendMode blendMode) {
final BitmapState state = mBitmapState;
if (state.mBlendMode != blendMode) {
state.mBlendMode = blendMode;
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
blendMode);
invalidateSelf();
}
}
/**
* No longer needed by ProgressBar, but still here due to UnsupportedAppUsage.
*/
@UnsupportedAppUsage
private ColorStateList getTint() {
return mBitmapState.mTint;
}
/**
* No longer needed by ProgressBar, but still here due to UnsupportedAppUsage.
*/
@UnsupportedAppUsage
private Mode getTintMode() {
return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode);
}
/**
* @hide Candidate for future API inclusion
*/
@Override
public void setXfermode(Xfermode xfermode) {
mBitmapState.mPaint.setXfermode(xfermode);
invalidateSelf();
}
/**
* A mutable BitmapDrawable still shares its Bitmap with any other Drawable
* that comes from the same resource.
*
* @return This drawable.
*/
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mBitmapState = new BitmapState(mBitmapState);
mMutated = true;
}
return this;
}
/**
* @hide
*/
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
@Override
protected boolean onStateChange(int[] stateSet) {
final BitmapState state = mBitmapState;
if (state.mTint != null && state.mBlendMode != null) {
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
state.mBlendMode);
return true;
}
return false;
}
@Override
public boolean isStateful() {
return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
|| super.isStateful();
}
@Override
public boolean hasFocusStateSpecified() {
return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified();
}
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
updateStateFromTypedArray(a, mSrcDensityOverride);
verifyRequiredAttributes(a);
a.recycle();
// Update local properties.
updateLocalState(r);
}
/**
* Ensures all required attributes are set.
*
* @throws XmlPullParserException if any required attributes are missing
*/
private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
// If we're not waiting on a theme, verify required attributes.
final BitmapState state = mBitmapState;
if (state.mBitmap == null && (state.mThemeAttrs == null
|| state.mThemeAttrs[R.styleable.BitmapDrawable_src] == 0)) {
throw new XmlPullParserException(a.getPositionDescription() +
": <bitmap> requires a valid 'src' attribute");
}
}
/**
* Updates the constant state from the values in the typed array.
*/
private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
throws XmlPullParserException {
final Resources r = a.getResources();
final BitmapState state = mBitmapState;
// Account for any configuration changes.
state.mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
state.mSrcDensityOverride = srcDensityOverride;
state.mTargetDensity = Drawable.resolveDensity(r, 0);
final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
if (srcResId != 0) {
final TypedValue value = new TypedValue();
r.getValueForDensity(srcResId, srcDensityOverride, value, true);
// Pretend the requested density is actually the display density. If
// the drawable returned is not the requested density, then force it
// to be scaled later by dividing its density by the ratio of
// requested density to actual device density. Drawables that have
// undefined density or no density don't need to be handled here.
if (srcDensityOverride > 0 && value.density > 0
&& value.density != TypedValue.DENSITY_NONE) {
if (value.density == srcDensityOverride) {
value.density = r.getDisplayMetrics().densityDpi;
} else {
value.density =
(value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride;
}
}
int density = Bitmap.DENSITY_NONE;
if (value.density == TypedValue.DENSITY_DEFAULT) {
density = DisplayMetrics.DENSITY_DEFAULT;
} else if (value.density != TypedValue.DENSITY_NONE) {
density = value.density;
}
Bitmap bitmap = null;
try (InputStream is = r.openRawResource(srcResId, value)) {
ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (Exception e) {
// Do nothing and pick up the error below.
}
if (bitmap == null) {
throw new XmlPullParserException(a.getPositionDescription() +
": <bitmap> requires a valid 'src' attribute");
}
state.mBitmap = bitmap;
}
final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
state.mAutoMirrored = a.getBoolean(
R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
if (tintMode != -1) {
state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
if (tint != null) {
state.mTint = tint;
}
final Paint paint = mBitmapState.mPaint;
paint.setAntiAlias(a.getBoolean(
R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
paint.setFilterBitmap(a.getBoolean(
R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
if (tileMode != TILE_MODE_UNDEFINED) {
final Shader.TileMode mode = parseTileMode(tileMode);
setTileModeXY(mode, mode);
}
final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
if (tileModeX != TILE_MODE_UNDEFINED) {
setTileModeX(parseTileMode(tileModeX));
}
final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
if (tileModeY != TILE_MODE_UNDEFINED) {
setTileModeY(parseTileMode(tileModeY));
}
}
@Override
public void applyTheme(Theme t) {
super.applyTheme(t);
final BitmapState state = mBitmapState;
if (state == null) {
return;
}
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
try {
updateStateFromTypedArray(a, state.mSrcDensityOverride);
} catch (XmlPullParserException e) {
rethrowAsRuntimeException(e);
} finally {
a.recycle();
}
}
// Apply theme to contained color state list.
if (state.mTint != null && state.mTint.canApplyTheme()) {
state.mTint = state.mTint.obtainForTheme(t);
}
// Update local properties.
updateLocalState(t.getResources());
}
private static Shader.TileMode parseTileMode(int tileMode) {
switch (tileMode) {
case TILE_MODE_CLAMP:
return Shader.TileMode.CLAMP;
case TILE_MODE_REPEAT:
return Shader.TileMode.REPEAT;
case TILE_MODE_MIRROR:
return Shader.TileMode.MIRROR;
default:
return null;
}
}
@Override
public boolean canApplyTheme() {
return mBitmapState != null && mBitmapState.canApplyTheme();
}
@Override
public int getIntrinsicWidth() {
return mBitmapWidth;
}
@Override
public int getIntrinsicHeight() {
return mBitmapHeight;
}
@Override
public int getOpacity() {
if (mBitmapState.mGravity != Gravity.FILL) {
return PixelFormat.TRANSLUCENT;
}
final Bitmap bitmap = mBitmapState.mBitmap;
return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
}
@Override
public final ConstantState getConstantState() {
mBitmapState.mChangingConfigurations |= getChangingConfigurations();
return mBitmapState;
}
final static class BitmapState extends ConstantState {
final Paint mPaint;
// Values loaded during inflation.
int[] mThemeAttrs = null;
Bitmap mBitmap = null;
ColorStateList mTint = null;
BlendMode mBlendMode = DEFAULT_BLEND_MODE;
int mGravity = Gravity.FILL;
float mBaseAlpha = 1.0f;
Shader.TileMode mTileModeX = null;
Shader.TileMode mTileModeY = null;
// The density to use when looking up the bitmap in Resources. A value of 0 means use
// the system's density.
int mSrcDensityOverride = 0;
// The density at which to render the bitmap.
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
boolean mAutoMirrored = false;
@Config int mChangingConfigurations;
boolean mRebuildShader;
BitmapState(Bitmap bitmap) {
mBitmap = bitmap;
mPaint = new Paint(DEFAULT_PAINT_FLAGS);
}
BitmapState(BitmapState bitmapState) {
mBitmap = bitmapState.mBitmap;
mTint = bitmapState.mTint;
mBlendMode = bitmapState.mBlendMode;
mThemeAttrs = bitmapState.mThemeAttrs;
mChangingConfigurations = bitmapState.mChangingConfigurations;
mGravity = bitmapState.mGravity;
mTileModeX = bitmapState.mTileModeX;
mTileModeY = bitmapState.mTileModeY;
mSrcDensityOverride = bitmapState.mSrcDensityOverride;
mTargetDensity = bitmapState.mTargetDensity;
mBaseAlpha = bitmapState.mBaseAlpha;
mPaint = new Paint(bitmapState.mPaint);
mRebuildShader = bitmapState.mRebuildShader;
mAutoMirrored = bitmapState.mAutoMirrored;
}
@Override
public boolean canApplyTheme() {
return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
}
@Override
public Drawable newDrawable() {
return new BitmapDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new BitmapDrawable(this, res);
}
@Override
public @Config int getChangingConfigurations() {
return mChangingConfigurations
| (mTint != null ? mTint.getChangingConfigurations() : 0);
}
}
private BitmapDrawable(BitmapState state, Resources res) {
init(state, res);
}
/**
* The one helper to rule them all. This is called by all public & private
* constructors to set the state and initialize local properties.
*/
private void init(BitmapState state, Resources res) {
mBitmapState = state;
updateLocalState(res);
if (mBitmapState != null && res != null) {
mBitmapState.mTargetDensity = mTargetDensity;
}
}
/**
* 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) {
mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
mBitmapState.mBlendMode);
computeBitmapSize();
}
}
ShapeDrawable
/*
* Copyright (C) 2007 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.graphics.drawable.shapes.Shape;
import android.util.AttributeSet;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* A Drawable object that draws primitive shapes. A ShapeDrawable takes a
* {@link android.graphics.drawable.shapes.Shape} object and manages its
* presence on the screen. If no Shape is given, then the ShapeDrawable will
* default to a {@link android.graphics.drawable.shapes.RectShape}.
* <p>
* This object can be defined in an XML file with the <code><shape></code>
* element.
* </p>
* <div class="special reference"> <h3>Developer Guides</h3>
* <p>
* For more information about how to use ShapeDrawable, read the <a
* href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable">
* Canvas and Drawables</a> document. For more information about defining a
* ShapeDrawable in XML, read the
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape">
* Drawable Resources</a> document.
* </p>
* </div>
*
* @attr ref android.R.styleable#ShapeDrawablePadding_left
* @attr ref android.R.styleable#ShapeDrawablePadding_top
* @attr ref android.R.styleable#ShapeDrawablePadding_right
* @attr ref android.R.styleable#ShapeDrawablePadding_bottom
* @attr ref android.R.styleable#ShapeDrawable_color
* @attr ref android.R.styleable#ShapeDrawable_width
* @attr ref android.R.styleable#ShapeDrawable_height
*/
public class ShapeDrawable extends Drawable {
private @NonNull ShapeState mShapeState;
private BlendModeColorFilter mBlendModeColorFilter;
private boolean mMutated;
/**
* ShapeDrawable constructor.
*/
public ShapeDrawable() {
this(new ShapeState(), null);
}
/**
* Creates a ShapeDrawable with a specified Shape.
*
* @param s the Shape that this ShapeDrawable should be
*/
public ShapeDrawable(Shape s) {
this(new ShapeState(), null);
mShapeState.mShape = s;
}
/**
* Returns the Shape of this ShapeDrawable.
*/
public Shape getShape() {
return mShapeState.mShape;
}
/**
* Sets the Shape of this ShapeDrawable.
*/
public void setShape(Shape s) {
mShapeState.mShape = s;
updateShape();
}
/**
* Sets a ShaderFactory to which requests for a
* {@link android.graphics.Shader} object will be made.
*
* @param fact an instance of your ShaderFactory implementation
*/
public void setShaderFactory(ShaderFactory fact) {
mShapeState.mShaderFactory = fact;
}
/**
* Returns the ShaderFactory used by this ShapeDrawable for requesting a
* {@link android.graphics.Shader}.
*/
public ShaderFactory getShaderFactory() {
return mShapeState.mShaderFactory;
}
/**
* Returns the Paint used to draw the shape.
*/
public Paint getPaint() {
return mShapeState.mPaint;
}
/**
* Sets padding for the shape.
*
* @param left padding for the left side (in pixels)
* @param top padding for the top (in pixels)
* @param right padding for the right side (in pixels)
* @param bottom padding for the bottom (in pixels)
*/
public void setPadding(int left, int top, int right, int bottom) {
if ((left | top | right | bottom) == 0) {
mShapeState.mPadding = null;
} else {
if (mShapeState.mPadding == null) {
mShapeState.mPadding = new Rect();
}
mShapeState.mPadding.set(left, top, right, bottom);
}
invalidateSelf();
}
/**
* Sets padding for this shape, defined by a Rect object. Define the padding
* in the Rect object as: left, top, right, bottom.
*/
public void setPadding(Rect padding) {
if (padding == null) {
mShapeState.mPadding = null;
} else {
if (mShapeState.mPadding == null) {
mShapeState.mPadding = new Rect();
}
mShapeState.mPadding.set(padding);
}
invalidateSelf();
}
/**
* Sets the intrinsic (default) width for this shape.
*
* @param width the intrinsic width (in pixels)
*/
public void setIntrinsicWidth(int width) {
mShapeState.mIntrinsicWidth = width;
invalidateSelf();
}
/**
* Sets the intrinsic (default) height for this shape.
*
* @param height the intrinsic height (in pixels)
*/
public void setIntrinsicHeight(int height) {
mShapeState.mIntrinsicHeight = height;
invalidateSelf();
}
@Override
public int getIntrinsicWidth() {
return mShapeState.mIntrinsicWidth;
}
@Override
public int getIntrinsicHeight() {
return mShapeState.mIntrinsicHeight;
}
@Override
public boolean getPadding(Rect padding) {
if (mShapeState.mPadding != null) {
padding.set(mShapeState.mPadding);
return true;
} else {
return super.getPadding(padding);
}
}
private static int modulateAlpha(int paintAlpha, int alpha) {
int scale = alpha + (alpha >>> 7); // convert to 0..256
return paintAlpha * scale >>> 8;
}
/**
* Called from the drawable's draw() method after the canvas has been set to
* draw the shape at (0,0). Subclasses can override for special effects such
* as multiple layers, stroking, etc.
*/
protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
shape.draw(canvas, paint);
}
@Override
public void draw(Canvas canvas) {
final Rect r = getBounds();
final ShapeState state = mShapeState;
final Paint paint = state.mPaint;
final int prevAlpha = paint.getAlpha();
paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha));
// only draw shape if it may affect output
if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) {
final boolean clearColorFilter;
if (mBlendModeColorFilter != null && paint.getColorFilter() == null) {
paint.setColorFilter(mBlendModeColorFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
if (state.mShape != null) {
// need the save both for the translate, and for the (unknown)
// Shape
final int count = canvas.save();
canvas.translate(r.left, r.top);
onDraw(state.mShape, canvas, paint);
canvas.restoreToCount(count);
} else {
canvas.drawRect(r, paint);
}
if (clearColorFilter) {
paint.setColorFilter(null);
}
}
// restore
paint.setAlpha(prevAlpha);
}
@Override
public @Config int getChangingConfigurations() {
return super.getChangingConfigurations() | mShapeState.getChangingConfigurations();
}
/**
* Set the alpha level for this drawable [0..255]. Note that this drawable
* also has a color in its paint, which has an alpha as well. These two
* values are automatically combined during drawing. Thus if the color's
* alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then
* the combined alpha that will be used during drawing will be 37.5% (i.e.
* 96).
*/
@Override
public void setAlpha(int alpha) {
mShapeState.mAlpha = alpha;
invalidateSelf();
}
@Override
public int getAlpha() {
return mShapeState.mAlpha;
}
@Override
public void setTintList(ColorStateList tint) {
mShapeState.mTint = tint;
mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint,
mShapeState.mBlendMode);
invalidateSelf();
}
@Override
public void setTintBlendMode(@NonNull BlendMode blendMode) {
mShapeState.mBlendMode = blendMode;
mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint,
blendMode);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mShapeState.mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
/**
* @hide
*/
@Override
@TestApi
public void setXfermode(@Nullable Xfermode mode) {
mShapeState.mPaint.setXfermode(mode);
invalidateSelf();
}
@Override
public int getOpacity() {
if (mShapeState.mShape == null) {
final Paint p = mShapeState.mPaint;
if (p.getXfermode() == null) {
final int alpha = p.getAlpha();
if (alpha == 0) {
return PixelFormat.TRANSPARENT;
}
if (alpha == 255) {
return PixelFormat.OPAQUE;
}
}
}
// not sure, so be safe
return PixelFormat.TRANSLUCENT;
}
@Override
public void setDither(boolean dither) {
mShapeState.mPaint.setDither(dither);
invalidateSelf();
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateShape();
}
@Override
protected boolean onStateChange(int[] stateSet) {
final ShapeState state = mShapeState;
if (state.mTint != null && state.mBlendMode != null) {
mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
state.mBlendMode);
return true;
}
return false;
}
@Override
public boolean isStateful() {
final ShapeState s = mShapeState;
return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
}
@Override
public boolean hasFocusStateSpecified() {
return mShapeState.mTint != null && mShapeState.mTint.hasFocusStateSpecified();
}
/**
* Subclasses override this to parse custom subelements. If you handle it,
* return true, else return <em>super.inflateTag(...)</em>.
*/
protected boolean inflateTag(String name, Resources r, XmlPullParser parser,
AttributeSet attrs) {
if ("padding".equals(name)) {
TypedArray a = r.obtainAttributes(attrs,
com.android.internal.R.styleable.ShapeDrawablePadding);
setPadding(
a.getDimensionPixelOffset(
com.android.internal.R.styleable.ShapeDrawablePadding_left, 0),
a.getDimensionPixelOffset(
com.android.internal.R.styleable.ShapeDrawablePadding_top, 0),
a.getDimensionPixelOffset(
com.android.internal.R.styleable.ShapeDrawablePadding_right, 0),
a.getDimensionPixelOffset(
com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0));
a.recycle();
return true;
}
return false;
}
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable);
updateStateFromTypedArray(a);
a.recycle();
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) {
continue;
}
final String name = parser.getName();
// call our subclass
if (!inflateTag(name, r, parser, attrs)) {
android.util.Log.w("drawable", "Unknown element: " + name +
" for ShapeDrawable " + this);
}
}
// Update local properties.
updateLocalState();
}
@Override
public void applyTheme(Theme t) {
super.applyTheme(t);
final ShapeState state = mShapeState;
if (state == null) {
return;
}
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable);
updateStateFromTypedArray(a);
a.recycle();
}
// Apply theme to contained color state list.
if (state.mTint != null && state.mTint.canApplyTheme()) {
state.mTint = state.mTint.obtainForTheme(t);
}
// Update local properties.
updateLocalState();
}
private void updateStateFromTypedArray(TypedArray a) {
final ShapeState state = mShapeState;
final Paint paint = state.mPaint;
// Account for any configuration changes.
state.mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
int color = paint.getColor();
color = a.getColor(R.styleable.ShapeDrawable_color, color);
paint.setColor(color);
boolean dither = paint.isDither();
dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither);
paint.setDither(dither);
state.mIntrinsicWidth = (int) a.getDimension(
R.styleable.ShapeDrawable_width, state.mIntrinsicWidth);
state.mIntrinsicHeight = (int) a.getDimension(
R.styleable.ShapeDrawable_height, state.mIntrinsicHeight);
final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
if (tintMode != -1) {
state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
if (tint != null) {
state.mTint = tint;
}
}
private void updateShape() {
if (mShapeState.mShape != null) {
final Rect r = getBounds();
final int w = r.width();
final int h = r.height();
mShapeState.mShape.resize(w, h);
if (mShapeState.mShaderFactory != null) {
mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h));
}
}
invalidateSelf();
}
@Override
public void getOutline(Outline outline) {
if (mShapeState.mShape != null) {
mShapeState.mShape.getOutline(outline);
outline.setAlpha(getAlpha() / 255.0f);
}
}
@Override
public ConstantState getConstantState() {
mShapeState.mChangingConfigurations = getChangingConfigurations();
return mShapeState;
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mShapeState = new ShapeState(mShapeState);
updateLocalState();
mMutated = true;
}
return this;
}
/**
* @hide
*/
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
/**
* Defines the intrinsic properties of this ShapeDrawable's Shape.
*/
static final class ShapeState extends ConstantState {
final @NonNull Paint mPaint;
@Config int mChangingConfigurations;
int[] mThemeAttrs;
Shape mShape;
ColorStateList mTint;
BlendMode mBlendMode = DEFAULT_BLEND_MODE;
Rect mPadding;
int mIntrinsicWidth;
int mIntrinsicHeight;
int mAlpha = 255;
ShaderFactory mShaderFactory;
/**
* Constructs a new ShapeState.
*/
ShapeState() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
/**
* Constructs a new ShapeState that contains a deep copy of the
* specified ShapeState.
*
* @param orig the state to create a deep copy of
*/
ShapeState(@NonNull ShapeState orig) {
mChangingConfigurations = orig.mChangingConfigurations;
mPaint = new Paint(orig.mPaint);
mThemeAttrs = orig.mThemeAttrs;
if (orig.mShape != null) {
try {
mShape = orig.mShape.clone();
} catch (CloneNotSupportedException e) {
// Well, at least we tried.
mShape = orig.mShape;
}
}
mTint = orig.mTint;
mBlendMode = orig.mBlendMode;
if (orig.mPadding != null) {
mPadding = new Rect(orig.mPadding);
}
mIntrinsicWidth = orig.mIntrinsicWidth;
mIntrinsicHeight = orig.mIntrinsicHeight;
mAlpha = orig.mAlpha;
// We don't have any way to clone a shader factory, so hopefully
// this class doesn't contain any local state.
mShaderFactory = orig.mShaderFactory;
}
@Override
public boolean canApplyTheme() {
return mThemeAttrs != null
|| (mTint != null && mTint.canApplyTheme());
}
@Override
public Drawable newDrawable() {
return new ShapeDrawable(new ShapeState(this), null);
}
@Override
public Drawable newDrawable(Resources res) {
return new ShapeDrawable(new ShapeState(this), res);
}
@Override
public @Config int getChangingConfigurations() {
return mChangingConfigurations
| (mTint != null ? mTint.getChangingConfigurations() : 0);
}
}
/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
private ShapeDrawable(ShapeState state, Resources res) {
mShapeState = state;
updateLocalState();
}
/**
* 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() {
mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint,
mShapeState.mBlendMode);
}
/**
* Base class defines a factory object that is called each time the drawable
* is resized (has a new width or height). Its resize() method returns a
* corresponding shader, or null. Implement this class if you'd like your
* ShapeDrawable to use a special {@link android.graphics.Shader}, such as a
* {@link android.graphics.LinearGradient}.
*/
public static abstract class ShaderFactory {
/**
* Returns the Shader to be drawn when a Drawable is drawn. The
* dimensions of the Drawable are passed because they may be needed to
* adjust how the Shader is configured for drawing. This is called by
* ShapeDrawable.setShape().
*
* @param width the width of the Drawable being drawn
* @param height the heigh of the Drawable being drawn
* @return the Shader to be drawn
*/
public abstract Shader resize(int width, int height);
}
}
LayerDrawable
/*
* Copyright (C) 2006 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* A Drawable that manages an array of other Drawables. These are drawn in array
* order, so the element with the largest index will be drawn on top.
* <p>
* It can be defined in an XML file with the <code><layer-list></code> element.
* Each Drawable in the layer is defined in a nested <code><item></code>.
* <p>
* For more information, see the guide to
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
*
* @attr ref android.R.styleable#LayerDrawable_paddingMode
* @attr ref android.R.styleable#LayerDrawableItem_left
* @attr ref android.R.styleable#LayerDrawableItem_top
* @attr ref android.R.styleable#LayerDrawableItem_right
* @attr ref android.R.styleable#LayerDrawableItem_bottom
* @attr ref android.R.styleable#LayerDrawableItem_start
* @attr ref android.R.styleable#LayerDrawableItem_end
* @attr ref android.R.styleable#LayerDrawableItem_width
* @attr ref android.R.styleable#LayerDrawableItem_height
* @attr ref android.R.styleable#LayerDrawableItem_gravity
* @attr ref android.R.styleable#LayerDrawableItem_drawable
* @attr ref android.R.styleable#LayerDrawableItem_id
*/
public class LayerDrawable extends Drawable implements Drawable.Callback {
private static final String LOG_TAG = "LayerDrawable";
/**
* Padding mode used to nest each layer inside the padding of the previous
* layer.
*
* @see #setPaddingMode(int)
*/
public static final int PADDING_MODE_NEST = 0;
/**
* Padding mode used to stack each layer directly atop the previous layer.
*
* @see #setPaddingMode(int)
*/
public static final int PADDING_MODE_STACK = 1;
/**
* Value used for undefined start and end insets.
*
* @see #getLayerInsetStart(int)
* @see #getLayerInsetEnd(int)
*/
public static final int INSET_UNDEFINED = Integer.MIN_VALUE;
@NonNull
@UnsupportedAppUsage
LayerState mLayerState;
private int[] mPaddingL;
private int[] mPaddingT;
private int[] mPaddingR;
private int[] mPaddingB;
private final Rect mTmpRect = new Rect();
private final Rect mTmpOutRect = new Rect();
private final Rect mTmpContainer = new Rect();
private Rect mHotspotBounds;
private boolean mMutated;
private boolean mSuspendChildInvalidation;
private boolean mChildRequestedInvalidation;
/**
* Creates a new layer drawable with the list of specified layers.
*
* @param layers a list of drawables to use as layers in this new drawable,
* must be non-null
*/
public LayerDrawable(@NonNull Drawable[] layers) {
this(layers, null);
}
/**
* Creates a new layer drawable with the specified list of layers and the
* specified constant state.
*
* @param layers The list of layers to add to this drawable.
* @param state The constant drawable state.
*/
LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) {
this(state, null);
if (layers == null) {
throw new IllegalArgumentException("layers must be non-null");
}
final int length = layers.length;
final ChildDrawable[] r = new ChildDrawable[length];
for (int i = 0; i < length; i++) {
r[i] = new ChildDrawable(mLayerState.mDensity);
Drawable child = layers[i];
r[i].mDrawable = child;
if (child != null) {
child.setCallback(this);
mLayerState.mChildrenChangingConfigurations |= child.getChangingConfigurations();
}
}
mLayerState.mNumChildren = length;
mLayerState.mChildren = r;
ensurePadding();
refreshPadding();
}
LayerDrawable() {
this((LayerState) null, null);
}
/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
if (mLayerState.mNumChildren > 0) {
ensurePadding();
refreshPadding();
}
}
LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
return new LayerState(state, this, res);
}
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
// The density may have changed since the last update. This will
// apply scaling to any existing constant state properties.
final LayerState state = mLayerState;
final int density = Drawable.resolveDensity(r, 0);
state.setDensity(density);
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
updateStateFromTypedArray(a);
a.recycle();
final ChildDrawable[] array = state.mChildren;
final int N = state.mNumChildren;
for (int i = 0; i < N; i++) {
final ChildDrawable layer = array[i];
layer.setDensity(density);
}
inflateLayers(r, parser, attrs, theme);
ensurePadding();
refreshPadding();
}
@Override
public void applyTheme(@NonNull Theme t) {
super.applyTheme(t);
final LayerState state = mLayerState;
final int density = Drawable.resolveDensity(t.getResources(), 0);
state.setDensity(density);
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(
state.mThemeAttrs, R.styleable.LayerDrawable);
updateStateFromTypedArray(a);
a.recycle();
}
final ChildDrawable[] array = state.mChildren;
final int N = state.mNumChildren;
for (int i = 0; i < N; i++) {
final ChildDrawable layer = array[i];
layer.setDensity(density);
if (layer.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(
layer.mThemeAttrs, R.styleable.LayerDrawableItem);
updateLayerFromTypedArray(layer, a);
a.recycle();
}
final Drawable d = layer.mDrawable;
if (d != null && d.canApplyTheme()) {
d.applyTheme(t);
// Update cached mask of child changing configurations.
state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
}
}
}
/**
* Inflates child layers using the specified parser.
*/
private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final LayerState state = mLayerState;
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
final ChildDrawable layer = new ChildDrawable(state.mDensity);
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
updateLayerFromTypedArray(layer, a);
a.recycle();
// If the layer doesn't have a drawable or unresolved theme
// attribute for a drawable, attempt to parse one from the child
// element. If multiple child elements exist, we'll only use the
// first one.
if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(parser.getPositionDescription()
+ ": <item> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
// We found a child drawable. Take ownership.
layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
layer.mDrawable.setCallback(this);
state.mChildrenChangingConfigurations |=
layer.mDrawable.getChangingConfigurations();
}
addLayer(layer);
}
}
/**
* Initializes the constant state from the values in the typed array.
*/
private void updateStateFromTypedArray(@NonNull TypedArray a) {
final LayerState state = mLayerState;
// Account for any configuration changes.
state.mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
final int attr = a.getIndex(i);
switch (attr) {
case R.styleable.LayerDrawable_opacity:
state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride);
break;
case R.styleable.LayerDrawable_paddingTop:
state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop);
break;
case R.styleable.LayerDrawable_paddingBottom:
state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom);
break;
case R.styleable.LayerDrawable_paddingLeft:
state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft);
break;
case R.styleable.LayerDrawable_paddingRight:
state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight);
break;
case R.styleable.LayerDrawable_paddingStart:
state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart);
break;
case R.styleable.LayerDrawable_paddingEnd:
state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd);
break;
case R.styleable.LayerDrawable_autoMirrored:
state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored);
break;
case R.styleable.LayerDrawable_paddingMode:
state.mPaddingMode = a.getInteger(attr, state.mPaddingMode);
break;
}
}
}
private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
final LayerState state = mLayerState;
// Account for any configuration changes.
state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
layer.mThemeAttrs = a.extractThemeAttrs();
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
final int attr = a.getIndex(i);
switch (attr) {
case R.styleable.LayerDrawableItem_left:
layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL);
break;
case R.styleable.LayerDrawableItem_top:
layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT);
break;
case R.styleable.LayerDrawableItem_right:
layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR);
break;
case R.styleable.LayerDrawableItem_bottom:
layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB);
break;
case R.styleable.LayerDrawableItem_start:
layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS);
break;
case R.styleable.LayerDrawableItem_end:
layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE);
break;
case R.styleable.LayerDrawableItem_width:
layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth);
break;
case R.styleable.LayerDrawableItem_height:
layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight);
break;
case R.styleable.LayerDrawableItem_gravity:
layer.mGravity = a.getInteger(attr, layer.mGravity);
break;
case R.styleable.LayerDrawableItem_id:
layer.mId = a.getResourceId(attr, layer.mId);
break;
}
}
final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
if (dr != null) {
if (layer.mDrawable != null) {
// It's possible that a drawable was already set, in which case
// we should clear the callback. We may have also integrated the
// drawable's changing configurations, but we don't have enough
// information to revert that change.
layer.mDrawable.setCallback(null);
}
// Take ownership of the new drawable.
layer.mDrawable = dr;
layer.mDrawable.setCallback(this);
state.mChildrenChangingConfigurations |=
layer.mDrawable.getChangingConfigurations();
}
}
@Override
public boolean canApplyTheme() {
return mLayerState.canApplyTheme() || super.canApplyTheme();
}
/**
* @hide
*/
@Override
public boolean isProjected() {
if (super.isProjected()) {
return true;
}
final ChildDrawable[] layers = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
Drawable childDrawable = layers[i].mDrawable;
if (childDrawable != null && childDrawable.isProjected()) {
return true;
}
}
return false;
}
/**
* Adds a new layer at the end of list of layers and returns its index.
*
* @param layer The layer to add.
* @return The index of the layer.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int addLayer(@NonNull ChildDrawable layer) {
final LayerState st = mLayerState;
final int N = st.mChildren != null ? st.mChildren.length : 0;
final int i = st.mNumChildren;
if (i >= N) {
final ChildDrawable[] nu = new ChildDrawable[N + 10];
if (i > 0) {
System.arraycopy(st.mChildren, 0, nu, 0, i);
}
st.mChildren = nu;
}
st.mChildren[i] = layer;
st.mNumChildren++;
st.invalidateCache();
return i;
}
/**
* Add a new layer to this drawable. The new layer is identified by an id.
*
* @param dr The drawable to add as a layer.
* @param themeAttrs Theme attributes extracted from the layer.
* @param id The id of the new layer.
* @param left The left padding of the new layer.
* @param top The top padding of the new layer.
* @param right The right padding of the new layer.
* @param bottom The bottom padding of the new layer.
*/
ChildDrawable addLayer(Drawable dr, int[] themeAttrs, int id,
int left, int top, int right, int bottom) {
final ChildDrawable childDrawable = createLayer(dr);
childDrawable.mId = id;
childDrawable.mThemeAttrs = themeAttrs;
childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
childDrawable.mInsetL = left;
childDrawable.mInsetT = top;
childDrawable.mInsetR = right;
childDrawable.mInsetB = bottom;
addLayer(childDrawable);
mLayerState.mChildrenChangingConfigurations |= dr.getChangingConfigurations();
dr.setCallback(this);
return childDrawable;
}
private ChildDrawable createLayer(Drawable dr) {
final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
layer.mDrawable = dr;
return layer;
}
/**
* Adds a new layer containing the specified {@code drawable} to the end of
* the layer list and returns its index.
*
* @param dr The drawable to add as a new layer.
* @return The index of the new layer.
*/
public int addLayer(Drawable dr) {
final ChildDrawable layer = createLayer(dr);
final int index = addLayer(layer);
ensurePadding();
refreshChildPadding(index, layer);
return index;
}
/**
* Looks for a layer with the given ID and returns its {@link Drawable}.
* <p>
* If multiple layers are found for the given ID, returns the
* {@link Drawable} for the matching layer at the highest index.
*
* @param id The layer ID to search for.
* @return The {@link Drawable} for the highest-indexed layer that has the
* given ID, or null if not found.
*/
public Drawable findDrawableByLayerId(int id) {
final ChildDrawable[] layers = mLayerState.mChildren;
for (int i = mLayerState.mNumChildren - 1; i >= 0; i--) {
if (layers[i].mId == id) {
return layers[i].mDrawable;
}
}
return null;
}
/**
* Sets the ID of a layer.
*
* @param index The index of the layer to modify, must be in the range
* {@code 0...getNumberOfLayers()-1}.
* @param id The id to assign to the layer.
*
* @see #getId(int)
* @attr ref android.R.styleable#LayerDrawableItem_id
*/
public void setId(int index, int id) {
mLayerState.mChildren[index].mId = id;
}
/**
* Returns the ID of the specified layer.
*
* @param index The index of the layer, must be in the range
* {@code 0...getNumberOfLayers()-1}.
* @return The id of the layer or {@link android.view.View#NO_ID} if the
* layer has no id.
*
* @see #setId(int, int)
* @attr ref android.R.styleable#LayerDrawableItem_id
*/
public int getId(int index) {
if (index >= mLayerState.mNumChildren) {
throw new IndexOutOfBoundsException();
}
return mLayerState.mChildren[index].mId;
}
/**
* Returns the number of layers contained within this layer drawable.
*
* @return The number of layers.
*/
public int getNumberOfLayers() {
return mLayerState.mNumChildren;
}
/**
* Replaces the {@link Drawable} for the layer with the given id.
*
* @param id The layer ID to search for.
* @param drawable The replacement {@link Drawable}.
* @return Whether the {@link Drawable} was replaced (could return false if
* the id was not found).
*/
public boolean setDrawableByLayerId(int id, Drawable drawable) {
final int index = findIndexByLayerId(id);
if (index < 0) {
return false;
}
setDrawable(index, drawable);
return true;
}
/**
* Returns the layer with the specified {@code id}.
* <p>
* If multiple layers have the same ID, returns the layer with the lowest
* index.
*
* @param id The ID of the layer to return.
* @return The index of the layer with the specified ID.
*/
public int findIndexByLayerId(int id) {
final ChildDrawable[] layers = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final ChildDrawable childDrawable = layers[i];
if (childDrawable.mId == id) {
return i;
}
}
return -1;
}
/**
* Sets the drawable for the layer at the specified index.
*
* @param index The index of the layer to modify, must be in the range
* {@code 0...getNumberOfLayers()-1}.
* @param drawable The drawable to set for the layer.
*
* @see #getDrawable(int)
* @attr ref android.R.styleable#LayerDrawableItem_drawable
*/
public void setDrawable(int index, Drawable drawable) {
if (index >= mLayerState.mNumChildren) {
throw new IndexOutOfBoundsException();
}
final ChildDrawable[] layers = mLayerState.mChildren;
final ChildDrawable childDrawable = layers[index];
if (childDrawable.mDrawable != null) {
if (drawable != null) {
final Rect bounds = childDrawable.mDrawable.getBounds();
drawable.setBounds(bounds);
}
childDrawable.mDrawable.setCallback(null);
}
if (drawable != null) {
drawable.setCallback(this);
}
childDrawable.mDrawable = drawable;
mLayerState.invalidateCache();
refreshChildPadding(index, childDrawable);
}
/**
* Returns the drawable for the layer at the specified index.
*
* @param index The index of the layer, must be in the range
* {@code 0...getNumberOfLayers()-1}.
* @return The {@link Drawable} at the specified layer index.
*
* @see #setDrawable(int, Drawable)
* @attr ref android.R.styleable#LayerDrawableItem_drawable
*/
public Drawable getDrawable(int index) {
if (index >= mLayerState.mNumChildren) {
throw new IndexOutOfBoundsException();
}
return mLayerState.mChildren[index].mDrawable;
}
/**
* Sets an explicit size for the specified layer.
* <p>
* <strong>Note:</strong> Setting an explicit layer size changes the
* default layer gravity behavior. See {@link #setLayerGravity(int, int)}
* for more information.
*
* @param index the index of the layer to adjust
* @param w width in pixels, or -1 to use the intrinsic width
* @param h height in pixels, or -1 to use the intrinsic height
* @see #getLayerWidth(int)
* @see #getLayerHeight(int)
* @attr ref android.R.styleable#LayerDrawableItem_width
* @attr ref android.R.styleable#LayerDrawableItem_height
*/
public void setLayerSize(int index, int w, int h) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mWidth = w;
childDrawable.mHeight = h;
}
/**
* @param index the index of the layer to adjust
* @param w width in pixels, or -1 to use the intrinsic width
* @attr ref android.R.styleable#LayerDrawableItem_width
*/
public void setLayerWidth(int index, int w) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mWidth = w;
}
/**
* @param index the index of the drawable to adjust
* @return the explicit width of the layer, or -1 if not specified
* @see #setLayerSize(int, int, int)
* @attr ref android.R.styleable#LayerDrawableItem_width
*/
public int getLayerWidth(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mWidth;
}
/**
* @param index the index of the layer to adjust
* @param h height in pixels, or -1 to use the intrinsic height
* @attr ref android.R.styleable#LayerDrawableItem_height
*/
public void setLayerHeight(int index, int h) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mHeight = h;
}
/**
* @param index the index of the drawable to adjust
* @return the explicit height of the layer, or -1 if not specified
* @see #setLayerSize(int, int, int)
* @attr ref android.R.styleable#LayerDrawableItem_height
*/
public int getLayerHeight(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mHeight;
}
/**
* Sets the gravity used to position or stretch the specified layer within
* its container. Gravity is applied after any layer insets (see
* {@link #setLayerInset(int, int, int, int, int)}) or padding (see
* {@link #setPaddingMode(int)}).
* <p>
* If gravity is specified as {@link Gravity#NO_GRAVITY}, the default
* behavior depends on whether an explicit width or height has been set
* (see {@link #setLayerSize(int, int, int)}), If a dimension is not set,
* gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or
* {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction
* defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}.
*
* @param index the index of the drawable to adjust
* @param gravity the gravity to set for the layer
*
* @see #getLayerGravity(int)
* @attr ref android.R.styleable#LayerDrawableItem_gravity
*/
public void setLayerGravity(int index, int gravity) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mGravity = gravity;
}
/**
* @param index the index of the layer
* @return the gravity used to position or stretch the specified layer
* within its container
*
* @see #setLayerGravity(int, int)
* @attr ref android.R.styleable#LayerDrawableItem_gravity
*/
public int getLayerGravity(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mGravity;
}
/**
* Specifies the insets in pixels for the drawable at the specified index.
*
* @param index the index of the drawable to adjust
* @param l number of pixels to add to the left bound
* @param t number of pixels to add to the top bound
* @param r number of pixels to subtract from the right bound
* @param b number of pixels to subtract from the bottom bound
*
* @attr ref android.R.styleable#LayerDrawableItem_left
* @attr ref android.R.styleable#LayerDrawableItem_top
* @attr ref android.R.styleable#LayerDrawableItem_right
* @attr ref android.R.styleable#LayerDrawableItem_bottom
*/
public void setLayerInset(int index, int l, int t, int r, int b) {
setLayerInsetInternal(index, l, t, r, b, INSET_UNDEFINED, INSET_UNDEFINED);
}
/**
* Specifies the relative insets in pixels for the drawable at the
* specified index.
*
* @param index the index of the layer to adjust
* @param s number of pixels to inset from the start bound
* @param t number of pixels to inset from the top bound
* @param e number of pixels to inset from the end bound
* @param b number of pixels to inset from the bottom bound
*
* @attr ref android.R.styleable#LayerDrawableItem_start
* @attr ref android.R.styleable#LayerDrawableItem_top
* @attr ref android.R.styleable#LayerDrawableItem_end
* @attr ref android.R.styleable#LayerDrawableItem_bottom
*/
public void setLayerInsetRelative(int index, int s, int t, int e, int b) {
setLayerInsetInternal(index, 0, t, 0, b, s, e);
}
/**
* @param index the index of the layer to adjust
* @param l number of pixels to inset from the left bound
* @attr ref android.R.styleable#LayerDrawableItem_left
*/
public void setLayerInsetLeft(int index, int l) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetL = l;
}
/**
* @param index the index of the layer
* @return number of pixels to inset from the left bound
* @attr ref android.R.styleable#LayerDrawableItem_left
*/
public int getLayerInsetLeft(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mInsetL;
}
/**
* @param index the index of the layer to adjust
* @param r number of pixels to inset from the right bound
* @attr ref android.R.styleable#LayerDrawableItem_right
*/
public void setLayerInsetRight(int index, int r) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetR = r;
}
/**
* @param index the index of the layer
* @return number of pixels to inset from the right bound
* @attr ref android.R.styleable#LayerDrawableItem_right
*/
public int getLayerInsetRight(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mInsetR;
}
/**
* @param index the index of the layer to adjust
* @param t number of pixels to inset from the top bound
* @attr ref android.R.styleable#LayerDrawableItem_top
*/
public void setLayerInsetTop(int index, int t) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetT = t;
}
/**
* @param index the index of the layer
* @return number of pixels to inset from the top bound
* @attr ref android.R.styleable#LayerDrawableItem_top
*/
public int getLayerInsetTop(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mInsetT;
}
/**
* @param index the index of the layer to adjust
* @param b number of pixels to inset from the bottom bound
* @attr ref android.R.styleable#LayerDrawableItem_bottom
*/
public void setLayerInsetBottom(int index, int b) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetB = b;
}
/**
* @param index the index of the layer
* @return number of pixels to inset from the bottom bound
* @attr ref android.R.styleable#LayerDrawableItem_bottom
*/
public int getLayerInsetBottom(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mInsetB;
}
/**
* @param index the index of the layer to adjust
* @param s number of pixels to inset from the start bound
* @attr ref android.R.styleable#LayerDrawableItem_start
*/
public void setLayerInsetStart(int index, int s) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetS = s;
}
/**
* @param index the index of the layer
* @return the number of pixels to inset from the start bound, or
* {@link #INSET_UNDEFINED} if not specified
* @attr ref android.R.styleable#LayerDrawableItem_start
*/
public int getLayerInsetStart(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mInsetS;
}
/**
* @param index the index of the layer to adjust
* @param e number of pixels to inset from the end bound, or
* {@link #INSET_UNDEFINED} if not specified
* @attr ref android.R.styleable#LayerDrawableItem_end
*/
public void setLayerInsetEnd(int index, int e) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetE = e;
}
/**
* @param index the index of the layer
* @return number of pixels to inset from the end bound
* @attr ref android.R.styleable#LayerDrawableItem_end
*/
public int getLayerInsetEnd(int index) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
return childDrawable.mInsetE;
}
private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) {
final ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetL = l;
childDrawable.mInsetT = t;
childDrawable.mInsetR = r;
childDrawable.mInsetB = b;
childDrawable.mInsetS = s;
childDrawable.mInsetE = e;
}
/**
* Specifies how layer padding should affect the bounds of subsequent
* layers. The default value is {@link #PADDING_MODE_NEST}.
*
* @param mode padding mode, one of:
* <ul>
* <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
* padding of the previous layer
* <li>{@link #PADDING_MODE_STACK} to stack each layer directly
* atop the previous layer
* </ul>
*
* @see #getPaddingMode()
* @attr ref android.R.styleable#LayerDrawable_paddingMode
*/
public void setPaddingMode(int mode) {
if (mLayerState.mPaddingMode != mode) {
mLayerState.mPaddingMode = mode;
}
}
/**
* @return the current padding mode
*
* @see #setPaddingMode(int)
* @attr ref android.R.styleable#LayerDrawable_paddingMode
*/
public int getPaddingMode() {
return mLayerState.mPaddingMode;
}
/**
* Temporarily suspends child invalidation.
*
* @see #resumeChildInvalidation()
*/
private void suspendChildInvalidation() {
mSuspendChildInvalidation = true;
}
/**
* Resumes child invalidation after suspension, immediately performing an
* invalidation if one was requested by a child during suspension.
*
* @see #suspendChildInvalidation()
*/
private void resumeChildInvalidation() {
mSuspendChildInvalidation = false;
if (mChildRequestedInvalidation) {
mChildRequestedInvalidation = false;
invalidateSelf();
}
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
if (mSuspendChildInvalidation) {
mChildRequestedInvalidation = true;
} else {
// This may have been called as the result of a tint changing, in
// which case we may need to refresh the cached statefulness or
// opacity.
mLayerState.invalidateCache();
invalidateSelf();
}
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
unscheduleSelf(what);
}
@Override
public void draw(Canvas canvas) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.draw(canvas);
}
}
}
@Override
public @Config int getChangingConfigurations() {
return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
}
@Override
public boolean getPadding(Rect padding) {
final LayerState layerState = mLayerState;
if (layerState.mPaddingMode == PADDING_MODE_NEST) {
computeNestedPadding(padding);
} else {
computeStackedPadding(padding);
}
final int paddingT = layerState.mPaddingTop;
final int paddingB = layerState.mPaddingBottom;
// Resolve padding for RTL. Relative padding overrides absolute
// padding.
final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
final int paddingRtlL = isLayoutRtl ? layerState.mPaddingEnd : layerState.mPaddingStart;
final int paddingRtlR = isLayoutRtl ? layerState.mPaddingStart : layerState.mPaddingEnd;
final int paddingL = paddingRtlL >= 0 ? paddingRtlL : layerState.mPaddingLeft;
final int paddingR = paddingRtlR >= 0 ? paddingRtlR : layerState.mPaddingRight;
// If padding was explicitly specified (e.g. not -1) then override the
// computed padding in that dimension.
if (paddingL >= 0) {
padding.left = paddingL;
}
if (paddingT >= 0) {
padding.top = paddingT;
}
if (paddingR >= 0) {
padding.right = paddingR;
}
if (paddingB >= 0) {
padding.bottom = paddingB;
}
return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
}
/**
* Sets the absolute padding.
* <p>
* If padding in a dimension is specified as {@code -1}, the resolved
* padding will use the value computed according to the padding mode (see
* {@link #setPaddingMode(int)}).
* <p>
* Calling this method clears any relative padding values previously set
* using {@link #setPaddingRelative(int, int, int, int)}.
*
* @param left the left padding in pixels, or -1 to use computed padding
* @param top the top padding in pixels, or -1 to use computed padding
* @param right the right padding in pixels, or -1 to use computed padding
* @param bottom the bottom padding in pixels, or -1 to use computed
* padding
* @attr ref android.R.styleable#LayerDrawable_paddingLeft
* @attr ref android.R.styleable#LayerDrawable_paddingTop
* @attr ref android.R.styleable#LayerDrawable_paddingRight
* @attr ref android.R.styleable#LayerDrawable_paddingBottom
* @see #setPaddingRelative(int, int, int, int)
*/
public void setPadding(int left, int top, int right, int bottom) {
final LayerState layerState = mLayerState;
layerState.mPaddingLeft = left;
layerState.mPaddingTop = top;
layerState.mPaddingRight = right;
layerState.mPaddingBottom = bottom;
// Clear relative padding values.
layerState.mPaddingStart = -1;
layerState.mPaddingEnd = -1;
}
/**
* Sets the relative padding.
* <p>
* If padding in a dimension is specified as {@code -1}, the resolved
* padding will use the value computed according to the padding mode (see
* {@link #setPaddingMode(int)}).
* <p>
* Calling this method clears any absolute padding values previously set
* using {@link #setPadding(int, int, int, int)}.
*
* @param start the start padding in pixels, or -1 to use computed padding
* @param top the top padding in pixels, or -1 to use computed padding
* @param end the end padding in pixels, or -1 to use computed padding
* @param bottom the bottom padding in pixels, or -1 to use computed
* padding
* @attr ref android.R.styleable#LayerDrawable_paddingStart
* @attr ref android.R.styleable#LayerDrawable_paddingTop
* @attr ref android.R.styleable#LayerDrawable_paddingEnd
* @attr ref android.R.styleable#LayerDrawable_paddingBottom
* @see #setPadding(int, int, int, int)
*/
public void setPaddingRelative(int start, int top, int end, int bottom) {
final LayerState layerState = mLayerState;
layerState.mPaddingStart = start;
layerState.mPaddingTop = top;
layerState.mPaddingEnd = end;
layerState.mPaddingBottom = bottom;
// Clear absolute padding values.
layerState.mPaddingLeft = -1;
layerState.mPaddingRight = -1;
}
/**
* Returns the left padding in pixels.
* <p>
* A return value of {@code -1} means there is no explicit padding set for
* this dimension. As a result, the value for this dimension returned by
* {@link #getPadding(Rect)} will be computed from the child layers
* according to the padding mode (see {@link #getPaddingMode()}.
*
* @return the left padding in pixels, or -1 if not explicitly specified
* @see #setPadding(int, int, int, int)
* @see #getPadding(Rect)
*/
public int getLeftPadding() {
return mLayerState.mPaddingLeft;
}
/**
* Returns the right padding in pixels.
* <p>
* A return value of {@code -1} means there is no explicit padding set for
* this dimension. As a result, the value for this dimension returned by
* {@link #getPadding(Rect)} will be computed from the child layers
* according to the padding mode (see {@link #getPaddingMode()}.
*
* @return the right padding in pixels, or -1 if not explicitly specified
* @see #setPadding(int, int, int, int)
* @see #getPadding(Rect)
*/
public int getRightPadding() {
return mLayerState.mPaddingRight;
}
/**
* Returns the start padding in pixels.
* <p>
* A return value of {@code -1} means there is no explicit padding set for
* this dimension. As a result, the value for this dimension returned by
* {@link #getPadding(Rect)} will be computed from the child layers
* according to the padding mode (see {@link #getPaddingMode()}.
*
* @return the start padding in pixels, or -1 if not explicitly specified
* @see #setPaddingRelative(int, int, int, int)
* @see #getPadding(Rect)
*/
public int getStartPadding() {
return mLayerState.mPaddingStart;
}
/**
* Returns the end padding in pixels.
* <p>
* A return value of {@code -1} means there is no explicit padding set for
* this dimension. As a result, the value for this dimension returned by
* {@link #getPadding(Rect)} will be computed from the child layers
* according to the padding mode (see {@link #getPaddingMode()}.
*
* @return the end padding in pixels, or -1 if not explicitly specified
* @see #setPaddingRelative(int, int, int, int)
* @see #getPadding(Rect)
*/
public int getEndPadding() {
return mLayerState.mPaddingEnd;
}
/**
* Returns the top padding in pixels.
* <p>
* A return value of {@code -1} means there is no explicit padding set for
* this dimension. As a result, the value for this dimension returned by
* {@link #getPadding(Rect)} will be computed from the child layers
* according to the padding mode (see {@link #getPaddingMode()}.
*
* @return the top padding in pixels, or -1 if not explicitly specified
* @see #setPadding(int, int, int, int)
* @see #setPaddingRelative(int, int, int, int)
* @see #getPadding(Rect)
*/
public int getTopPadding() {
return mLayerState.mPaddingTop;
}
/**
* Returns the bottom padding in pixels.
* <p>
* A return value of {@code -1} means there is no explicit padding set for
* this dimension. As a result, the value for this dimension returned by
* {@link #getPadding(Rect)} will be computed from the child layers
* according to the padding mode (see {@link #getPaddingMode()}.
*
* @return the bottom padding in pixels, or -1 if not explicitly specified
* @see #setPadding(int, int, int, int)
* @see #setPaddingRelative(int, int, int, int)
* @see #getPadding(Rect)
*/
public int getBottomPadding() {
return mLayerState.mPaddingBottom;
}
private void computeNestedPadding(Rect padding) {
padding.left = 0;
padding.top = 0;
padding.right = 0;
padding.bottom = 0;
// Add all the padding.
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
refreshChildPadding(i, array[i]);
padding.left += mPaddingL[i];
padding.top += mPaddingT[i];
padding.right += mPaddingR[i];
padding.bottom += mPaddingB[i];
}
}
private void computeStackedPadding(Rect padding) {
padding.left = 0;
padding.top = 0;
padding.right = 0;
padding.bottom = 0;
// Take the max padding.
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
refreshChildPadding(i, array[i]);
padding.left = Math.max(padding.left, mPaddingL[i]);
padding.top = Math.max(padding.top, mPaddingT[i]);
padding.right = Math.max(padding.right, mPaddingR[i]);
padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
}
}
/**
* Populates <code>outline</code> with the first available (non-empty) layer outline.
*
* @param outline Outline in which to place the first available layer outline
*/
@Override
public void getOutline(@NonNull Outline outline) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.getOutline(outline);
if (!outline.isEmpty()) {
return;
}
}
}
}
@Override
public void setHotspot(float x, float y) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setHotspot(x, y);
}
}
}
@Override
public void setHotspotBounds(int left, int top, int right, int bottom) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setHotspotBounds(left, top, right, bottom);
}
}
if (mHotspotBounds == null) {
mHotspotBounds = new Rect(left, top, right, bottom);
} else {
mHotspotBounds.set(left, top, right, bottom);
}
}
@Override
public void getHotspotBounds(Rect outRect) {
if (mHotspotBounds != null) {
outRect.set(mHotspotBounds);
} else {
super.getHotspotBounds(outRect);
}
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
final boolean changed = super.setVisible(visible, restart);
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setVisible(visible, restart);
}
}
return changed;
}
@Override
public void setDither(boolean dither) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setDither(dither);
}
}
}
@Override
public void setAlpha(int alpha) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setAlpha(alpha);
}
}
}
@Override
public int getAlpha() {
final Drawable dr = getFirstNonNullDrawable();
if (dr != null) {
return dr.getAlpha();
} else {
return super.getAlpha();
}
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setColorFilter(colorFilter);
}
}
}
@Override
public void setTintList(ColorStateList tint) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setTintList(tint);
}
}
}
@Override
public void setTintBlendMode(@NonNull BlendMode blendMode) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setTintBlendMode(blendMode);
}
}
}
private Drawable getFirstNonNullDrawable() {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
return dr;
}
}
return null;
}
/**
* Sets the opacity of this drawable directly instead of collecting the
* states from the layers.
*
* @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
* PixelFormat.UNKNOWN} for the default behavior
* @see PixelFormat#UNKNOWN
* @see PixelFormat#TRANSLUCENT
* @see PixelFormat#TRANSPARENT
* @see PixelFormat#OPAQUE
*/
public void setOpacity(int opacity) {
mLayerState.mOpacityOverride = opacity;
}
@Override
public int getOpacity() {
if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
return mLayerState.mOpacityOverride;
}
return mLayerState.getOpacity();
}
@Override
public void setAutoMirrored(boolean mirrored) {
mLayerState.mAutoMirrored = mirrored;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.setAutoMirrored(mirrored);
}
}
}
@Override
public boolean isAutoMirrored() {
return mLayerState.mAutoMirrored;
}
@Override
public void jumpToCurrentState() {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.jumpToCurrentState();
}
}
}
@Override
public boolean isStateful() {
return mLayerState.isStateful();
}
@Override
public boolean hasFocusStateSpecified() {
return mLayerState.hasFocusStateSpecified();
}
@Override
protected boolean onStateChange(int[] state) {
boolean changed = false;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null && dr.isStateful() && dr.setState(state)) {
refreshChildPadding(i, array[i]);
changed = true;
}
}
if (changed) {
updateLayerBounds(getBounds());
}
return changed;
}
@Override
protected boolean onLevelChange(int level) {
boolean changed = false;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null && dr.setLevel(level)) {
refreshChildPadding(i, array[i]);
changed = true;
}
}
if (changed) {
updateLayerBounds(getBounds());
}
return changed;
}
@Override
protected void onBoundsChange(Rect bounds) {
updateLayerBounds(bounds);
}
private void updateLayerBounds(Rect bounds) {
try {
suspendChildInvalidation();
updateLayerBoundsInternal(bounds);
} finally {
resumeChildInvalidation();
}
}
private void updateLayerBoundsInternal(Rect bounds) {
int paddingL = 0;
int paddingT = 0;
int paddingR = 0;
int paddingB = 0;
final Rect outRect = mTmpOutRect;
final int layoutDirection = getLayoutDirection();
final boolean isLayoutRtl = layoutDirection == LayoutDirection.RTL;
final boolean isPaddingNested = mLayerState.mPaddingMode == PADDING_MODE_NEST;
final ChildDrawable[] array = mLayerState.mChildren;
for (int i = 0, count = mLayerState.mNumChildren; i < count; i++) {
final ChildDrawable r = array[i];
final Drawable d = r.mDrawable;
if (d == null) {
continue;
}
final int insetT = r.mInsetT;
final int insetB = r.mInsetB;
// Resolve insets for RTL. Relative insets override absolute
// insets.
final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
// Establish containing region based on aggregate padding and
// requested insets for the current layer.
final Rect container = mTmpContainer;
container.set(bounds.left + insetL + paddingL, bounds.top + insetT + paddingT,
bounds.right - insetR - paddingR, bounds.bottom - insetB - paddingB);
// Compute a reasonable default gravity based on the intrinsic and
// explicit dimensions, if specified.
final int intrinsicW = d.getIntrinsicWidth();
final int intrinsicH = d.getIntrinsicHeight();
final int layerW = r.mWidth;
final int layerH = r.mHeight;
final int gravity = resolveGravity(r.mGravity, layerW, layerH, intrinsicW, intrinsicH);
// Explicit dimensions override intrinsic dimensions.
final int resolvedW = layerW < 0 ? intrinsicW : layerW;
final int resolvedH = layerH < 0 ? intrinsicH : layerH;
Gravity.apply(gravity, resolvedW, resolvedH, container, outRect, layoutDirection);
d.setBounds(outRect);
if (isPaddingNested) {
paddingL += mPaddingL[i];
paddingR += mPaddingR[i];
paddingT += mPaddingT[i];
paddingB += mPaddingB[i];
}
}
}
/**
* Resolves layer gravity given explicit gravity and dimensions.
* <p>
* If the client hasn't specified a gravity but has specified an explicit
* dimension, defaults to START or TOP. Otherwise, defaults to FILL to
* preserve legacy behavior.
*
* @param gravity layer gravity
* @param width width of the layer if set, -1 otherwise
* @param height height of the layer if set, -1 otherwise
* @return the default gravity for the layer
*/
private static int resolveGravity(int gravity, int width, int height,
int intrinsicWidth, int intrinsicHeight) {
if (!Gravity.isHorizontal(gravity)) {
if (width < 0) {
gravity |= Gravity.FILL_HORIZONTAL;
} else {
gravity |= Gravity.START;
}
}
if (!Gravity.isVertical(gravity)) {
if (height < 0) {
gravity |= Gravity.FILL_VERTICAL;
} else {
gravity |= Gravity.TOP;
}
}
// If a dimension if not specified, either implicitly or explicitly,
// force FILL for that dimension's gravity. This ensures that colors
// are handled correctly and ensures backward compatibility.
if (width < 0 && intrinsicWidth < 0) {
gravity |= Gravity.FILL_HORIZONTAL;
}
if (height < 0 && intrinsicHeight < 0) {
gravity |= Gravity.FILL_VERTICAL;
}
return gravity;
}
@Override
public int getIntrinsicWidth() {
int width = -1;
int padL = 0;
int padR = 0;
final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final ChildDrawable r = array[i];
if (r.mDrawable == null) {
continue;
}
// Take the resolved layout direction into account. If start / end
// padding are defined, they will be resolved (hence overriding) to
// left / right or right / left depending on the resolved layout
// direction. If start / end padding are not defined, use the
// left / right ones.
final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
// Don't apply padding and insets for children that don't have
// an intrinsic dimension.
final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth;
final int w = minWidth < 0 ? -1 : minWidth + insetL + insetR + padL + padR;
if (w > width) {
width = w;
}
if (nest) {
padL += mPaddingL[i];
padR += mPaddingR[i];
}
}
return width;
}
@Override
public int getIntrinsicHeight() {
int height = -1;
int padT = 0;
int padB = 0;
final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final ChildDrawable r = array[i];
if (r.mDrawable == null) {
continue;
}
// Don't apply padding and insets for children that don't have
// an intrinsic dimension.
final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight;
final int h = minHeight < 0 ? -1 : minHeight + r.mInsetT + r.mInsetB + padT + padB;
if (h > height) {
height = h;
}
if (nest) {
padT += mPaddingT[i];
padB += mPaddingB[i];
}
}
return height;
}
/**
* Refreshes the cached padding values for the specified child.
*
* @return true if the child's padding has changed
*/
private boolean refreshChildPadding(int i, ChildDrawable r) {
if (r.mDrawable != null) {
final Rect rect = mTmpRect;
r.mDrawable.getPadding(rect);
if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i]
|| rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
mPaddingL[i] = rect.left;
mPaddingT[i] = rect.top;
mPaddingR[i] = rect.right;
mPaddingB[i] = rect.bottom;
return true;
}
}
return false;
}
/**
* Ensures the child padding caches are large enough.
*/
@UnsupportedAppUsage
void ensurePadding() {
final int N = mLayerState.mNumChildren;
if (mPaddingL != null && mPaddingL.length >= N) {
return;
}
mPaddingL = new int[N];
mPaddingT = new int[N];
mPaddingR = new int[N];
mPaddingB = new int[N];
}
void refreshPadding() {
final int N = mLayerState.mNumChildren;
final ChildDrawable[] array = mLayerState.mChildren;
for (int i = 0; i < N; i++) {
refreshChildPadding(i, array[i]);
}
}
@Override
public ConstantState getConstantState() {
if (mLayerState.canConstantState()) {
mLayerState.mChangingConfigurations = getChangingConfigurations();
return mLayerState;
}
return null;
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mLayerState = createConstantState(mLayerState, null);
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.mutate();
}
}
mMutated = true;
}
return this;
}
/**
* @hide
*/
public void clearMutated() {
super.clearMutated();
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.clearMutated();
}
}
mMutated = false;
}
@Override
public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
boolean changed = false;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
changed |= dr.setLayoutDirection(layoutDirection);
}
}
updateLayerBounds(getBounds());
return changed;
}
static class ChildDrawable {
@UnsupportedAppUsage
public Drawable mDrawable;
public int[] mThemeAttrs;
public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
public int mInsetL, mInsetT, mInsetR, mInsetB;
public int mInsetS = INSET_UNDEFINED;
public int mInsetE = INSET_UNDEFINED;
public int mWidth = -1;
public int mHeight = -1;
public int mGravity = Gravity.NO_GRAVITY;
public int mId = View.NO_ID;
ChildDrawable(int density) {
mDensity = density;
}
ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable owner,
@Nullable Resources res) {
final Drawable dr = orig.mDrawable;
final Drawable clone;
if (dr != null) {
final ConstantState cs = dr.getConstantState();
if (cs == null) {
StateListDrawable
LevelListDrawable
/*
* Copyright (C) 2006 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 java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
import android.util.AttributeSet;
/**
* A resource that manages a number of alternate Drawables, each assigned a maximum numerical value.
* Setting the level value of the object with {@link #setLevel(int)} will load the image with the next
* greater or equal value assigned to its max attribute.
* A good example use of
* a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current
* battery level.
* <p>
* It can be defined in an XML file with the <code><level-list></code> element.
* Each Drawable level is defined in a nested <code><item></code>. For example:
* </p>
* <pre>
* <level-list xmlns:android="http://schemas.android.com/apk/res/android">
* <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
* <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
* <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
* <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
* </level-list>
*</pre>
* <p>With this XML saved into the res/drawable/ folder of the project, it can be referenced as
* the drawable for an {@link android.widget.ImageView}. The default image is the first in the list.
* It can then be changed to one of the other levels with
* {@link android.widget.ImageView#setImageLevel(int)}. For more
* information, see the guide to <a
* href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
*
* @attr ref android.R.styleable#LevelListDrawableItem_minLevel
* @attr ref android.R.styleable#LevelListDrawableItem_maxLevel
* @attr ref android.R.styleable#LevelListDrawableItem_drawable
*/
public class LevelListDrawable extends DrawableContainer {
private LevelListState mLevelListState;
private boolean mMutated;
public LevelListDrawable() {
this(null, null);
}
public void addLevel(int low, int high, Drawable drawable) {
if (drawable != null) {
mLevelListState.addLevel(low, high, drawable);
// in case the new state matches our current state...
onLevelChange(getLevel());
}
}
// overrides from Drawable
@Override
protected boolean onLevelChange(int level) {
int idx = mLevelListState.indexOfLevel(level);
if (selectDrawable(idx)) {
return true;
}
return super.onLevelChange(level);
}
@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
updateDensity(r);
inflateChildElements(r, parser, attrs, theme);
}
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
int type;
int low = 0;
final int innerDepth = parser.getDepth() + 1;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
TypedArray a = obtainAttributes(r, theme, attrs,
com.android.internal.R.styleable.LevelListDrawableItem);
low = a.getInt(
com.android.internal.R.styleable.LevelListDrawableItem_minLevel, 0);
int high = a.getInt(
com.android.internal.R.styleable.LevelListDrawableItem_maxLevel, 0);
int drawableRes = a.getResourceId(
com.android.internal.R.styleable.LevelListDrawableItem_drawable, 0);
a.recycle();
if (high < 0) {
throw new XmlPullParserException(parser.getPositionDescription()
+ ": <item> tag requires a 'maxLevel' attribute");
}
Drawable dr;
if (drawableRes != 0) {
dr = r.getDrawable(drawableRes, theme);
} else {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(
parser.getPositionDescription()
+ ": <item> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
}
mLevelListState.addLevel(low, high, dr);
}
onLevelChange(getLevel());
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mLevelListState.mutate();
mMutated = true;
}
return this;
}
@Override
LevelListState cloneConstantState() {
return new LevelListState(mLevelListState, this, null);
}
/**
* @hide
*/
public void clearMutated() {
super.clearMutated();
mMutated = false;
}
private final static class LevelListState extends DrawableContainerState {
private int[] mLows;
private int[] mHighs;
LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) {
super(orig, owner, res);
if (orig != null) {
// Perform a shallow copy and rely on mutate() to deep-copy.
mLows = orig.mLows;
mHighs = orig.mHighs;
} else {
mLows = new int[getCapacity()];
mHighs = new int[getCapacity()];
}
}
private void mutate() {
mLows = mLows.clone();
mHighs = mHighs.clone();
}
public void addLevel(int low, int high, Drawable drawable) {
int pos = addChild(drawable);
mLows[pos] = low;
mHighs[pos] = high;
}
public int indexOfLevel(int level) {
final int[] lows = mLows;
final int[] highs = mHighs;
final int N = getChildCount();
for (int i = 0; i < N; i++) {
if (level >= lows[i] && level <= highs[i]) {
return i;
}
}
return -1;
}
@Override
public Drawable newDrawable() {
return new LevelListDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new LevelListDrawable(this, res);
}
@Override
public void growArray(int oldSize, int newSize) {
super.growArray(oldSize, newSize);
int[] newInts = new int[newSize];
System.arraycopy(mLows, 0, newInts, 0, oldSize);
mLows = newInts;
newInts = new int[newSize];
System.arraycopy(mHighs, 0, newInts, 0, oldSize);
mHighs = newInts;
}
}
@Override
protected void setConstantState(@NonNull DrawableContainerState state) {
super.setConstantState(state);
if (state instanceof LevelListState) {
mLevelListState = (LevelListState) state;
}
}
private LevelListDrawable(LevelListState state, Resources res) {
final LevelListState as = new LevelListState(state, this, res);
setConstantState(as);
onLevelChange(getLevel());
}
}
TransitionDrawable
/*
* Copyright (C) 2008 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 android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.SystemClock;
/**
* An extension of LayerDrawables that is intended to cross-fade between
* the first and second layer. To start the transition, call {@link #startTransition(int)}. To
* display just the first layer, call {@link #resetTransition()}.
* <p>
* It can be defined in an XML file with the <code><transition></code> element.
* Each Drawable in the transition is defined in a nested <code><item></code>. For more
* information, see the guide to <a
* href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
*
* @attr ref android.R.styleable#LayerDrawableItem_left
* @attr ref android.R.styleable#LayerDrawableItem_top
* @attr ref android.R.styleable#LayerDrawableItem_right
* @attr ref android.R.styleable#LayerDrawableItem_bottom
* @attr ref android.R.styleable#LayerDrawableItem_drawable
* @attr ref android.R.styleable#LayerDrawableItem_id
*
*/
public class TransitionDrawable extends LayerDrawable implements Drawable.Callback {
/**
* A transition is about to start.
*/
private static final int TRANSITION_STARTING = 0;
/**
* The transition has started and the animation is in progress
*/
private static final int TRANSITION_RUNNING = 1;
/**
* No transition will be applied
*/
private static final int TRANSITION_NONE = 2;
/**
* The current state of the transition. One of {@link #TRANSITION_STARTING},
* {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE}
*/
private int mTransitionState = TRANSITION_NONE;
private boolean mReverse;
private long mStartTimeMillis;
private int mFrom;
@UnsupportedAppUsage
private int mTo;
private int mDuration;
private int mOriginalDuration;
@UnsupportedAppUsage
private int mAlpha = 0;
@UnsupportedAppUsage
private boolean mCrossFade;
/**
* Create a new transition drawable with the specified list of layers. At least
* 2 layers are required for this drawable to work properly.
*/
public TransitionDrawable(Drawable[] layers) {
this(new TransitionState(null, null, null), layers);
}
/**
* Create a new transition drawable with no layer. To work correctly, at least 2
* layers must be added to this drawable.
*
* @see #TransitionDrawable(Drawable[])
*/
TransitionDrawable() {
this(new TransitionState(null, null, null), (Resources) null);
}
private TransitionDrawable(TransitionState state, Resources res) {
super(state, res);
}
private TransitionDrawable(TransitionState state, Drawable[] layers) {
super(layers, state);
}
@Override
LayerState createConstantState(LayerState state, Resources res) {
return new TransitionState((TransitionState) state, this, res);
}
/**
* Begin the second layer on top of the first layer.
*
* @param durationMillis The length of the transition in milliseconds
*/
public void startTransition(int durationMillis) {
mFrom = 0;
mTo = 255;
mAlpha = 0;
mDuration = mOriginalDuration = durationMillis;
mReverse = false;
mTransitionState = TRANSITION_STARTING;
invalidateSelf();
}
/**
* Show the second layer on top of the first layer immediately
*
* @hide
*/
public void showSecondLayer() {
mAlpha = 255;
mReverse = false;
mTransitionState = TRANSITION_NONE;
invalidateSelf();
}
/**
* Show only the first layer.
*/
public void resetTransition() {
mAlpha = 0;
mTransitionState = TRANSITION_NONE;
invalidateSelf();
}
/**
* Reverses the transition, picking up where the transition currently is.
* If the transition is not currently running, this will start the transition
* with the specified duration. If the transition is already running, the last
* known duration will be used.
*
* @param duration The duration to use if no transition is running.
*/
public void reverseTransition(int duration) {
final long time = SystemClock.uptimeMillis();
// Animation is over
if (time - mStartTimeMillis > mDuration) {
if (mTo == 0) {
mFrom = 0;
mTo = 255;
mAlpha = 0;
mReverse = false;
} else {
mFrom = 255;
mTo = 0;
mAlpha = 255;
mReverse = true;
}
mDuration = mOriginalDuration = duration;
mTransitionState = TRANSITION_STARTING;
invalidateSelf();
return;
}
mReverse = !mReverse;
mFrom = mAlpha;
mTo = mReverse ? 0 : 255;
mDuration = (int) (mReverse ? time - mStartTimeMillis :
mOriginalDuration - (time - mStartTimeMillis));
mTransitionState = TRANSITION_STARTING;
}
@Override
public void draw(Canvas canvas) {
boolean done = true;
switch (mTransitionState) {
case TRANSITION_STARTING:
mStartTimeMillis = SystemClock.uptimeMillis();
done = false;
mTransitionState = TRANSITION_RUNNING;
break;
case TRANSITION_RUNNING:
if (mStartTimeMillis >= 0) {
float normalized = (float)
(SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration;
done = normalized >= 1.0f;
normalized = Math.min(normalized, 1.0f);
mAlpha = (int) (mFrom + (mTo - mFrom) * normalized);
}
break;
}
final int alpha = mAlpha;
final boolean crossFade = mCrossFade;
final ChildDrawable[] array = mLayerState.mChildren;
if (done) {
// the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw
// the appropriate drawable[s] and return
if (!crossFade || alpha == 0) {
array[0].mDrawable.draw(canvas);
}
if (alpha == 0xFF) {
array[1].mDrawable.draw(canvas);
}
return;
}
Drawable d;
d = array[0].mDrawable;
if (crossFade) {
d.setAlpha(255 - alpha);
}
d.draw(canvas);
if (crossFade) {
d.setAlpha(0xFF);
}
if (alpha > 0) {
d = array[1].mDrawable;
d.setAlpha(alpha);
d.draw(canvas);
d.setAlpha(0xFF);
}
if (!done) {
invalidateSelf();
}
}
/**
* Enables or disables the cross fade of the drawables. When cross fade
* is disabled, the first drawable is always drawn opaque. With cross
* fade enabled, the first drawable is drawn with the opposite alpha of
* the second drawable. Cross fade is disabled by default.
*
* @param enabled True to enable cross fading, false otherwise.
*/
public void setCrossFadeEnabled(boolean enabled) {
mCrossFade = enabled;
}
/**
* Indicates whether the cross fade is enabled for this transition.
*
* @return True if cross fading is enabled, false otherwise.
*/
public boolean isCrossFadeEnabled() {
return mCrossFade;
}
static class TransitionState extends LayerState {
TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) {
super(orig, owner, res);
}
@Override
public Drawable newDrawable() {
return new TransitionDrawable(this, (Resources) null);
}
@Override
public Drawable newDrawable(Resources res) {
return new TransitionDrawable(this, res);
}
@Override
public @Config int getChangingConfigurations() {
return mChangingConfigurations;
}
}
}
InsetDrawable
/*
* Copyright (C) 2008 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* A Drawable that insets another Drawable by a specified distance or fraction of the content bounds.
* This is used when a View needs a background that is smaller than
* the View's actual bounds.
*
* <p>It can be defined in an XML file with the <code><inset></code> element. For more
* information, see the guide to <a
* href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
*
* @attr ref android.R.styleable#InsetDrawable_visible
* @attr ref android.R.styleable#InsetDrawable_drawable
* @attr ref android.R.styleable#InsetDrawable_insetLeft
* @attr ref android.R.styleable#InsetDrawable_insetRight
* @attr ref android.R.styleable#InsetDrawable_insetTop
* @attr ref android.R.styleable#InsetDrawable_insetBottom
*/
public class InsetDrawable extends DrawableWrapper {
private final Rect mTmpRect = new Rect();
private final Rect mTmpInsetRect = new Rect();
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private InsetState mState;
/**
* No-arg constructor used by drawable inflation.
*/
InsetDrawable() {
this(new InsetState(null, null), null);
}
/**
* Creates a new inset drawable with the specified inset.
*
* @param drawable The drawable to inset.
* @param inset Inset in pixels around the drawable.
*/
public InsetDrawable(@Nullable Drawable drawable, int inset) {
this(drawable, inset, inset, inset, inset);
}
/**
* Creates a new inset drawable with the specified inset.
*
* @param drawable The drawable to inset.
* @param inset Inset in fraction (range: [0, 1)) of the inset content bounds.
*/
public InsetDrawable(@Nullable Drawable drawable, float inset) {
this(drawable, inset, inset, inset, inset);
}
/**
* Creates a new inset drawable with the specified insets in pixels.
*
* @param drawable The drawable to inset.
* @param insetLeft Left inset in pixels.
* @param insetTop Top inset in pixels.
* @param insetRight Right inset in pixels.
* @param insetBottom Bottom inset in pixels.
*/
public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop,
int insetRight, int insetBottom) {
this(new InsetState(null, null), null);
mState.mInsetLeft = new InsetValue(0f, insetLeft);
mState.mInsetTop = new InsetValue(0f, insetTop);
mState.mInsetRight = new InsetValue(0f, insetRight);
mState.mInsetBottom = new InsetValue(0f, insetBottom);
setDrawable(drawable);
}
/**
* Creates a new inset drawable with the specified insets in fraction of the view bounds.
*
* @param drawable The drawable to inset.
* @param insetLeftFraction Left inset in fraction (range: [0, 1)) of the inset content bounds.
* @param insetTopFraction Top inset in fraction (range: [0, 1)) of the inset content bounds.
* @param insetRightFraction Right inset in fraction (range: [0, 1)) of the inset content bounds.
* @param insetBottomFraction Bottom inset in fraction (range: [0, 1)) of the inset content bounds.
*/
public InsetDrawable(@Nullable Drawable drawable, float insetLeftFraction,
float insetTopFraction, float insetRightFraction, float insetBottomFraction) {
this(new InsetState(null, null), null);
mState.mInsetLeft = new InsetValue(insetLeftFraction, 0);
mState.mInsetTop = new InsetValue(insetTopFraction, 0);
mState.mInsetRight = new InsetValue(insetRightFraction, 0);
mState.mInsetBottom = new InsetValue(insetBottomFraction, 0);
setDrawable(drawable);
}
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
// Inflation will advance the XmlPullParser and AttributeSet.
super.inflate(r, parser, attrs, theme);
updateStateFromTypedArray(a);
verifyRequiredAttributes(a);
a.recycle();
}
@Override
public void applyTheme(@NonNull Theme t) {
super.applyTheme(t);
final InsetState state = mState;
if (state == null) {
return;
}
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
try {
updateStateFromTypedArray(a);
verifyRequiredAttributes(a);
} catch (XmlPullParserException e) {
rethrowAsRuntimeException(e);
} finally {
a.recycle();
}
}
}
private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
// If we're not waiting on a theme, verify required attributes.
if (getDrawable() == null && (mState.mThemeAttrs == null
|| mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
throw new XmlPullParserException(a.getPositionDescription()
+ ": <inset> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
}
private void updateStateFromTypedArray(@NonNull TypedArray a) {
final InsetState 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();
// Inset attribute may be overridden by more specific attributes.
if (a.hasValue(R.styleable.InsetDrawable_inset)) {
final InsetValue inset = getInset(a, R.styleable.InsetDrawable_inset, new InsetValue());
state.mInsetLeft = inset;
state.mInsetTop = inset;
state.mInsetRight = inset;
state.mInsetBottom = inset;
}
state.mInsetLeft = getInset(a, R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
state.mInsetTop = getInset(a, R.styleable.InsetDrawable_insetTop, state.mInsetTop);
state.mInsetRight = getInset(a, R.styleable.InsetDrawable_insetRight, state.mInsetRight);
state.mInsetBottom = getInset(a, R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
}
private InsetValue getInset(@NonNull TypedArray a, int index, InsetValue defaultValue) {
if (a.hasValue(index)) {
TypedValue tv = a.peekValue(index);
if (tv.type == TypedValue.TYPE_FRACTION) {
float f = tv.getFraction(1.0f, 1.0f);
if (f >= 1f) {
throw new IllegalStateException("Fraction cannot be larger than 1");
}
return new InsetValue(f, 0);
} else {
int dimension = a.getDimensionPixelOffset(index, 0);
if (dimension != 0) {
return new InsetValue(0, dimension);
}
}
}
return defaultValue;
}
private void getInsets(Rect out) {
final Rect b = getBounds();
out.left = mState.mInsetLeft.getDimension(b.width());
out.right = mState.mInsetRight.getDimension(b.width());
out.top = mState.mInsetTop.getDimension(b.height());
out.bottom = mState.mInsetBottom.getDimension(b.height());
}
@Override
public boolean getPadding(Rect padding) {
final boolean pad = super.getPadding(padding);
getInsets(mTmpInsetRect);
padding.left += mTmpInsetRect.left;
padding.right += mTmpInsetRect.right;
padding.top += mTmpInsetRect.top;
padding.bottom += mTmpInsetRect.bottom;
return pad || (mTmpInsetRect.left | mTmpInsetRect.right
| mTmpInsetRect.top | mTmpInsetRect.bottom) != 0;
}
@Override
public Insets getOpticalInsets() {
final Insets contentInsets = super.getOpticalInsets();
getInsets(mTmpInsetRect);
return Insets.of(
contentInsets.left + mTmpInsetRect.left,
contentInsets.top + mTmpInsetRect.top,
contentInsets.right + mTmpInsetRect.right,
contentInsets.bottom + mTmpInsetRect.bottom);
}
@Override
public int getOpacity() {
final InsetState state = mState;
final int opacity = getDrawable().getOpacity();
getInsets(mTmpInsetRect);
if (opacity == PixelFormat.OPAQUE &&
(mTmpInsetRect.left > 0 || mTmpInsetRect.top > 0 || mTmpInsetRect.right > 0
|| mTmpInsetRect.bottom > 0)) {
return PixelFormat.TRANSLUCENT;
}
return opacity;
}
@Override
protected void onBoundsChange(Rect bounds) {
final Rect r = mTmpRect;
r.set(bounds);
r.left += mState.mInsetLeft.getDimension(bounds.width());
r.top += mState.mInsetTop.getDimension(bounds.height());
r.right -= mState.mInsetRight.getDimension(bounds.width());
r.bottom -= mState.mInsetBottom.getDimension(bounds.height());
// Apply inset bounds to the wrapped drawable.
super.onBoundsChange(r);
}
@Override
public int getIntrinsicWidth() {
final int childWidth = getDrawable().getIntrinsicWidth();
final float fraction = mState.mInsetLeft.mFraction + mState.mInsetRight.mFraction;
if (childWidth < 0 || fraction >= 1) {
return -1;
}
return (int) (childWidth / (1 - fraction)) + mState.mInsetLeft.mDimension
+ mState.mInsetRight.mDimension;
}
@Override
public int getIntrinsicHeight() {
final int childHeight = getDrawable().getIntrinsicHeight();
final float fraction = mState.mInsetTop.mFraction + mState.mInsetBottom.mFraction;
if (childHeight < 0 || fraction >= 1) {
return -1;
}
return (int) (childHeight / (1 - fraction)) + mState.mInsetTop.mDimension
+ mState.mInsetBottom.mDimension;
}
@Override
public void getOutline(@NonNull Outline outline) {
getDrawable().getOutline(outline);
}
@Override
DrawableWrapperState mutateConstantState() {
mState = new InsetState(mState, null);
return mState;
}
static final class InsetState extends DrawableWrapper.DrawableWrapperState {
private int[] mThemeAttrs;
InsetValue mInsetLeft;
InsetValue mInsetTop;
InsetValue mInsetRight;
InsetValue mInsetBottom;
InsetState(@Nullable InsetState orig, @Nullable Resources res) {
super(orig, res);
if (orig != null) {
mInsetLeft = orig.mInsetLeft.clone();
mInsetTop = orig.mInsetTop.clone();
mInsetRight = orig.mInsetRight.clone();
mInsetBottom = orig.mInsetBottom.clone();
if (orig.mDensity != mDensity) {
applyDensityScaling(orig.mDensity, mDensity);
}
} else {
mInsetLeft = new InsetValue();
mInsetTop = new InsetValue();
mInsetRight = new InsetValue();
mInsetBottom = new InsetValue();
}
}
@Override
void onDensityChanged(int sourceDensity, int targetDensity) {
super.onDensityChanged(sourceDensity, targetDensity);
applyDensityScaling(sourceDensity, targetDensity);
}
/**
* Called when the constant state density changes to scale
* density-dependent properties specific to insets.
*
* @param sourceDensity the previous constant state density
* @param targetDensity the new constant state density
*/
private void applyDensityScaling(int sourceDensity, int targetDensity) {
mInsetLeft.scaleFromDensity(sourceDensity, targetDensity);
mInsetTop.scaleFromDensity(sourceDensity, targetDensity);
mInsetRight.scaleFromDensity(sourceDensity, targetDensity);
mInsetBottom.scaleFromDensity(sourceDensity, targetDensity);
}
@Override
public Drawable newDrawable(@Nullable Resources res) {
// If this drawable is being created for a different density,
// just create a new constant state and call it a day.
final InsetState state;
if (res != null) {
final int densityDpi = res.getDisplayMetrics().densityDpi;
final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
if (density != mDensity) {
state = new InsetState(this, res);
} else {
state = this;
}
} else {
state = this;
}
return new InsetDrawable(state, res);
}
}
static final class InsetValue implements Cloneable {
final float mFraction;
int mDimension;
public InsetValue() {
this(0f, 0);
}
public InsetValue(float fraction, int dimension) {
mFraction = fraction;
mDimension = dimension;
}
int getDimension(int boundSize) {
return (int) (boundSize * mFraction) + mDimension;
}
void scaleFromDensity(int sourceDensity, int targetDensity) {
if (mDimension != 0) {
mDimension = Bitmap.scaleFromDensity(mDimension, sourceDensity, targetDensity);
}
}
@Override
public InsetValue clone() {
return new InsetValue(mFraction, mDimension);
}
}
/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) {
super(state, res);
mState = state;
}
}
ScaleDrawable
* Copyright (C) 2006 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* A Drawable that changes the size of another Drawable based on its current
* level value. You can control how much the child Drawable changes in width
* and height based on the level, as well as a gravity to control where it is
* placed in its overall container. Most often used to implement things like
* progress bars.
* <p>
* The default level may be specified from XML using the
* {@link android.R.styleable#ScaleDrawable_level android:level} property. When
* this property is not specified, the default level is 0, which corresponds to
* zero height and/or width depending on the values specified for
* {@code android.R.styleable#ScaleDrawable_scaleWidth scaleWidth} and
* {@code android.R.styleable#ScaleDrawable_scaleHeight scaleHeight}. At run
* time, the level may be set via {@link #setLevel(int)}.
* <p>
* A scale drawable may be defined in an XML file with the {@code <scale>}
* element. For more information, see the guide to
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable
* Resources</a>.
*
* @attr ref android.R.styleable#ScaleDrawable_scaleWidth
* @attr ref android.R.styleable#ScaleDrawable_scaleHeight
* @attr ref android.R.styleable#ScaleDrawable_scaleGravity
* @attr ref android.R.styleable#ScaleDrawable_drawable
* @attr ref android.R.styleable#ScaleDrawable_level
*/
public class ScaleDrawable extends DrawableWrapper {
private static final int MAX_LEVEL = 10000;
private final Rect mTmpRect = new Rect();
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private ScaleState mState;
ScaleDrawable() {
this(new ScaleState(null, null), null);
}
/**
* Creates a new scale drawable with the specified gravity and scale
* properties.
*
* @param drawable the drawable to scale
* @param gravity gravity constant (see {@link Gravity} used to position
* the scaled drawable within the parent container
* @param scaleWidth width scaling factor [0...1] to use then the level is
* at the maximum value, or -1 to not scale width
* @param scaleHeight height scaling factor [0...1] to use then the level
* is at the maximum value, or -1 to not scale height
*/
public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
this(new ScaleState(null, null), null);
mState.mGravity = gravity;
mState.mScaleWidth = scaleWidth;
mState.mScaleHeight = scaleHeight;
setDrawable(drawable);
}
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
// Inflation will advance the XmlPullParser and AttributeSet.
super.inflate(r, parser, attrs, theme);
updateStateFromTypedArray(a);
verifyRequiredAttributes(a);
a.recycle();
updateLocalState();
}
@Override
public void applyTheme(@NonNull Theme t) {
super.applyTheme(t);
final ScaleState state = mState;
if (state == null) {
return;
}
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
try {
updateStateFromTypedArray(a);
verifyRequiredAttributes(a);
} catch (XmlPullParserException e) {
rethrowAsRuntimeException(e);
} finally {
a.recycle();
}
}
updateLocalState();
}
private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
// If we're not waiting on a theme, verify required attributes.
if (getDrawable() == null && (mState.mThemeAttrs == null
|| mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
throw new XmlPullParserException(a.getPositionDescription()
+ ": <scale> tag requires a 'drawable' attribute or "
+ "child tag defining a drawable");
}
}
private void updateStateFromTypedArray(@NonNull TypedArray a) {
final ScaleState 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();
state.mScaleWidth = getPercent(a,
R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
state.mScaleHeight = getPercent(a,
R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight);
state.mGravity = a.getInt(
R.styleable.ScaleDrawable_scaleGravity, state.mGravity);
state.mUseIntrinsicSizeAsMin = a.getBoolean(
R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
state.mInitialLevel = a.getInt(
R.styleable.ScaleDrawable_level, state.mInitialLevel);
}
private static float getPercent(TypedArray a, int index, float defaultValue) {
final int type = a.getType(index);
if (type == TypedValue.TYPE_FRACTION || type == TypedValue.TYPE_NULL) {
return a.getFraction(index, 1, 1, defaultValue);
}
// Coerce to float.
final String s = a.getString(index);
if (s != null) {
if (s.endsWith("%")) {
final String f = s.substring(0, s.length() - 1);
return Float.parseFloat(f) / 100.0f;
}
}
return defaultValue;
}
@Override
public void draw(Canvas canvas) {
final Drawable d = getDrawable();
if (d != null && d.getLevel() != 0) {
d.draw(canvas);
}
}
@Override
public int getOpacity() {
final Drawable d = getDrawable();
if (d.getLevel() == 0) {
return PixelFormat.TRANSPARENT;
}
final int opacity = d.getOpacity();
if (opacity == PixelFormat.OPAQUE && d.getLevel() < MAX_LEVEL) {
return PixelFormat.TRANSLUCENT;
}
return opacity;
}
@Override
protected boolean onLevelChange(int level) {
super.onLevelChange(level);
onBoundsChange(getBounds());
invalidateSelf();
return true;
}
@Override
protected void onBoundsChange(Rect bounds) {
final Drawable d = getDrawable();
final Rect r = mTmpRect;
final boolean min = mState.mUseIntrinsicSizeAsMin;
final int level = getLevel();
int w = bounds.width();
if (mState.mScaleWidth > 0) {
final int iw = min ? d.getIntrinsicWidth() : 0;
w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);
}
int h = bounds.height();
if (mState.mScaleHeight > 0) {
final int ih = min ? d.getIntrinsicHeight() : 0;
h -= (int) ((h - ih) * (MAX_LEVEL - level) * mState.mScaleHeight / MAX_LEVEL);
}
final int layoutDirection = getLayoutDirection();
Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
if (w > 0 && h > 0) {
d.setBounds(r.left, r.top, r.right, r.bottom);
}
}
@Override
DrawableWrapperState mutateConstantState() {
mState = new ScaleState(mState, null);
return mState;
}
static final class ScaleState extends DrawableWrapper.DrawableWrapperState {
/** Constant used to disable scaling for a particular dimension. */
private static final float DO_NOT_SCALE = -1.0f;
private int[] mThemeAttrs;
float mScaleWidth = DO_NOT_SCALE;
float mScaleHeight = DO_NOT_SCALE;
int mGravity = Gravity.LEFT;
boolean mUseIntrinsicSizeAsMin = false;
int mInitialLevel = 0;
ScaleState(ScaleState orig, Resources res) {
super(orig, res);
if (orig != null) {
mScaleWidth = orig.mScaleWidth;
mScaleHeight = orig.mScaleHeight;
mGravity = orig.mGravity;
mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
mInitialLevel = orig.mInitialLevel;
}
}
@Override
public Drawable newDrawable(Resources res) {
return new ScaleDrawable(this, res);
}
}
/**
* Creates a new ScaleDrawable based on the specified constant state.
* <p>
* The resulting drawable is guaranteed to have a new constant state.
*
* @param state constant state from which the drawable inherits
*/
private ScaleDrawable(ScaleState state, Resources res) {
super(state, res);
mState = state;
updateLocalState();
}
private void updateLocalState() {
setLevel(mState.mInitialLevel);
}
}