Files
frameworks_base/graphics/java/android/graphics/drawable/DrawableInflater.java
Leon Scroggins III 127d31a684 Expose AnimatedImageDrawable
Bug: 63908092
Test: I85979ae3d8c6a6dae6e4299dc3be291e12024290

Implement Animatable2, adding listeners for starting and ending the
animation.

Add setLoopCount for changing the loop count.

Add the ability to inflate from XML, by using the name of the class or
"animated-image", which mimics "nine-patch", "bitmap" etc.

Move internal variables to a State class so that they can be transferred
to a default constructed AnimatedImageDrawable.

Change-Id: Ice8149e7de55f7ffb4b4ba9dd9c856582fc42bc9
2018-01-24 19:20:18 -05:00

234 lines
8.8 KiB
Java

/*
* Copyright (C) 2015 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 org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.util.AttributeSet;
import android.view.InflateException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashMap;
/**
* Instantiates a drawable XML file into its corresponding
* {@link android.graphics.drawable.Drawable} objects.
* <p>
* For performance reasons, inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use this inflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource (R.
* <em>something</em> file.)
*
* @hide Pending API finalization.
*/
public final class DrawableInflater {
private static final HashMap<String, Constructor<? extends Drawable>> CONSTRUCTOR_MAP =
new HashMap<>();
private final Resources mRes;
private final ClassLoader mClassLoader;
/**
* Loads the drawable resource with the specified identifier.
*
* @param context the context in which the drawable should be loaded
* @param id the identifier of the drawable resource
* @return a drawable, or {@code null} if the drawable failed to load
*/
@Nullable
public static Drawable loadDrawable(@NonNull Context context, @DrawableRes int id) {
return loadDrawable(context.getResources(), context.getTheme(), id);
}
/**
* Loads the drawable resource with the specified identifier.
*
* @param resources the resources from which the drawable should be loaded
* @param theme the theme against which the drawable should be inflated
* @param id the identifier of the drawable resource
* @return a drawable, or {@code null} if the drawable failed to load
*/
@Nullable
public static Drawable loadDrawable(
@NonNull Resources resources, @Nullable Theme theme, @DrawableRes int id) {
return resources.getDrawable(id, theme);
}
/**
* Constructs a new drawable inflater using the specified resources and
* class loader.
*
* @param res the resources used to resolve resource identifiers
* @param classLoader the class loader used to load custom drawables
* @hide
*/
public DrawableInflater(@NonNull Resources res, @NonNull ClassLoader classLoader) {
mRes = res;
mClassLoader = classLoader;
}
/**
* Inflates a drawable from inside an XML document using an optional
* {@link Theme}.
* <p>
* This method should be called on a parser positioned at a tag in an XML
* document defining a drawable resource. It will attempt to create a
* Drawable from the tag at the current position.
*
* @param name the name of the tag at the current position
* @param parser an XML parser positioned at the drawable tag
* @param attrs an attribute set that wraps the parser
* @param theme the theme against which the drawable should be inflated, or
* {@code null} to not inflate against a theme
* @return a drawable
*
* @throws XmlPullParserException
* @throws IOException
*/
@NonNull
public Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
return inflateFromXmlForDensity(name, parser, attrs, 0, theme);
}
/**
* Version of {@link #inflateFromXml(String, XmlPullParser, AttributeSet, Theme)} that accepts
* an override density.
*/
@NonNull
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
// Inner classes must be referenced as Outer$Inner, but XML tag names
// can't contain $, so the <drawable> tag allows developers to specify
// the class in an attribute. We'll still run it through inflateFromTag
// to stay consistent with how LayoutInflater works.
if (name.equals("drawable")) {
name = attrs.getAttributeValue(null, "class");
if (name == null) {
throw new InflateException("<drawable> tag must specify class attribute");
}
}
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
@NonNull
@SuppressWarnings("deprecation")
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "adaptive-icon":
return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
case "animated-image":
return new AnimatedImageDrawable();
default:
return null;
}
}
@NonNull
private Drawable inflateFromClass(@NonNull String className) {
try {
Constructor<? extends Drawable> constructor;
synchronized (CONSTRUCTOR_MAP) {
constructor = CONSTRUCTOR_MAP.get(className);
if (constructor == null) {
final Class<? extends Drawable> clazz =
mClassLoader.loadClass(className).asSubclass(Drawable.class);
constructor = clazz.getConstructor();
CONSTRUCTOR_MAP.put(className, constructor);
}
}
return constructor.newInstance();
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(
"Error inflating class " + className);
ie.initCause(e);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a Drawable subclass.
final InflateException ie = new InflateException(
"Class is not a Drawable " + className);
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
final InflateException ie = new InflateException(
"Class not found " + className);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
"Error inflating class " + className);
ie.initCause(e);
throw ie;
}
}
}