/* * 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. *

* 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. * something file.) * * @hide Pending API finalization. */ public final class DrawableInflater { private static final HashMap> 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}. *

* 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 { // Inner classes must be referenced as Outer$Inner, but XML tag names // can't contain $, so the 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(" tag must specify class attribute"); } } Drawable drawable = inflateFromTag(name); if (drawable == null) { drawable = inflateFromClass(name); } 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 "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(); default: return null; } } @NonNull private Drawable inflateFromClass(@NonNull String className) { try { Constructor constructor; synchronized (CONSTRUCTOR_MAP) { constructor = CONSTRUCTOR_MAP.get(className); if (constructor == null) { final Class 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; } } }