How to refresh a GridView? - android

I have a GridView which is pretty similar to the Google tutorial, except that I want to add the ImageViews on runtime (via a subactivity). The results are okay, but the layout of the View is messed up: The GridView doesn't fill the content of its parent, what do I have to do to design it properly?
Here the code of adding the children:
public void initializeWorkbench(GridView gv, Vector<String> items) {
Prototype.workbench.setDimension(screenWidth, divider.height()+workbenchArea.height());
Prototype.workbench.activateWorkbench();
// this measures the workbench correctly
Log.d(Prototype.TAG, "workbench width: "+Prototype.workbench.getMeasuredWidth());
// 320
Log.d(Prototype.TAG, "workbench height: "+Prototype.workbench.getMeasuredHeight());
// 30
ImageAdapter imgAdapter = new ImageAdapter(this.getContext(), items);
gv.setAdapter(imgAdapter);
gv.measure(screenWidth, screenHeight);
gv.requestLayout();
gv.forceLayout();
Log.d(Prototype.TAG, "gv width: "+gv.getMeasuredWidth());
// 22
Log.d(Prototype.TAG, "gv height: "+gv.getMeasuredHeight());
// 119
Prototype.workbench.setDimension(screenWidth, divider.height()+workbenchArea.height());
}
}
activateWorkbench, setDimension and measure in the workbench (LinearLayout above the GridView):
public void activateWorkbench() {
if(this.equals(Prototype.workbench)) {
this.setOrientation(VERTICAL);
show = true;
measure();
}
}
public void setDimension(int w, int h) {
width = w;
height = h;
this.setLayoutParams(new LinearLayout.LayoutParams(width, height));
this.invalidate();
}
private void measure() {
if (this.getOrientation() == LinearLayout.VERTICAL) {
int h = 0;
int w = 0;
this.measureChildren(0, 0);
for (int i = 0; i < this.getChildCount(); i++) {
View v = this.getChildAt(i);
h += v.getMeasuredHeight();
w = (w < v.getMeasuredWidth()) ? v.getMeasuredWidth() : w;
}
if (this.equals(Prototype.tagarea))
height = (h < height) ? height : h;
if (this.equals(Prototype.tagarea))
width = (w < width) ? width : w;
}
this.setMeasuredDimension(width, height);
}
The ImageAdapter constructor:
public ImageAdapter(Context c, Vector<String> items) {
mContext = c;
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Something else is wrong. It may be one of many other states, but
// all we need
// to know is we can neither read nor write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
if (mExternalStorageAvailable && mExternalStorageWriteable) {
for (String item : items) {
File f = new File(item);
if (f.exists()) {
try {
FileInputStream fis = new FileInputStream(f);
Bitmap b = BitmapFactory.decodeStream(fis);
bitmaps.add(b);
files.add(f);
} catch (FileNotFoundException e) {
Log.e(Prototype.TAG, "", e);
}
}
}
}
}
And the xml layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="bottom"
android:paddingLeft="0px"
android:paddingTop="0px"
android:paddingRight="0px">
<com.unimelb.pt3.ui.TransparentPanel
android:id="#+id/workbench"
android:layout_width="fill_parent"
android:layout_height="10px"
android:paddingTop="0px"
android:paddingLeft="0px"
android:paddingBottom="0px"
android:paddingRight="0px">
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnWidth="90dp"
android:numColumns="auto_fit"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:stretchMode="columnWidth"
android:gravity="center" />
</com.unimelb.pt3.ui.TransparentPanel>
</LinearLayout>

the GridView has an invalidateViews() method.
when you call this method: "all the views to be rebuilt and redrawn."
http://developer.android.com/reference/android/widget/GridView.html
i think this is what you need:)

You must, first tell the adapter to notify that the data has changed and set the adapter again to the grid
adapter.notifyDataChanged();
grid.setAdapter(adapter);

This may be helpful.
I refresh a gridview of book image thumbnails after a delete is executed on an item.
Using adapter.notifyDataChanged(); as mentioned above didn't work for me as it's called in my adapter.
//this is a call that retrieves cached data.
//your constructor can be designed and used without it.
final Object data = getLastNonConfigurationInstance();
I essentially just reload the adapter and bind it to the same view.
//reload the adapter
adapter = new BooksAdapter(MyBooks.this, MyBooks.this, data, show_collection );
grid.invalidateViews();
grid.setAdapter(adapter);

#flyerz #snagnever
Together you guys have got it. It should be:
adapter.notifyDataChanged();
grid.invalidateViews();
This will flag the adapter that its data has changed, which will then be propagated to the grid whenever after the invalidateViews() method is called.
Glad I found this question because I could not figure out how to add items to the grid after its been rendered.

None of these answers actually worked for me and I had to mash all of them together. To actually get the GridView to update, you need to do this:
adapter.notifyDataChanged();
grid.invalidateViews();
grid.setAdapter(adapter);
Hope this helps anyone who couldn't get the other solutions to work.

You should not call invalidateViews and setAdapter to refresh your grid view. This is not a good idea to keep your grid view updated, if you update in that way it would cost a lot of time and memory.
If you have a chance to look at getView method you will see that convertView is created just once. When you call notifyDataChanged, it will update this view. Whereas if you call invalidateViews, previously created views will be recreated. This is not a good solution.
Your getView method is called when you call notifyDataChanged. So your getView method should look something like the code below.
public List list;
public class SimpleItemViewHolder extends Object
{
public TextView textView;
public ImageView imageView;
}
public View getView(int position, View convertView, ViewGroup parent){
View itemView = convertView;
SimpleItemViewHolder viewHolder;
if(convertView==null)
{
viewHolder = (SimpleItemViewHolder)itemView.getTag();
}else{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.layout_name, null);
TextView labelField = (TextView) itemView.findViewById(R.id.label_field);
labelField.setText(list.get(position).Name);
//Typeface boldFont = Typeface.createFromAsset(context.getAssets(), "fonts/Font-Bold.ttf");
//labelField.setTypeface(boldFont);
ImageView imageView = (ImageView) itemView.findViewById(R.id.image_view);
//Bitmap bitmap = init your bitmap here;
//imageView.setImageBitmap(bitmap);
viewHolder = new SimpleItemViewHolder();
viewHolder.imageView = imageView;
viewHolder.textView = labelField;
itemView.setTag(viewHolder);
}
//don't create new views, instead use previous ones and update them.
viewHolder.textView.setText(list.get(position).Name);
//viewHolder.imageView.setImageBitmap(your_bitmap);
return itemView;
}

adapter.notifyDataChanged();
may not works just because data for the adapter is stale (if Activity or fragment was not destroyed and have been sitting in the back stack for instance).
So, if firstly refresh data, then after it will be working.

You are placing the GridView inside com.unimelb.pt3.ui.TransparentPanel which is 10px tall.
You shouldn't use px, you should use dp.
Change com.unimelb.pt3.ui.TransparentPanel's android:layout_height="10px" to android:layout_height="fill_parent"

In your adapter :
class MAdapter extends BaseAdapter{
List<Objects> mObjects;
...
public void clearAdapter(){
mObjects.clear();
}
public void addNewValues(List<Objects> mObjects){
this.mObjects = mObjects;
}
...
}
adapter.clearAdapter(); // clear old values
adapter.addNewValues(MObjects);
adapter.notifyDataChanged();

Related

How to keep ViewHolder's width after view was recycled

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.

how do you efficiently load bitmaps from drawable folder into a ListView?

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

Image resets to default after scrolling - Android Custom View

So I've been creating a custom view similar to GridView. It loads and scrolls through images just fine when they are resources inside the app but now that I'm using images coming in through an HTTP request, the images aren't loading correctly.
When the app starts: all images are set to the default (bad)
After scrolling past that cell and immediately scrolling back to it: image loads correctly (good)
After scrolling back to that same cell sometime later: image was set back to the default (bad)
Does anyone have any ideas of what could be causing this error? I assume it's some kind of recycling issue but I haven't been able to fix it.
Here is my xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingBottom="1dip"
android:background="#color/white"
android:id="#+id/highlight_counter_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:gravity="center" >
<ImageView
android:id="#+id/catalog_image"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<View
android:id="#+id/solid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/black" />
<View
android:id="#+id/text_gradient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#id/solid"
android:background="#drawable/highlight_text_gradient" />
<TextView
android:id="#+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="#dimen/highlight_text_padding"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="2"
android:textColor="#color/white"
android:textSize="#dimen/text_large" />
</RelativeLayout>
Here is an excerpt from my adapter (where I think the problem probably lies):
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final LayoutParams lp;
int viewType = getItemViewType(position);
ImageView img;
PulseTextView title;
Resources res = getContext().getResources();
int height = (int) res.getDimension(R.dimen.icon_main_size);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.get().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.element_item, parent, false)
LayoutParams layp = new LayoutParams(height);
convertView.setLayoutParams(layp);
}
img = ViewHolder.get(convertView,R.id.catalog_image);
title = ViewHolder.get(convertView,R.id.title);
final CatalogItem channel = getCatalogItem(position);
// find the url of the associated image then set image
String url = null;
try {
url = mCatalogHandler.getImageUrl(CatalogHandler.VALUE_ICON, channel.mPrimaryKey, 100, 100);
} catch (CatalogException e) {
e.printStackTrace();
}
if (url == null || TextUtils.isEmpty(url) || url.equals("null")) {
img.setImageBitmap(mDefaultPic);
} else {
// downloads the image to img
mImageDownloader.download(url, img, mDefaultPic, false);
}
title.setText(channel.mDomain);
img.setScaleType(ScaleType.FIT_XY);
img.setTag(RAMImageCache.KEY_URL, url);
// set the gradient behind the text
View grad = convertView.findViewById(R.id.text_gradient);
ViewUtils.setHeight(grad, height * 3 / 5);
grad.getBackground().setDither(true);
View solid = convertView.findViewById(R.id.solid);
ViewUtils.setHeight(solid, height / 5);
// set the padding based on the position on the screen
DisplayMetrics displaymetrics = new DisplayMetrics();
((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int width = displaymetrics.widthPixels;
if (convertView.getRight() == width && convertView.getLeft() == 0) {
convertView.setPadding(0, 0, 0, 1);
} else if (convertView.getRight() == width) {
//convertView.setPadding(1, 0, 0, 1);
convertView.setPadding(0, 0, 1, 1);
} else if (convertView.getLeft() == 0) {
//convertView.setPadding(0, 0, 1, 1);
convertView.setPadding(1, 0, 0, 1);
}
// set the onclicklistener to jump to the next fragment
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putString("channelitem", channel.getMetadata().toString());
ChannelFragment fragment = new ChannelFragment();
fragment.setArguments(bundle);
((PulseFragmentActivity)mContext.get()).openFragment(fragment);
}
});
return convertView;
}
static class ViewHolder {
ImageView img;
TextView title;
public ViewHolder(ImageView i, PulseTextView t) {
img = i;
title = t;
}
public static <T extends View> T get(View view, int id) {
SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
view.setTag(viewHolder);
}
View childView = viewHolder.get(id);
if (childView == null) {
childView = view.findViewById(id);
viewHolder.put(id, childView);
}
return (T) childView;
}
}
Any point in the right direction would help greatly! Let me know if there are any other code snippets you would need to see.
I suggest one way to debug is to try another image download library to find out if the error is in your code. I used https://github.com/koush/UrlImageViewHelper and it works well when cell is reused, and its API is similar to what you used now
The behaviour very much depends on what and how does your mImageDownloader handles the downloaded image.
In most cases LRU Cache implementation might be used to store your downloaded image and this cache has a maximum value of bytes assigned. Once your cached image exceeded this value, the old bitmaps will be discarded, hence why you are seeing default image and you need to re-download it.
My suggestion is after you have downloaded the image, scale it as small as you can, so that you can cache as much bitmaps as you could.
There is no way Android could cache up all your downloaded images due to memory restriction.

Creating hierarchical listFragment in Android

I am building a forum-ish app, and need to display the forumposts in a hierarchical manner so that people can easily see which post are responding to (children of) which. Each forumpost has its own object, and this object contains an (int) depth variable which is intended to use for defining its position related to its parent.
I have a BroadcastReciever which takes in the data and sets a listadapter based on the data.
My plan was now to use getListAdapter().getChildAt(position).setLeft(pixels); method for each entry, however i get a nullpointerexception at the setLeft method. My code for this is:
#Override
public void onReceive(Context context, Intent intent) {
String jsonEmner = intent
.getStringExtra(RestService.PARAM_OUT_MSG);
emner = gson.fromJson(jsonEmner, EmneItem[].class);
Log.e(this.getClass().getSimpleName(), jsonEmner);
populateEmner();
int firstID = getListView().getFirstVisiblePosition();
int listSize = getListView().getCount();
for (int count = firstID; count < listSize; count++) {
EmneItem e = (EmneItem) getListView().getItemAtPosition(count);
getListView().getChildAt(count).setLeft(e.getDepth());
}
}
This code is inside the BroadCastReciever method inside the ListFragment class. I was hoping someone could point me in the right direction how to do this :)
I feel a bit stupid answering my own question, but I managed to find a workaround in case anyone gets in a similar situation.
I created my own custom ArrayAdapter, and an XML file which defines the layout for the rows. ArrayAdapter has a getView() method which lets you customize each entry, and based on my entries "depth" variable, i set the textviews LayoutParams.
In case anyone wants to see how its done:
class CustomAdapter extends ArrayAdapter<EmneItem> {
CustomAdapter() {
super(getActivity(), R.layout.row, emner);
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
//Reusing convertView if possible, if not then inflating a new
if(row == null) {
LayoutInflater inflater = getActivity().getLayoutInflater();
row = inflater.inflate(R.layout.row, parent, false);
}
TextView postedby = (TextView) row.findViewById(R.id.postedbytextview);
postedby.setText("Posted by: " + emner[position].getPosted_by());
TextView argument = (TextView) row.findViewById(R.id.argumentTextView);
argument.setText(emner[position].getArgument());
TextView procontratextview = (TextView) row.findViewById(R.id.procontratextview);
int procontra = emner[position].getProcontra();
if (procontra == EmneItem.PRO) {
procontratextview.setText("PRO");
}
if (procontra == EmneItem.CONTRA) {
procontratextview.setText("CONTRA");
}
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if(emner[position].getDepth() == 1) {
llp.setMargins(10, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
} else if (emner[position].getDepth() == 2) {
llp.setMargins(20, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
} else if (emner[position].getDepth() == 3) {
llp.setMargins(30, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
} else if (emner[position].getDepth() >= 4) {
llp.setMargins(40, 0, 0, 0);
procontratextview.setLayoutParams(llp);
argument.setLayoutParams(llp);
}
return (row);
}
}
This is mostly based on an article i found at http://commonsware.com/Android/excerpt.pdf
Cheers

Android: ListView with a Custom ArrayAdapter acting up on long lists -- Timing issue!

HI all,
I have this "search results" ListView.
The search results can be of different "kinds" (different sections, call it).
To separate the "kinds" I add a row with a title. (I know about the expandable list, but can't use it for other reasons).
In my getView(), I check for a property, and if it's set, I change the background color of the row.
The problem: when I run a query that returns just a few rows (say 15), everything is fine. But when I run another that returns, say 600 rows, something goes wacko and it changes the background randomly, at a somewhat regular interval. Same thing happens when I'm running in debug mode and stop things in the middle.
So, it's definitely a timing issue.
I'm thinking this might be due to having to re-render the big list as the on-screen keyboard closes.
So, is the Adapter to blame? Is there any solution for this?
If the keyboard is the problem, is there a mechanism to tell the list "wait until the thing closes" before start rendering? (Not sure I like that, but it's better than getting a cute little rainbow...)
Thanks!
Llappall
--
Here's the adapter and the element layout (below):
private class ElementAdapter extends ArrayAdapter<Element> {
private ArrayList<Element> rows;
private Element.typeEnum type;
public ElementAdapter(Context context, int textViewResourceId, ArrayList<Element> rows) {
super(context, textViewResourceId, rows);
this.rows = rows;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.element, null);
}
Element row = rows.get(position);
if (row == null) {
return v;
}
v.setTag(row);
type = row.getType();
boolean isSectionType = type == Element.typeEnum.DIV118SECTION || type == Element.typeEnum.APPASECT ||
type == Element.typeEnum.APPBSECT || type == Element.typeEnum.AZSECT;
TextView title = (TextView) v.findViewById(R.id.title);
TextView body = (TextView) v.findViewById(R.id.body);
if (isSectionType) {
body.setMaxLines(5000);
}
title.setText(row.getTitle());
if (row.getBody() != null) {
body.setText(row.getBody());
}
if (type == Element.typeEnum.SEARCHLISTHEADER) {
v.setBackgroundColor(Color.rgb(230, 230, 250));
title.setBackgroundColor(Color.rgb(230, 230, 250));
body.setBackgroundColor(Color.rgb(230, 230, 250));
star.setBackgroundColor(Color.rgb(230, 230, 250));
}
return v;
}
}
==ELEMENT LAYOUT==
<TextView
android:id="#+id/body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
style="#style/ListItemSubTitle" />
</LinearLayout>
It would be much easier if you would post a getView() method here.
From what I can tell, you might be using recycled views wrong.
Check if background is changed to something if property is not set.
For example:
if (peoperty.isSet()) {
changeBackGround();
}
Just by itself will be wrong if you are reusing convertView, since the background will stay the same color how it was, when this view was used for a different row.
must be something like:
if (peoperty.isSet()) {
changeBackGround();
} else {
changeBackgroundToSomethingNeutral()
}
I took a liberty to rewrite that code for you, since you make too much weird stuff. Here's what I think is a optimized working code for your situation (didn't test any of it, but should work):
private class ElementAdapter extends ArrayAdapter<Element> {
public ElementAdapter(Context context, int textViewResourceId, ArrayList<Element> rows) {
super(context, textViewResourceId, rows);
this.rows = rows;
}
private final ArrayList<Element> rows;
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
ViewsHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.element, parent, false);
holder = new ViewsHolder();
holder.title = (TextView) v.findViewById(R.id.title);
holder.body = (TextView) v.findViewById(R.id.body);
convertView.setTag(holder);
} else {
holder = (ViewsHolder) convertView.getTag();
}
final Element row = rows.get(position);
final Element.typeEnum type = row.getType();
if (type.equals(Element.typeEnum.DIV118SECTION) || type.equals(Element.typeEnum.APPASECT) ||
type.equals(Element.typeEnum.APPBSECT) || type.equals(Element.typeEnum.AZSECT)) {
body.setMaxLines(5000);
}
holder.title.setText(row.getTitle());
if (row.getBody() != null) {
holder.body.setText(row.getBody());
} else {
holder.body.setText("");
}
if (type == Element.typeEnum.SEARCHLISTHEADER) {
convertView.setBackgroundColor(Color.rgb(230, 230, 250));
holder.title.setBackgroundColor(Color.rgb(230, 230, 250));
holder.body.setBackgroundColor(Color.rgb(230, 230, 250));
//star.setBackgroundColor(Color.rgb(230, 230, 250)); // Where did that come from?
}
return convertView;
}
private final class ViewsHolder {
public TextView title;
public TextView body;
}
}
Couple of notes on the original code:
if (row == null) {
return v;
}
is wrong. You shouldn't have any null elements in your list for any position in the list. Even if you have, you shouldn't just throw some random view for the row. What you are doing here, is returning "v", that can very well be (and probably will be) some recycled old row, that still displays old data, and that's going to confuse the user. I made an assumption that you won't have any empty elements when wrote the code.
if (row.getBody() != null) {
body.setText(row.getBody());
}
Is almost ok, but again, if you are reusing convertView (which is some random previous row that isn't displayed anymore), then if body is actually null you will just be displaying the old data, which again will confuse the user. If body is null, just set the string empty.
P.S.
I recommend you to watch this for tips and tricks about how to work with ListView: The world of ListView

Categories

Resources