can one drawable instance be used on multiple imageView? - android

in a listview, for each item it needs to show some image(drawable) for different item name. If the name are same they show same image. The drawable is specific and cost some time to make it (need remote resource).
So thought maybe cache the drawable and only build new one if it has not been cached.
saw some article "ANDROID DRAWABLE INSTANCES – DON’T SHARE THEM!"
http://loseyourmarbles.co/2013/09/android-drawable-instances-dont-share/,
and here also need to animate the drawable. But test seems not seeing bad behavior (maybe not tested enough)?
So anyone has had similar requirement and experienced whether one drawable instance could be used on multiple ImageView? Is this approach right way to speed up the listview?
Or can one drawable instance be used on multiple imageView?
HashMap<String, Drawable> mNameToDrawnable = new HashMap<>();
public Drawable getDrawable(Context context, String displayName){
Drawable cachedD = mNameToDrawnable.get(displayName);
if (cachedD != null) {
return cachedD;
}
Drawnable d = makeDrawable(displayName); //new Drawable(context.getResources());, etc ……
d.setDecodeDimensions(100, 100);
mNameToDrawnable.put(displayName, d);
return d;
}

If you want to use the cached drawable in your listview and you're worried about some issues with it you might want to call mutate() on your drawable. This might give you a better understanding : https://android-developers.googleblog.com/2009/05/drawable-mutations.html?m=1

Related

Drawable tinting in RecyclerView for pre-Lollipop

I am trying to use drawable tinting using the following code in my RecyclerView
Drawable likeDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_thumb_up);
Drawable likeWrappedDrawable = DrawableCompat.wrap(likeDrawable);
DrawableCompat.setTint(likeWrappedDrawable,ContextCompat.getColor(getActivity(), android.R.color.white));
holder.ivLike.setImageDrawable(likeWrappedDrawable);
Now all of this is being done in the onBindViewHolder of the RecyclerView Adapter
I change this tint between three colors based on the state of that list item. This works all fine for Lolipop and above but below this version, the color of the list item is unpredictable. Sometimes it shows the right color but on refreshing the list sometimes it changes to some other color.
Anything im doing wrong here or the tinting thing on pre-lollipop is still unusable in this particular case?
Update
Including the code from my onBindViewHolder
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drawable likeDrawable =
ContextCompat.getDrawable(getActivity(), R.drawable.ic_thumb_up);
Drawable likeWrappedDrawable = DrawableCompat.wrap(likeDrawable);
holder.tvLikeCount.setTextColor(ResUtil.getColor(R.color.light_font,
getActivity()));
DrawableCompat.setTint(likeWrappedDrawable,
ContextCompat.getColor(getActivity(), android.R.color.white));
if (tweetModel.isFavorited()) {
DrawableCompat.setTint(likeWrappedDrawable,
ContextCompat.getColor(getActivity(), android.R.color.holo_blue_light));
}
holder.ivLike.setImageDrawable(likeWrappedDrawable);
}
Call mutate() on the Drawable before calling setTint()
Drawable likeDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_thumb_up).mutate();
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.
http://developer.android.com/reference/android/graphics/drawable/Drawable.html#mutate()
TL;DR. If you're worried about efficiency, use TintedIconCache, a single class you can grab from this gist.
TintedIconCache cache = TintedIconCache.getInstance();
Drawable myTintedDrawable = cache.fetchTintedIcon(context, R.drawable.icon_01, R.color.color_01));
The need to do more than just .mutate()
As #Vladimir stated, you have to call drawable.mutate() to get an isolated state, otherwise any change made to your drawable properties would be reflected on all the other drawables sharing the same state.
This behavior is actually meant for good reasons, one of which is memory efficiency, and there are some scenarios where you'd want to keep that efficiency.
For instance, you might have a RecyclerView with items using the same drawable but tinted differently for each item according to some property (e.g. success, failure, warning, info, etc). If you're in that case, mutating your drawable for each item is not the best solution. You could think of creating all the possible drawables, with all the possible tints, but you might be creating useless onces. If you're in that case, I got you covered!
Enters TintedIconCache
A better solution is have some sort of drawable cache, where each uniquely tinted drawable is created only when needed, and then is cached efficiently for subsequent needs until you no longer need it or the system wants that occupied memory back.
I have already implemented this, in TintedIconCache. It's straightforward to use:
// Get an instance
TintedIconCache cache = TintedIconCache.getInstance();
// Will be fetched from the resources
Drawable backIcon = cache.fetchTintedIcon(context, R.drawable.icon, R.color.black));
// Will be fetched from the resources as well
Drawable bleuIcon = cache.fetchTintedIcon(context, R.drawable.icon, R.color.bleu));
// Will be fetched from the cache!!!
Drawable backIconTwo = cache.fetchTintedIcon(context, R.drawable.icon, R.color.back));
Consider looking at the gist to see how it works under the hood.

Setting an imageview src through an activity

I keep trying to set an imageview through my main activity and it keeps returning a null pointer exception in LogCat. My code is fairly self explanatory.
I'm pulling data from a JSON url and pulling data out of objects.
for (int i=0; i<forecastday_arr.length(); i++) {
HashMap<String, String> map = new HashMap<String, String>();
JSONObject e = forecastday_arr.getJSONObject(i);
JSONObject date_obj = e.getJSONObject("date");
String curDate = date_obj.getString("weekday");
String conditions = e.getString("conditions");
String icon_to_use = e.getString("icon");
map.put("weekday", curDate);
map.put("conditions", conditions);
map.put("icon", icon_to_use);
if (icon_to_use=="rain") {
ImageView imgView = (ImageView) findViewById(R.id.imageViewDayOne);
imgView.setImageBitmap(BitmapFactory.decodeResource(this.getResources(), R.drawable.rain));
}
mylist.add(map);
}
From my code I'm checking to see if the value in "icon" is equal to rain, which it is (today), and if so, return the rain icon that's stored in my drawable folder. Even without the if statement I get the same null pointer exception. I'm quite new to Android so debugging isn't the easiest thing right now.
I presume I'm setting the image correctly. I've attached a picture of my LogCat below.
Sorry to be so vague but I thought it would be really simple to set an image. I'm sorry if I've missed anything as well - if I have I will quickly correct.
Pastebin of my MainActivity.java: http://pastebin.com/uNfYDGAw and a pastebin of my activity_main.xml: http://pastebin.com/M1xy1buB
So it appears your NullPointerException is coming from this line:
imgView.setImageBitmap(BitmapFactory.decodeResource(this.getResources(), R.drawable.rain));
I suspect that either this.getResources() or more likely BitmapFactory.decodeResource is returning null.
Luckily for you, there is an easier way to set an image on an ImageView. You can use setImageResource to set the drawable resource at runtime, like this:
imgView.setImageResource(R.drawable.rain);
--EDIT--
You also need to make sure you call setContentView with the layout that contains the ImageView you are attempting to use in onCreate of you MainActivity, before trying to reference any Views using findViewById.
setContentView(R.layout.layoutContainingImageViewDayOne);
Lastly, if you are attempting to do any network requests, they should be done in a background thread (off the UI thread) or the application with get an "Application Not Responding" crash. AsyncTasks are useful for this (however there are other methods). See http://developer.android.com/training/basics/network-ops/connecting.html for more info.
--EDIT 2--
So now that you posted all of the code, I see there are some major flaws here. Your R.layout.activity_main should actually be renamed to R.layout.list_row since it contains your row elements. Right now you are trying to use the same layout (R.layout.activity_main) for your Activity and its ListAdapter, which is impossible. R.layout.activity_main needs to have a ListView in it, that you attach your ListAdapter to. You are also trying to set your ImageView in your Activity code for your list rows, which the ListAdapter will already do for you.
So I'm going to suggest you take a look at some examples and refactor your app, Vogella has some great tutorials - http://www.vogella.com/articles/AndroidListView/article.html.
First use equals method to compare the Strings. Instead of ==
if (icon_to_use != null && icon_to_use.equals("rain")) {
....
}
Cause of NullPointerException
It might be because of you did not call setContentView to the layout or did not set right layout. means imageViewDayOne is not define in that layout.

ViewFlipper not loading bitmap images

Hello all having some trouble when I attempt to add an image to a viewflipper page, I am pulling the bitmaps from the db4o database (not sure if it is the encoding or something it uses that is messing me up).
private void setImageView() {
page = (ViewFlipper) findViewById(R.id.viewFlipper1);
int temp = DigPlayDB.getInstance(getBaseContext()).getPlaysDBSize();
for(int j = 0; j < temp; ++j){
test.add(DigPlayDB.getInstance(getBaseContext()).getPlayByInt(j).getImage());
test1.add(DigPlayDB.getInstance(getBaseContext()).getPlayByInt(j).getPlayName());
}
for(int i=0;i<temp; i++)
{
// This will create dynamic image view and add them to ViewFlipper
setFlipperImage(test.get(i));
}
And then for the setting of the image and adding the view to the page
private void setFlipperImage(Bitmap image){
ImageView _image = new ImageView(getApplicationContext());
//_image.setBackgroundDrawable(new BitmapDrawable(getResources(), image));
_image.setImageBitmap(image);
page.addView(_image);
Log.d("db", "" + image);
}
It works right after I add an image to the database but just that image, older images as well as when I restart the application do not load up even though it says they do from a debugging log I set. I am thinking that the last one shows up since it could still be in a cache somewhere, but the older ones that are stored in the database and not in a cache are not encoded correctly or something. Any help would be awesome. Thanks!
Edit: I should mention that "test" is an arraylist of Bitmaps.
Ok, you said in the comment that you store the object as a Bitmap instance. I guess thats a Android or library class.
Don't do that. Only store instances of your own classes. Storing instances of your classes, java.util.collections, arrays and primitives are okay. Everything else is bound to issues: db4o will eagerly try to store any object. This is a issue for library instances. You don't have control of what they do, how they work internally and if they still work after loading.
I think that's whats happening here. As long as the application is running, db4o returns the cached instance of the object, which is fine. After restarting the application, db4o loads the Bitmap object. However the bitmap object isn't intended to be stored with db4o, so it stumbles over wrongly stored internal state.
So, store your picture in a byte-array. Or just as plain file on the SD-card.

Best practices to load several bitmaps multiple times

I am creating an Android board game similar to, for example, Bubble Pop where I need to use several bitmaps multiple times.
I have a list of Stones (10x10), where each Stone is an object which holds its bitmap and some other values. Lot of bitmaps (stone colors) are same.
Right now i am using something like this for every Stone in the list:
public class Stone extends Point{
private Bitmap mImg;
public Stone (int x, int y, Resources res, Stones mStone) {
...
mImg = BitmapFactory.decodeResource(mRes, mStone.getId());
}
protected void changeColor(Stones newD){
mStone = newD;
mImg = BitmapFactory.decodeResource(mRes, mStone.getId());
}
}
I found several similar questions, but it is all about big bitmaps. Also i found some Android documentation about caching images, but i am not sure if it solves my problem and how to share this cache between all my stones.
What is the best practice, to achive good performance and avoid OutofMemoryError?
You probably don't need cache. Since you should have a limited number of stone colors (thus bitmaps) you can consider holding those graphic assets in one single class (probably static global class or through singleton pattern.
In your Stone class, you just need to hold the stone's color Id and get the drawable from your assets class. (you can save bitmap, but drawable is much more efficient and you may easily change it to allow some animation later)
For example:
// Singleton - look at the link for the suggested pattern
public class GraphicAssets {
private Context mContext;
private Hashtable<Integer, Drawable> assets;
public Drawable getStone(int id){
if (assets.containsKey(id)) return assets.get(id);
// Create stone if not already load - lazy loading, you may load everything in constructor
Drawable d = new BitmapDrawable(BitmapFactory.decodeResource(mContext.getResources(), id));
assets.put(id, d);
return d;
}
}
you could make the Bitmap variable static or static final

Setting Android CheckBox to a different image... and then back to the original images

I'm using the following (very common) code to change the checkbox image in my Android app.
mCheck = (CheckBox) findViewById(R.id.chkMine);
mCheck.setButtonDrawable(R.drawable.my_image);
I see many people asking for that. But I never see the second part:
How do I put BACK the original checkbox imagery, later in my code?
I hesitate to try to design all my own images (checked, unchecked, ghosted-checked, ghosted-unchecked, etc) because I need the original images that would normally appear on many different version of Android.
Maybe, initially save the default image with a (non-existing?) getButtonDrawable() call, and then reuse it later?
I would think it would be something as simple as calling setButtonDrawable() a 2nd time to "undo" my changes. Or is it?
Thanks.
As you already correctly mention yourself, unfortunately there is no getButtonDrawable() to get a reference to the drawable used before you replace it. Obviously you could copy Android's resources over to your local project and use those to reset the CheckBox's button, but that would mean you would have to take into account all the different styles from themes, let alone any changes device manufacturers might make to those resources. It's not impossible to go down this lane, but you'll find out it will be quite of a hassle for something that sounds to simple.
What you may want to consider is doing the following: query the resources for resource ids of the drawables that you need. That way you don't explicitly have to deal with different themes, as the lookups will do that. You can easily put this functionality in a dedicated method with just a few lines. Example:
private static int getDefaultCheckBoxButtonDrawableResourceId(Context context) {
// pre-Honeycomb has a different way of setting the CheckBox button drawable
if (Build.VERSION.SDK_INT <= 10) return Resources.getSystem().getIdentifier("btn_check", "drawable", "android");
// starting with Honeycomb, retrieve the theme-based indicator as CheckBox button drawable
TypedValue value = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.listChoiceIndicatorMultiple, value, true);
return value.resourceId;
}
Below a quick example of setting the button drawable to some custom image, and reset it afterwards. I'm simply toggling between the app icon and original button drawable whenever the check state changes.
CheckBox mCheckBox = (CheckBox) findViewById(R.id.checkbox);
mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override public void onCheckedChanged(CompoundButton button, boolean isChecked) {
button.setButtonDrawable(isChecked ? R.drawable.icon : getDefaultCheckBoxButtonDrawableResourceId(StackOverflowActivity.this));
}
});
I think to retrieve the original images a call to setButtonDrawable() should work again.
But instead of your resource you'll reference to android's original resource.
mCheck = (CheckBox) findViewById(R.id.chkMine);
mCheck.setButtonDrawable(android.R.drawable.*);
Probably you've to lookup the file name on your own:
platforms > android-* > data > res > drawable-*
(source: accepted answer)
edit
Ha! Me knew me've seen that site before: Android R Drawables ^^v
(credits to Mef)

Categories

Resources