Android Drawable loader alternative
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package codetail.project.watchme.common.drawables; | |
import android.annotation.TargetApi; | |
import android.content.res.Resources; | |
import android.content.res.TypedArray; | |
import android.content.res.XmlResourceParser; | |
import android.graphics.BitmapFactory; | |
import android.graphics.drawable.ColorDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.os.Build; | |
import android.support.v4.util.LongSparseArray; | |
import android.util.AttributeSet; | |
import android.util.Log; | |
import android.util.TypedValue; | |
import android.util.Xml; | |
import org.xmlpull.v1.XmlPullParser; | |
import org.xmlpull.v1.XmlPullParserException; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.ref.WeakReference; | |
import java.util.HashMap; | |
import java.util.Map; | |
import codetail.project.watchme.common.compat.Android; | |
public class DrawablesCompat { | |
private static final Object mAccessLock = new Object(); | |
private static final Map<String, Class<? extends Drawable>> CLASS_MAP = new HashMap<>(); | |
private static final LongSparseArray<WeakReference<Drawable.ConstantState>> | |
sDrawableCache = new LongSparseArray<>(); | |
private static final LongSparseArray<WeakReference<Drawable.ConstantState>> | |
sColorDrawableCache = new LongSparseArray<>(); | |
private static final IDrawable IMPL; | |
static { | |
registerDrawable(RippleDrawable.class, "ripple"); | |
if(Android.isLollipop()){ | |
IMPL = new LollipopDrawableImpl(); | |
}else{ | |
IMPL = new BaseDrawableImpl(); | |
} | |
} | |
public static void registerDrawable(Class<? extends Drawable> clazz, String name) { | |
if (name == null || clazz == null) { | |
throw new NullPointerException("Class: " + clazz + ". Name: " + name); | |
} | |
CLASS_MAP.put(name, clazz); | |
} | |
public static void unregisterDrawable(String name) { | |
CLASS_MAP.remove(name); | |
} | |
/** | |
* Applies the specified theme to this Drawable and its children. | |
*/ | |
public static void applyTheme(Drawable d, Resources.Theme t) { | |
IMPL.applyTheme(d, t); | |
} | |
public static boolean canApplyTheme(Drawable d) { | |
return IMPL.canApplyTheme(d); | |
} | |
/** | |
* Create a drawable from an inputstream | |
*/ | |
public static Drawable createFromStream(InputStream is, String srcName) { | |
return createFromResourceStream(null, null, is, srcName); | |
} | |
/** | |
* 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) { | |
return createFromResourceStream(res, value, is, srcName, null); | |
} | |
/** | |
* 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, BitmapFactory.Options opts) { | |
return Drawable.createFromResourceStream(res, value, is, srcName, opts); | |
} | |
/** | |
* 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>. | |
*/ | |
public static Drawable createFromXml(Resources r, XmlPullParser parser) | |
throws XmlPullParserException, IOException { | |
return createFromXml(r, parser, null); | |
} | |
/** | |
* Create a drawable from an XML document using an optional {@link Resources.Theme}. | |
* For more information on how to create resources in XML, see | |
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. | |
*/ | |
public static Drawable createFromXml(Resources r, XmlPullParser parser, Resources.Theme theme) | |
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, theme); | |
if (drawable == null) { | |
throw new RuntimeException("Unknown initial tag: " + parser.getName()); | |
} | |
return drawable; | |
} | |
/** | |
* Create a drawable from inside an XML document using an optional | |
* {@link Resources.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. | |
*/ | |
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs, | |
Resources.Theme theme) | |
throws XmlPullParserException, IOException { | |
Drawable drawable = null; | |
final String name = parser.getName(); | |
try { | |
Class<? extends Drawable> clazz = CLASS_MAP.get(name); | |
if (clazz != null) { | |
drawable = clazz.newInstance(); | |
} else if (name.indexOf('.') > 0) { | |
drawable = (Drawable) Class.forName(name).newInstance(); | |
} | |
} catch (Exception e) { | |
throw new XmlPullParserException("Error while inflating drawable resource", parser, e); | |
} | |
if (drawable == null) { | |
if(Android.isLollipop()) { | |
return Drawable.createFromXmlInner(r, parser, attrs, theme); | |
} | |
return Drawable.createFromXmlInner(r, parser, attrs); | |
} | |
IMPL.inflate(drawable, r, parser, attrs, theme); | |
return drawable; | |
} | |
private static Drawable getCachedDrawable(LongSparseArray<WeakReference<Drawable.ConstantState>> cache, | |
long key, Resources res) { | |
synchronized (mAccessLock) { | |
WeakReference<Drawable.ConstantState> wr = cache.get(key); | |
if (wr != null) { | |
Drawable.ConstantState entry = wr.get(); | |
if (entry != null) { | |
return entry.newDrawable(res); | |
} else { | |
cache.delete(key); | |
} | |
} | |
} | |
return null; | |
} | |
public static Drawable getDrawable(Resources res, int resid, Resources.Theme theme) { | |
Log.i("ResourcesCompat", res.getResourceName(resid)); | |
TypedValue value = new TypedValue(); | |
res.getValue(resid, value, true); | |
return loadDrawable(res, value, theme); | |
} | |
public static Drawable getDrawable(Resources res, int resid) { | |
return getDrawable(res, resid, null); | |
} | |
public static Drawable getDrawable(TypedArray array, int index, Resources.Theme theme) { | |
TypedValue value = new TypedValue(); | |
array.getValue(index, value); | |
return loadDrawable(array.getResources(), value, theme); | |
} | |
public static Drawable getDrawable(TypedArray array, int index) { | |
return getDrawable(array, index, null); | |
} | |
public static Drawable loadDrawable(Resources res, TypedValue value, Resources.Theme theme) | |
throws Resources.NotFoundException { | |
if (value == null || value.resourceId == 0) { | |
return null; | |
} | |
final boolean isColorDrawable; | |
final LongSparseArray<WeakReference<Drawable.ConstantState>> cache; | |
final long key; | |
if(value.type >= TypedValue.TYPE_FIRST_COLOR_INT | |
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT){ | |
isColorDrawable = true; | |
cache = sColorDrawableCache; | |
key = value.data; | |
}else{ | |
isColorDrawable = false; | |
cache = sDrawableCache; | |
key = (long) value.assetCookie << 32 | value.data; | |
} | |
Drawable dr = getCachedDrawable(cache, key, res); | |
if (dr != null) { | |
return dr; | |
} | |
Drawable.ConstantState cs = null; | |
//TODO hm do i need implement preloading of drawables? | |
if(cs != null){ | |
final Drawable cloneDr = cs.newDrawable(res); | |
if(theme != null){ | |
dr = cloneDr.mutate(); | |
applyTheme(dr, theme); | |
}else{ | |
dr = cloneDr; | |
} | |
}else if(isColorDrawable){ | |
dr = new ColorDrawable(value.data); | |
}else{ | |
dr = loadDrawableForCookie(value, value.resourceId, res, theme); | |
} | |
if (dr != null) { | |
dr.setChangingConfigurations(value.changingConfigurations); | |
cacheDrawable(value, res, theme, isColorDrawable, key, dr, cache); | |
} | |
return dr; | |
} | |
private static void cacheDrawable(TypedValue value, Resources resources, Resources.Theme theme, | |
boolean isColorDrawable, long key, Drawable drawable, | |
LongSparseArray<WeakReference<Drawable.ConstantState>> caches){ | |
Drawable.ConstantState cs = drawable.getConstantState(); | |
if (cs == null) { | |
return; | |
} | |
synchronized (mAccessLock){ | |
caches.put(key, new WeakReference<>(cs)); | |
} | |
} | |
private static Drawable loadDrawableForCookie(TypedValue value, int id, Resources res, Resources.Theme theme){ | |
if (value.string == null) { | |
throw new Resources.NotFoundException("Resource \"" + res.getResourceName(id) + "\" (" | |
+ Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); | |
} | |
String file = value.string.toString(); | |
final Drawable dr; | |
if (file.endsWith(".xml")) { | |
try { | |
XmlResourceParser rp = res.getAssets().openXmlResourceParser(value.assetCookie, | |
file); | |
dr = DrawablesCompat.createFromXml(res, rp, theme); | |
rp.close(); | |
} catch (Exception e) { | |
Log.w(DrawablesCompat.class.getSimpleName(), "Failed to load drawable resource, " + | |
"using a fallback...", e); | |
return res.getDrawable(value.resourceId); | |
} | |
} else { | |
try { | |
InputStream is = res.getAssets().openNonAssetFd(value.assetCookie, file) | |
.createInputStream(); | |
dr = DrawablesCompat.createFromResourceStream(res, value, is, file, null); | |
is.close(); | |
} catch (Exception e) { | |
Log.w(DrawablesCompat.class.getSimpleName(), "Failed to load drawable resource, " + | |
"using a fallback...", e); | |
return res.getDrawable(value.resourceId); | |
} | |
} | |
return dr; | |
} | |
/** | |
* Create a drawable from file path name. | |
*/ | |
public static Drawable createFromPath(String pathName) { | |
return Drawable.createFromPath(pathName); | |
} | |
private interface IDrawable{ | |
void applyTheme(Drawable drawable, Resources.Theme t); | |
boolean canApplyTheme(Drawable drawable); | |
void inflate(Drawable drawable, Resources r, XmlPullParser parser, AttributeSet attrs, | |
Resources.Theme theme) throws XmlPullParserException, IOException; | |
} | |
static class BaseDrawableImpl implements IDrawable { | |
@Override | |
public void applyTheme(Drawable drawable, Resources.Theme t) { | |
} | |
@Override | |
public boolean canApplyTheme(Drawable drawable) { | |
return false; | |
} | |
@Override | |
public void inflate(Drawable drawable, Resources r, XmlPullParser parser, AttributeSet attrs, | |
Resources.Theme theme) throws XmlPullParserException, IOException { | |
if(drawable instanceof LollipopDrawable){ | |
((LollipopDrawable) drawable).inflate(r, parser, attrs, theme); | |
return; | |
} | |
drawable.inflate(r, parser, attrs); | |
} | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
static class LollipopDrawableImpl extends BaseDrawableImpl { | |
@Override | |
public void applyTheme(Drawable drawable, Resources.Theme t) { | |
drawable.applyTheme(t); | |
} | |
@Override | |
public boolean canApplyTheme(Drawable drawable) { | |
return drawable.canApplyTheme(); | |
} | |
@Override | |
public void inflate(Drawable drawable, Resources r, XmlPullParser parser, AttributeSet attrs, | |
Resources.Theme theme) throws XmlPullParserException, IOException { | |
drawable.inflate(r, parser, attrs, theme); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment