I'm using RecyclerView as horizontal list to show my images.
If I scroll to the fifth picture, the first two or three are recycled and ViewHolder loses its width. If I scroll back to the first image, the images are loaded again and that leads to jumps while scrolling.
Here is R.layout.fragment_details_view_img_item
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/details_view_img_item"
android:background="#color/red"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ImageView>
My ViewHolder and Adapter:
private class ViewHolder extends RecyclerView.ViewHolder {
ImageView img;
public ViewHolder(ImageView imgV){
super(imgV);
img = imgV;
}
}
private class ImageListAdapter extends RecyclerView.Adapter<ViewHolder> {
[...]
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
View v = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_details_view_img_item, parent, false);
v.setOnClickListener(listener);
logDebug("onCreateViewHolder");
return new ViewHolder((ImageView) v);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
logDebug("onBindViewHolder");
ImageItem item = data.get(i);
if (item != null) {
ImageView imgView = viewHolder.img;
imgView.setTag(item);
String imgurl = ImageUtil.imgUrlForAvailableHeightInPX(item, parentHeight);
ImageLoader.instance().loadASYNC(imgurl, imgView);
}
}
#Override
public void onViewRecycled(ViewHolder holder) {
super.onViewRecycled(holder);
logDebug("onViewRecycled: " + holder.img.getTag());
}
}
So how can I keep ViewHolder's width?
Before I start to approach this problem, one thing needs to be clear. The ViewHolder doesn't have any width, the ImageView it "holds" does have width, and that's what you're trying to control.
Now, considering this, your issue is (after making certain assumptions from looking at your code) that you need to know the width of a certain image when it is at a certain given height and while maintaining aspect ratio - before it arrives from the server.
This is a tricky one.
One option would be to preload all your images. This, however, is very costly with memory and could lead to memory crashes.
A better option would be to load all the images' details, without actually downloading the images' pixels. Then, you'll need to remember the aspect ratio of all the images in some cache, and set the dimension of the image you're loading prior to actually downloading the image contents.
To download an image's dimensions without downloading the image itself, you should use something like this:
public float getImageAspectRatio(InputStream inputStreamFromServer)
{
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStreamFromServer, null, decodeOptions);
final float imageDesiredWidth = decodeOptions.outWidth;
final float imageDesiredHeight = decodeOptions.outHeight;
return imageDesiredWidth / imageDesiredHeight;
}
Once you have this method, you'll need to preload all your images using this function:
private float[] mAspectRatios;
public void decodeAllAspectRatios(List<String> imageUrls)
{
mAspectRatios = new float[imageUrls.size()];
InputStream inputStream;
int index = 0;
for (String url : imageUrls)
{
// Get the input stream from the image url using whatever method you use.
mAspectRatios[index] = getImageAspectRatio(inputStream);
index++;
}
}
Important: Your RecyclerView should not begin working until this method finished working.
Once you have preloaded all your aspect ratios, we go back to your ViewHolder:
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
logDebug("onBindViewHolder");
ImageItem item = data.get(i);
if (item != null) {
ImageView imgView = viewHolder.img;
// set the layout params of the image, making it fit the correct size prior to loading the bitmap.
imgView.setLayoutParams(new LayoutParams(parentHeight * mAspectRatios[i], parentHeight));
imgView.setTag(item);
String imgurl = ImageUtil.imgUrlForAvailableHeightInPX(item, parentHeight);
ImageLoader.instance().loadASYNC(imgurl, imgView);
}
}
So long as you want your RecyclerView to display varying width images depending on their aspect ratio, I believe this is your best option.
Related
I am writing a photo picker for Facebook inside my app, i have a recyclerview with a grid layout and i want to prevent for scrolling up, i was able to do this by using scrollToPosition and this works but not the way i want
Problem
When i click in a photo on the 2 row that row jumps to the top and becomes the number 1 visible row, if i click the 3 row the samething happens.
I don't want the recycler to move if the view is visible it should remain the same, so if i click on a a photo that is on the last visible row i want the scroll to stay the same, i don't want it to make the last row the first.
Tries to solve it
I tried several things to fix this, i tried calling setNestedScrollingEnabled i followed this How to disable RecyclerView scrolling?
public static void onItemClick(int position){
//picker.setNestedScrollingEnabled(false);
for(int k = 0; k<photoBag.size();k++) {
if(k == position)
photoBag.set(position, new PhotoBag(photoBag.get(position).getPhoto(), true)); //Here im marking the photo to selected
else
photoBag.set(k, new PhotoBag(photoBag.get(k).getPhoto(), false));//Here im setting unselecting all the other photos
}
picker.setAdapter(adapter);
adapter.notifyDataSetChanged();
picker.scrollToPosition(position);
//Log.d("FacebookPicker", "position " + grid.findFirstCompletelyVisibleItemPosition());
//picker.setNestedScrollingEnabled(true);
}
I thought that maybe disabling the scroll would lock the recyclerview on the corrent position but it didn't jumps right up.
I also tried getting the Vertical offset and set it after calling notifyDataSetChange but i can't find a way to set the offset programmatically
EDIT
Adapter
class PickerAdapter extends RecyclerView.Adapter<PickerAdapter.PickerAdapterHolder> {
public final String TAG = "PickerAdapter";
private ArrayList<PhotoBag> photoBag;
private Context context;
private OnClickListener onClickListener;
class PickerAdapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView photo;
ImageView imageBorder;
PickerAdapterHolder(View view) {
super(view);
photo = (ImageView) view.findViewById(R.id.photoItem);
photo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.photoItem:
FacebookPhotoPicker.onItemClick(getAdapterPosition()); //i know that there are better ways to get the clicked item from other class but since im still debuging i don't need to worry about performace i just need it to work
break;
}
}
}
PickerAdapter(Context context, ArrayList<PhotoBag> itemList) {
this.photoBag = itemList;
this.context = context;
}
#Override
public PickerAdapterHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.facebook_picker_item, null);
return new PickerAdapterHolder(layoutView);
}
#Override
public void onBindViewHolder(final PickerAdapterHolder holder, final int position) {
if(photoBag.get(position).isSelected()){
int border = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics()));
Bitmap photo = photoBag.get(position).getPhoto();
photo = Bitmap.createScaledBitmap(photo,photo.getWidth() - (border*2), photo.getHeight() - (border*2), false);
photo = addWhiteBorder(photo,border);
holder.photo.setImageBitmap(photo);
}else {
holder.photo.setImageBitmap(photoBag.get(position).getPhoto());
}
}
#Override
public int getItemCount() {
return this.photoBag.size();
}
private Bitmap addWhiteBorder(Bitmap bmp, int borderSize) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bmp.getWidth() + borderSize * 2, bmp.getHeight() + borderSize * 2, bmp.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmp, borderSize, borderSize, null);
return bmpWithBorder;
}
remove those 2 lines from onItemClick
picker.setAdapter(adapter);
picker.scrollToPosition(position);
every time you setAdapter it resets position, and now you don't need to set a new position again.
this should work. If it doesn't, check this answer of mine (and their comments) about providing ID How to remain at a scroll position in RecyclerView after adding items at its first index and call notifydatasetchange
I have a RecyclerView which has a staggeredGridLayoutManager as layout manager. My layout stands as having 2 spans(cols), which items inside may have different heights.
Inflated items has a ImageView and some other views inside a LinearLayout container.
I want to save Inflated(or should I say binded?) View's size(height and width) after the view's image is fully loaded. Because this operation makes me know how much width and height the LinearLayout occupy at final-after the image is placed in the layout-.
After scrolling, this container may be recycled and binded again. What I want to achieve is to savebinded layout's size immediately after it is binded, according to the height and width values previously calculated because this makes recyclerView's item positions more stable. They are less likely move around.
I have mWidth and mHeight members in my ViewHolder, which basically store these values. However, I lost syncronisation between item position in adapter and corresponding ViewHolder. For example I calculate height of 8th item as 380px when it first become visible, which is correct. After recycling and binding 8th position again, my view's height retrieved as 300 px, which is incorrect.
Code:
BasicActivity is derived from Activity..
public ItemsRVAdapter(BasicActivity activity, JSONArray items){
this.items = items;
this.activity = activity;
this.itemControl = new Items(activity);
}
OnCreate:
#Override
public ItemListViewHolders onCreateViewHolder(ViewGroup viewGroup, int i) {
View layoutView =activity.getLayoutInflater().inflate(R.layout.list_element_items, viewGroup, false);
ItemListViewHolders rcv = new ItemListViewHolders(layoutView);
return rcv;
}
OnViewAttachedToWindow (I tried the same code here in different places, like onViewRecycled but I don't know this method is the most right place to calculete the size)
#Override
public void onViewAttachedToWindow(ItemListViewHolders holder)
{
holder.layoutCapsule.measure(LinearLayout.MeasureSpec.makeMeasureSpec(0, LinearLayout.MeasureSpec.UNSPECIFIED), LinearLayout.MeasureSpec.makeMeasureSpec(0, LinearLayout.MeasureSpec.UNSPECIFIED));
if(holder.image.getDrawable() != null){
holder.height = holder.layoutCapsule.getHeight();
holder.width = holder.layoutCapsule.getWidth();
}else{
holder.height = 0;
holder.width = 0;
}
}
onBindViewHolder: Only relevant part. Here I paired position value and my array's member index
#Override
public void onBindViewHolder(ItemListViewHolders holder, int position) {
try {
//JSONObject item = items.getJSONObject(holder.getAdapterPosition());
JSONObject item = items.getJSONObject(position);
holder.image.setImageDrawable(null);
ViewGroup viewGroup = holder.layoutCapsule; //Main Container
...
}
}
I recommend looking for a different approach to resolve your problem with the items moving around not depending on View sizes, but if you want to proceed this way this is my proposed solution:
Don't depend or save the size values on the holder as this gets recycled, you will need to create an object "descriptor" with the values (width and height) for each position and save them on a HashMap or something like that, save the values as you are doing it already, i understand on "onViewAttachedToWindow".
class Descriptor(){
int width;
int height;
void setWidth(int width){
this.width = width;
}
int getWidth(){
return width;
}
void setHeight(int height){
this.height = height;
}
int getHeight(){
return height;
}
Initialize array on constructor:
descriptors = new HashMap<Integer, Descriptor>();
in onBindViewHolder save the position on a view tag to use it on OnViewAttachedToWindow
public void onBindViewHolder(ItemListViewHolders holder, int position) {
....
holder.image.setTag(position);
...
}
populate values on onViewAttachedToWindow
public void onViewAttachedToWindow(ItemListViewHolders holder){
...
int position = (Integer)holder.image.getTag();
Descriptor d = descriptors.get(position);
if(d == null){
d = new Descriptor();
descriptors.put(position, d);
}
d.setWidth(holder.layoutCapsule.getWidth());
d.setHeight(holder.layoutCapsule.getHeight());
...
}
Then use the size data on the descriptor on the method you need getting it by position, you will be creating descriptors as the user is scrolling down, also this works on the asumption that the data maintains the same position during the life of the adapter.
How do you load images from drawable folder into a ListView in a way this happens fast and does not use large amounts of RAM?
NOTE: this post is deprecated, please use the RecyclerView for creating lists
I've been playing around with loading some (quite large) images stored in the drawable folder into a ListView and in this post I'd like to share the result I came to. Maybe (I hope so) this will save someone plenty of time. I've tested the code I'm posting on several Android 4+ devices and I can say that it runs pretty smoothly and the amount of RAM used stays relatively low. Some explanations go as following:
we are extending the BaseAdapter
images will be loaded in background using an AsyncTask
as common for this kind of adapters, we'll be using an ArrayList<> parametrized with Objects of some custom class. In my app, this class is called Weapon
we will scale the images depending on the screen size
we will apply a font to the TextView in each List Row
Feel free to use this code for any purposes and modify it in any way. The only thing I'm asking for is to test the code properly before claiming that something doesn't work. It works, believe me.
If you have noticed any copy-paste-edit mistakes (since I removed some code that is irrelevant for this little tutorial), your feedback is welcome.
Before I post the code, here's a small state diagram demonstrating the logic of the getView() method:
The code for the Adapter class goes below, I've tried to explain everything you need in comments:
public class WeaponAdapter extends BaseAdapter implements View.OnClickListener {
private ArrayList<Weapon> items;
private LayoutInflater inflater = null;
private WeaponHolder weaponHolder;
private Weapon wp;
private Context c;
private Bitmap bmp;
/*--- a simple View Holder class ---*/
static class WeaponHolder {
public TextView text;
public ImageView image, addFav;
public AsyncImageSetter mImageLoader;
}
/*--- Context and all weapons of specified class are passed here ---*/
public WeaponAdapter(ArrayList<Weapon> items, Context c) {
this.items = (ArrayList<Weapon>) items;
inflater = LayoutInflater.from(c);
this.c = c;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Weapon getItem(int position) {
return items.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
/*--- initialize our Weapon Object ---*/
wp = items.get(position);
if (convertView == null) {
/*--- no View is available. Inflate our list item layout and init the Views we need ---*/
convertView = inflater.inflate(R.layout.category_row, null);
weaponHolder = new WeaponHolder();
weaponHolder.text = (TextView) convertView
.findViewById(R.id.tvCatText);
weaponHolder.image = (ImageView) convertView
.findViewById(R.id.imgCatImage);
weaponHolder.addFav = (ImageView) convertView
.findViewById(R.id.imgAddFav);
convertView.setTag(weaponHolder);
} else {
weaponHolder = (WeaponHolder) convertView.getTag();
/*--- if convertView is not null, cancel the current loading operation to
* improve performance and decrease RAM usage ---*/
weaponHolder.mImageLoader.cancel();
}
/*--- load the image in background ---*/
weaponHolder.mImageLoader = new AsyncImageSetter(c, weaponHolder.image,
wp.getImage(), bmp, weaponHolder.text);
weaponHolder.mImageLoader.execute();
weaponHolder.text.setText(wp.getName());
weaponHolder.addFav.setOnClickListener(this);
return convertView;
}
#Override
public void onClick(View v) {
// do any stuff here
}
}
Here's our AsyncTask that will load and set the images in background.
NOTE: my Weapon class has a getImage() method which returns the resId of the drawable corresponding to a Weapon Object. You can modify this part in a way it works for you.
public class AsyncImageSetter extends AsyncTask<Void, Void, Bitmap> {
private ImageView img;
private int image_resId;
private Bitmap bmp;
private Context c;
private boolean cancel = false;
private int sampleSize;
private TextView txtGunName;
private Typeface font;
public AsyncImageSetter(Context c, ImageView img, int image_ResId,
Bitmap bmp, TextView txtGunName) {
this.img = img;
this.image_resId = image_ResId;
this.bmp = bmp;
this.c = c;
this.txtGunName = txtGunName;
}
public void cancel() {
cancel = true;
}
#Override
protected void onPreExecute() {
/*--- we hide the Views from the user until the content is ready. This will prevent
* the user from seeing an image being "transformed" into the next one (as a result of
* View recycling) on slow devices.
*/
img.setVisibility(View.GONE);
txtGunName.setVisibility(View.GONE);
font = Typeface.createFromAsset(c.getAssets(), "b_reg.otf");
super.onPreExecute();
}
#Override
protected Bitmap doInBackground(Void... params) {
if (!cancel) {
try {
return decodeAndScale(bmp);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onPostExecute(Bitmap result) {
img.setVisibility(View.VISIBLE);
try {
img.setImageBitmap(result);
} catch (Exception e) {
/*--- show an error icon in case something went wrong ---*/
img.setImageResource(R.drawable.ic_warn);
}
txtGunName.setVisibility(View.VISIBLE);
txtGunName.setTypeface(font);
super.onPostExecute(result);
}
private Bitmap decodeAndScale(Bitmap bmp) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = setSampleSize();
return BitmapFactory.decodeResource(c.getResources(), image_resId,
options);
}
private int setSampleSize() {
// TODO add multiple screens check
/*--- modify this method to match your needs ---*/
if (GetSettings.getScreenWidth((Activity) c) >= 320) {
/*--- physical width >= 480px ---*/
sampleSize = 2;
}
return sampleSize;
}}
You may have noticed that I use the getScreenWidth() method from the GetSettings class. Its code is quite simple and returns a dp value representing the device's screen width:
public static int getScreenWidth(Activity a) {
Display display = a.getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float density = a.getResources().getDisplayMetrics().density;
float dpWidth = outMetrics.widthPixels / density;
return (int) dpWidth;
}
Well, that's all and I hope this post did help someone. Cheers.
P.S. if you are definitely sure something doesn't work, most likely it was caused by your internal app structure that is different from the one I use. In this case, I recommend you to do following steps:
Ask a new question so you'll be able to add properly formatted code and LogCat output
Notify me by adding a comment to my post. I will be glad to help you figure out what's wrong
Use the listview scroll state to set the images accordingly.
When listview is flinged, don't set images right at the same time, once the state is idle, use your code to set the images.
In this way it will avoid memory allocation for thos views which are not visible.If you try to set images when list view is scrolled, it can cause out of memory error.
Also, make sure that your drawable is having good size to fit in view. Getting images from drawable have always been fast, main concern here will be of memory usage.
Also create an array of drawables to set as your list item, so while setting the adapter you already have a hand full of drawables to be used for list item and do not populate list view with drawables based on condition
I want to make a GridView that contains 75dp width and 75dp height imageView. And also i want this square fill the screen size, with no space in between. Right now i am just using 500 count. So its randomly printing 500 square images. Its not filling up the space. Please check the image below for tablet version.
Here is my Adapter:
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return 500;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some
// attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(75, 75));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(0, 0, 0, 0);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(R.drawable.sample_0);
return imageView;
}
}
In getCount you could divide the area of the screen by the area of the squares you're filling it with.
public int getCount() {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int area = size.x * size.y;
// TODO: you may want to pass in the square dimensions or make them constants
return area / (75 * 75);
}
Of course, this isn't right, since it uses the display and not the parent ViewGroup. Instead of trying to do this all in the adapter, have the constructor for this take in the dimensions of the parent and do the math there, saving an instance variable with the count:
private int numBoxes = 0;
private static final SQUARE_SIZE = 75;
public ImageAdapter(Context c, ViewGroup parent) {
mContext = c;
numBoxes = (parent.getWidth() * parent.getHeight()) / (SQUARE_SIZE * SQUARE_SIZE);
}
public int getCount() {
return numBoxes;
}
Expanding on the answer by Nick White, you need to make sure the parent layout has been laid out before you can get the height and width. If the parent width and/or height are "wrap_content" or "match_parent", the getHeight() and getWidth() will return zero in the adapter's constructor, if the adapter is being created in the Activity onCreate().
This answer gives a way to listen for onLayout events and handle sizing. I did try to do something similar, but I found the results in my case were less than perfect, as the grid would draw then redraw after I applied the size changes. So far the best results I've had with grid sizing is to use qualified resource values to size the image views inside the grid. The resource size value is then set according to the screen density or size by creating various values in values-xxx resource folders.
In my case, I was altering the size of the cell images to make a certain number fit the screen by applying something like:
Integer size = getResources().getInteger(R.int.image_size));
imageView.setLayoutParams(new GridView.LayoutParams(size, size));
It should be possible to do something similar to calculate the number of columns for the screen size.
I'm showing list with one ImageView on every row of list.
For that, I download images from net in another AsyncTask using Drawable.createFromStream
And store them as Drawable in ArrayList which I pass to my Adapter class extending BaseAdapter class.
But the images are taken with high-resolution camera, so may be of very large size.
And I'm getting OutOfMemory error.
So my questions :
What is more efficient, storing images as drawable or as bitmap or any other format?
Am I doing right, by storing all images in memory(in array list). i.e. I'm thinking, once I get a image, I will show it on ImageView and will not store in ArrayList.
is there any way, I can compress the images after download, so they will take less space in memory.
My total code is present here
Android documentation provides a very good example showing how to handle bitmaps in your android app. The example uses an on-disk and in-memory cache and loads the images in the background. By doing so, the main UI thread is not slowed down by loading the images.
Loading Bitmaps effectively
In the example the images are loaded from picasa. It's easy, however, to adapt the example, so that pictures stored locally are used. You simply have to write your own ImageLoader extending from the 'ImageResizer':
public class ImageLoader extends ImageResizer {
public ImageLoader(Context context, int imageWidth, int imageHeight) {
super(context, imageWidth, imageHeight);
}
public ImageLoader(Context context, int imageSize) {
super(context, imageSize);
}
#Override
protected Bitmap processBitmap(Object data) {
return decodeSampledBitmapFromFile((String)data, imageWidth, imageHeight);
}
}
But to answer your question directly: it's ok to load images as Bitmaps. But you have to use a cache and weak references, so that the images can be garbage collected in case they are not visible on the screen. Caching them and using a background task for loading allows for a slick UI.
I don't see any efficiency in storing high-density images into memory - it's totally not recommended to store large ammount of images as bitmaps in memory (good for you that you have a good device ;))
See p.1
Try downscaling the images to fit the device's needs - that's not a simple job though. Also, see View.setTag(Object tag)
The adapter
public class MyImageListAdapter extends BaseAdapter implements ImageLoadingNotifier {
private LayoutInflater inflater = null;
public MyImageListAdapter() {
inflater = LayoutInflater)HomeActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return listImageInfo.size();
}
public Object getItem(int position) {
return listImageInfo.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.list_row, null);
}
TextView tvName = (TextView) vi.findViewById(R.id.tv_name);
TextView tvTime = (TextView) vi.findViewById(R.id.tv_time);
ImageView image = (ImageView) vi.findViewById(R.id.iv_image);
final Button btnDelete = (Button) vi.findViewById(R.id.btn_delete);
image.setImageDrawable(R.drawable.default_placeholder);//set default place-holder
new GetDrawableFromUrl(listImageInfo.get(position), vi).execute();
tvName.setText("Name: " + listImageInfo.get(position).getImage_name());
tvTime.setText("Date: " + listImageInfo.get(position).getDate_created());
btnDelete.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final int position = listView.getPositionForView((View) v.getParent());
positionOgBtnToDelete = position;
Log.v("delete btn clicked", "delete btn no: " + position);
Toast.makeText(HomeActivity.this, "Btn delete position: " + position, Toast.LENGTH_LONG).show();
showAlertToConfirmDelete();
}
});
return vi;
}
}
The AsyncTask GetDrawableFromUrl
public class GetDrawableFromUrl extends AsyncTask<Void, Void, Drawable> {
public ImageInfo imageInfoObj;
private ImageView view;
GetDrawableFromUrl(ImageInfo imageInfo, ImageView view) {
imageInfoObj = imageInfo;
this.view = view;
}
#Override
protected Drawable doInBackground(Void... params) {
try {
return Drawable.createFromStream(((java.io.InputStream) new java.net.URL(imageInfoObj.getImageUrl()).getContent()), "src_name");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Drawable drawable) {
if (drawable != null) {
//imageInfoObj.setImage(drawable);
this.view.setImageDrawable(drawable);
//listImageInfo.add(imageInfoObj); //this one is called when the json is parsed
showImagesInList(); //don't know what it does (??)
}
}
}
The JSON parsing
JSONArray jsonArray = jsonObj.getJSONArray("result");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObjInner = jsonArray.getJSONObject(i);
ImageInfo imageInfo = new ImageInfo();
imageInfo.setImageUrl("http://www.dvimaytech.com/markphoto/" + jsonObjInner.getString("image"));
//new GetDrawableFromUrl(imageInfo).execute(); //don't needed here
imageInfo.setEmail(jsonObjInner.getString("emailid"));
imageInfo.setImage_id(jsonObjInner.getString("image_id"));
imageInfo.setImage_name(jsonObjInner.getString("image_name"));
imageInfo.setAmount(jsonObjInner.getString("amount"));
imageInfo.setImage_description(jsonObjInner.getString("image_description"));
imageInfo.setDate_created(jsonObjInner.getString("date_created"));
listImageInfo.add(imageInfo);
}
And, the use of any kind of List of images becomes unnecesary :)
Instead of starting the async task (GetDrawableFromUrl) when parsing the json objects, you can start the task in getView(...) method. This way you will not be constrained to store the drawables into that ArrayList, since you'll be modifying the ImageView after the image was downloaded. And, by default, you can put a placeholder, until the image is downloaded (or in case there are some network errors).
This way the images will start downloading only when the getView method will be called for that specific item.
The bottom line is that each view from the ListView will keep a reference to it's specific drawable (that was set using vi.setTag(image).
If this helps somehow, you know what to do ;)
There is pretty good library calling AQuery. YOu can use it and simple get all stuff like memory and file caching by writting only 2 line of code. So you even wouldn't need to prepare a drawable, you can call it directly from Adapter.getView() callback.
AQuery aq = new AQuery(rowView);
aq.id(R.id.image).image(url, false, true);
Hope it help you!
From AQuery docs:
Down Sampling (handling huge images)
We are loading a huge image from the network, but we only need the image to be bigger than 200 pixels wide. Passing in the target width of 200 will down sample the image to conserve memory.Aquery will only down sample with power of 2 (2,4,8...) for good image quality and
efficiency.The resulting image width will be between 200 and 399 pixels
String imageUrl = "http://farm6.static.flickr.com/5035/5802797131_a729dac808_b.jpg";
aq.id(R.id.image1).image(imageUrl, true, true, 200, 0);