diff options
Diffstat (limited to 'graphics/java/android/graphics/drawable/Drawable.java')
-rw-r--r-- | graphics/java/android/graphics/drawable/Drawable.java | 768 |
1 files changed, 768 insertions, 0 deletions
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java new file mode 100644 index 000000000000..00212415aa93 --- /dev/null +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -0,0 +1,768 @@ +/* + * 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.InputStream; +import java.io.IOException; +import java.util.Arrays; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.*; +import android.util.AttributeSet; +import android.util.StateSet; +import android.util.Xml; + +/** + * 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#setBackgroundDrawable(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>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> + * <p>For information and examples of creating drawable resources (XML or bitmap files that + * can be loaded in code), see <a href="{@docRoot}devel/resources-i18n.html">Resources + * and Internationalization</a>. + */ +public abstract class Drawable { + + private int[] mStateSet = StateSet.WILD_CARD; + private int mLevel = 0; + private int mChangingConfigurations = 0; + private Rect mBounds = new Rect(); + /*package*/ Callback mCallback = null; + private boolean mVisible = true; + + /** + * 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(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.left != left || oldBounds.top != top || + oldBounds.right != right || oldBounds.bottom != bottom) { + 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(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(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 + */ + 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. + * + * @return The bounds of the drawable (which may change later, so caller + * beware). + */ + public final Rect getBounds() { + return mBounds; + } + + /** + * 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.res.Configuration}. + * + * @see android.content.res.Configuration + */ + public void setChangingConfigurations(int configs) { + mChangingConfigurations = configs; + } + + /** + * Return a mask of the configuration parameters for which this drawable + * mau 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.res.Configuration}. + * + * @see android.content.res.Configuration + */ + public 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. This can improve the look on + * those devices, but can also slow down the drawing a little. + */ + public void setDither(boolean dither) {} + + /** + * Set to true to have the drawable filter its bitmap when scaled or rotated + * (for drawables that use bitmaps). If the drawable does not use bitmaps, + * this call is ignored. This can improve the look when scaled or rotated, + * but also slows down the drawing. + */ + public void setFilterBitmap(boolean filter) {} + + /** + * 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 static 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. + */ + public void invalidateDrawable(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} + */ + public void scheduleDrawable(Drawable who, 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. + */ + public void unscheduleDrawable(Drawable who, 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. + */ + public final void setCallback(Callback cb) { + mCallback = cb; + } + + /** + * 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 + */ + public void invalidateSelf() + { + if (mCallback != null) { + mCallback.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(Runnable what, long when) + { + if (mCallback != null) { + mCallback.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(Runnable what) + { + if (mCallback != null) { + mCallback.unscheduleDrawable(this, what); + } + } + + /** + * Specify an alpha value for the drawable. 0 means fully transparent, and + * 255 means fully opaque. + */ + public abstract void setAlpha(int alpha); + + /** + * Specify an optional colorFilter for the drawable. Pass null to remove + * any filters. + */ + public abstract void setColorFilter(ColorFilter cf); + + /** + * Specify a color and porterduff mode to be the colorfilter for this + * drawable. + */ + public void setColorFilter(int color, PorterDuff.Mode mode) { + setColorFilter(new PorterDuffColorFilter(color, mode)); + } + + public void clearColorFilter() { + setColorFilter(null); + } + + /** + * Indicates whether this view 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 view changes its appearance based on state, false + * otherwise. + * + * @see #setState(int[]) + */ + public boolean isStateful() { + 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(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 int[] getState() { + return mStateSet; + } + + /** + * @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 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(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 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; + mVisible = visible; + return changed; + } + + public final boolean isVisible() { + return mVisible; + } + + /** + * 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>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 <em>not</em> take into account a + * custom alpha or color filter that has been applied by the client through + * the {@link #setAlpha} or {@link #setColorFilter} methods. + * + * @return int The opacity class of the Drawable. + * + * @see android.graphics.PixelFormat + */ + public abstract 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 int resolveOpacity(int op1, 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 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 recognize the + * specified state. + */ + protected void onBoundsChange(Rect bounds) {} + + /** + * Return the intrinsic width of the underlying drawable object. Returns + * -1 if it has no intrinsic width, such as with a solid color. + */ + public int getIntrinsicWidth() { + return -1; + } + + /** + * Return the intrinsic height of the underlying drawable object. Returns + * -1 if it has no intrinsic height, such as with a solid color. + */ + 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). Returns true if this drawable + * actually has a padding, else false. When false is returned, the padding + * is always set to 0. + */ + public boolean getPadding(Rect padding) { + padding.set(0, 0, 0, 0); + return false; + } + + /** + * Create a drawable from an inputstream + */ + public static Drawable createFromStream(InputStream is, String srcName) { + if (is == null) { + return null; + } + + /* 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(); + Bitmap bm = BitmapFactory.decodeStream(is, pad, null); + if (bm != null) { + byte[] np = bm.getNinePatchChunk(); + if (np == null || !NinePatch.isNinePatchChunk(np)) { + np = null; + pad = null; + } + return drawableFromBitmap(bm, np, pad, srcName); + } + return null; + } + + /** + * Create a drawable from an XML document. For more information on how to + * create resources in XML, see + * <a href="{@docRoot}devel/resources-i18n.html">Resources and + * Internationalization</a>. + */ + public static Drawable createFromXml(Resources r, XmlPullParser parser) + throws XmlPullParserException, IOException { + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + 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 = createFromXmlInner(r, parser, attrs); + + 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. + */ + public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + Drawable drawable; + + final String name = parser.getName(); + + if (name.equals("selector")) { + drawable = new StateListDrawable(); + } else if (name.equals("level-list")) { + drawable = new LevelListDrawable(); + } else if (name.equals("layer-list")) { + drawable = new LayerDrawable(); + } else if (name.equals("transition")) { + drawable = new TransitionDrawable(); + } else if (name.equals("color")) { + drawable = new ColorDrawable(); + } else if (name.equals("shape")) { + drawable = new GradientDrawable(); + } else if (name.equals("scale")) { + drawable = new ScaleDrawable(); + } else if (name.equals("clip")) { + drawable = new ClipDrawable(); + } else if (name.equals("rotate")) { + drawable = new RotateDrawable(); + } else if (name.equals("animation-list")) { + drawable = new AnimationDrawable(); + } else if (name.equals("inset")) { + drawable = new InsetDrawable(); + } else if (name.equals("bitmap")) { + drawable = new BitmapDrawable(); + } else { + throw new XmlPullParserException(parser.getPositionDescription() + + ": invalid drawable tag " + name); + } + + drawable.inflate(r, parser, attrs); + return drawable; + } + + + /** + * Create a drawable from file path name. + */ + public static Drawable createFromPath(String pathName) { + if (pathName == null) { + return null; + } + + Bitmap bm = BitmapFactory.decodeFile(pathName); + if (bm != null) { + return drawableFromBitmap(bm, null, null, pathName); + } + + return null; + } + + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + + TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); + inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); + a.recycle(); + } + + void inflateWithAttributes(Resources r, XmlPullParser parser, + TypedArray attrs, int visibleAttr) + throws XmlPullParserException, IOException { + + mVisible = attrs.getBoolean(visibleAttr, mVisible); + } + + public static abstract class ConstantState { + public abstract Drawable newDrawable(); + public abstract int getChangingConfigurations(); + } + + public ConstantState getConstantState() { + return null; + } + + private static Drawable drawableFromBitmap(Bitmap bm, byte[] np, Rect pad, String srcName) { + if (np != null) { + return new NinePatchDrawable(bm, np, pad, srcName); + } + return new BitmapDrawable(bm); + } +} + |