Android - GridView forcing update on data change - android

I've implemented this sample (2. Custom Adapter example)
http://www.mkyong.com/android/android-gridview-example/
and it works.
I then wanted to "refresh" the data with a new set, which didn't work until I did this:
imageAdapter.notifyDataSetChanged();
and then removed the following check in the ImageAdapter getView method :
if (convertView == null) {
Here is my current getView method
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
// if (convertView == null) { // stopped my GridView from updating !!
if (true)
{
gridView = new View(context);
// get layout from mobile.xml
gridView = inflater.inflate(R.layout.mobile, null);
// set value into textview
TextView textView = (TextView) gridView
.findViewById(R.id.grid_item_label);
textView.setText(mobileValues[position]);
// set image based on selected text
ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.square);
} else {
gridView = (View) convertView;
}
return gridView;
}
I'm concerned it is now doing unnecessary processing over and over - i.e. inflating view multiple times?
Should I be creating a new GridView every time this is called ?

it didn't work because the following rows
TextView textView = (TextView) gridView
.findViewById(R.id.grid_item_label);
textView.setText(mobileValues[position]);
// set image based on selected text
ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.square);
go outside the if/else. The check if (convertView == null) is necessary. If you don't have it you will inflate n different views, with n == getCount(). For n large it will be a problem. And you probably want to implement the android ViewHolder pattern, to give your users the best UX possible.
As correctly pointed out by #Vzsg, get rid also of gridView = new View(context);. It an additional useless allocation

I usually just update the adapter with the new results, and set it to the GridView.
So in your example, when I want to update my GridView - I do the following.
gridView.setAdapter(newAdapter);
You can have a utility method, such as that will make get a new Adapter much easier.
private ArrayAdapter getAdapter(String [] data){...}

Related

Change Listview item background

As Adapter for ListView, I am using custom adapter. On Adapter's getView method, I am trying to change listitem's background image using setBackgroundDrawable.
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
CurrencyModel currency = new CurrencyModel();
currency = currencyList.get(position);
if (convertView == null) {
vi = inflater.inflate(R.layout.listitem_currency, null);
}
TextView tvSymbol = (TextView) vi.findViewById(R.id.tvSymbol);
TextView tvSize = (TextView) vi.findViewById(R.id.tvSize);
TextView tvName = (TextView) vi.findViewById(R.id.tvName);
TextView tvRate = (TextView) vi.findViewById(R.id.tvRate);
tvSymbol.setText(currency.getSymbol());
tvSize.setText(currency.getSize());
tvName.setText(currency.getName());
tvRate.setText(currency.getRate());
if (currency.getSymbol().equals("AUD")) {
vi.setBackgroundDrawable(context.getResources().getDrawable(
R.drawable.bg_exch_high_cell));
}
return vi;
}
When activity starts, everything is working correctly - listitem with AUD has different background. All mystery starts when I scroll the listview - other listitems also get "special" background. The more scroll, the more listitems changed. I do not have idea why this is happening. How to solve this problem?
if (currency.getSymbol().equals("AUD")) {
vi.setBackgroundDrawable(context.getResources().getDrawable(
R.drawable.bg_exch_high_cell));
}
else {//restore default background}
This happens because cell views are reused by listview. You should restore default background for other items.
More about list view performance
TextView tvSymbol = (TextView) vi.findViewById(R.id.tvSymbol);
Before this line add the default background the you put to the item. The reason in your code why it happens is that android try using the the already created view(vi). Since if it is assigned bg_exch_high_cell, it will retain it. So reset it in the beginning.
vi.setBackgroundDrawable(context.getResources().getDrawable(
//default background drawable here ));
TextView tvSymbol = (TextView) vi.findViewById(R.id.tvSymbol);

How to save and maintain ListView items states after scroll?

After looking for some answers here, I find myself in a disturbing situation where my Listview is really getting on my nerve.
Here are the questions I looked for :
Maintain ListView Item State
How to save state of CheckBox while scrolling in ListView?
I'm using a custom adapter with a custom row as below.
My Listview is simple as it is displaying a custom row made of three elements :
1) an ImageView displaying contact picture cropped in a circle ;
2) a TextViewdisplaying the contact full name as plain text ;
3) and finally an ImageView that holds the purpouse of a CheckBox.
Please focus on the last element. The ImageView CheckBox-like will have its src changed upon click.
When the user click, the ImageView will switch between a check sign and an unchecked sign according to it's previous status. Possible status are : {checked | unchecked}
So far so good.
But as soon as I scroll the ListView, any aforementioned change will disappear as Android recycle unused view.
Here comes the so-called ViewHolder pattern. Unfortunately, this pattern is failling me on two issues :
First, when scrolling, my organized-in-an-alphabetical-order listview gets disorganized.
e.g. somehow, whitout any reason, the first displayed contact name gets displayed again later on the ListView as I scrolled. That can happen with any row ! So it would seem unused view are being wrongly re-used.
Second, and in accordance to the first issue, the checked status do seem to stay, but not always and if it does stay, it may very well stay on the wrong row ... and that can happen randomly, of course. Therefore ViewHoder is not a viable solution.
Before discouvering the ViewHolder pattern, I have been using a HashMap to store the item position upon click as followed :
ContactsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public final void onItemClick(final AdapterView<?> adapterView, final View view,
final int position, final long id) {
final ImageView check = (ImageView) view.findViewById(R.id.checkImage);
final TextView name = (TextView) view.findViewById(R.id.contactName);
final Boolean isChecked = Boolean.valueOf(checkedContactsList.isChecked(position));
if (isChecked != null && isChecked.booleanValue() == true) {
check.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.unchecked_sign));
checkedContactsList.(position);
} else {
check.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.checked_sign));
checkedContactsList.add(position, true);
}
}
});
I tried adding a different value instead of position.
I tried with ContactsListView.getPositionForView(view)
And I also tried with the View's ID, but still it doesn't work.
I wish I could use ContactsListView.getSelectedItemPosition() but it returns -1 as there is no selection event because I'm handling a touch/click event.
And this is how my Custom Adapter looks like :
public final View getView(final int position,
final View convertView, final ViewGroup parent) {
final LayoutInflater inflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View contactRowView = inflater.inflate(R.layout.contact_row, parent, false);
final ImageView contactPic = (ImageView) contactRowView.findViewById(R.id.contactPic);
final TextView contactName = (TextView) contactRowView.findViewById(R.id.contactName);
final ImageView checkImage = (ImageView) contactRowView.findViewById(R.id.checkImage);
// the list is the same as above and therefore contains the exact same entries
if (this.checkedContactsList.isChecked(position))
checkImage.setImageDrawable(this.context.getResources().getDrawable(R.drawable.checked_sign));
contactPic.setImageBitmap(cropePictureInCircle(this.contacts.get(position).getPicture()));
contactName.setText(this.contacts.get(position).getName());
return contactRowView;
}
Is there a good way to keep the checked row checked and the unchecked row unchecked in the given alphabetical order ?
Thanks !
For the list position change I know the solution but for the second problem I am still searching for a solution, anyway first make a viewHolder class;
public class ViewHolder{
//put all of your textviews and image views and
//all views here like this
TextView contactName;
ImageView checkImage;
ImageView contactImage;
}
Then edit your adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
final View contactRowView = convertView;
ViewHolder holder;
if (contactRowView == null) {
final LayoutInflater inflater = (LayoutInflater)
this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE
);
contactRowView =
inflater.inflate(R.layout.contact_row, parent,
false);
holder = new ViewHolder():
holder.contactPic = (ImageView)
contactRowView.findViewById(R.id.contactPic);
holder.contactName = (TextView)
contactRowView.findViewById(R.id.contactName);
holder.checkImage = (ImageView)
contactRowView.findViewById(R.id.checkImage);
contactRowView.setTag(holder);
} else {
holder = contactRowView.getTag();
// the list is the same as above and therefore contains the exact same entries
if (this.checkedContactsList.isChecked(position))
holder.checkImage.setImageDrawable(this.context.get.
Resources().getDrawable(R.drawable.checked_sign));
holder.contactPic.setImageBitmap(cropePictureInCircle(this.contacts.get(position).getPicture()));
holder.contactName.setText(this.contacts.get(position).getName());
return contactRowView;
}
}
Hope this helps and sorry because writing code from my phone is totally awkward.

How to delete previous List contents and add new contents?

I have a list that contain 10 images and name. On clicking next button its showing 10 images with name, but its showing previous 10 images also (total 20 images).
I want to display only the present 10 images and want to delete the previous 10 images from the ListView. How can i do this?
I have tried myArrayList.clear(); arrayList.remove();, adapter.clearListView(); and listView.removeAllView(); but still i am getting 20 images (10 present images and 10 previous image).
Please suggest some solution with sample code.
My code is
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder view;
LayoutInflater inflator = activity.getLayoutInflater();
if(convertView==null)
{
view = new ViewHolder();
convertView = inflator.inflate(R.layout.image_detail_view, null);
view.txtViewTitle = (TextView) convertView.findViewById(R.id.textView1);
view.imgViewFlag = (ImageView) convertView.findViewById(R.id.imageView1);
convertView.setTag(view);
}
else
{
view = (ViewHolder) convertView.getTag();
}
listImage.clear();
listText.clear();
notifySetDataChange();
return convertView;
}
and i am creating list view in another class and calling adapter in above code..
i have tried to clearing listView also..
Images and names are from server and they are dynamic in nature..
You cant clear list data in getView method. You have to clear from listAdapter. Then it will work correctly.
You can deal only with adapter. Reset data in adapter and call adapter.notifyDataSetChanged().
UDP:
getView() is invoked by system every time it needs a view for row. So, don't change a list with data and call notifyDataSetChanged() in this method.
class CustomAdapter extends ArrayAdapter {
private List<YourItem> items;
public void setItems(List<YourItem> newItems) {
items = newItems;
notifyDataSetChanged();
}
public View getView(View convertView, int position, ...) {
if (convertView == null) {
// your code
} else {
// your code
}
YourItem item = items.get(position);
// set image to convertView
return convertView;
}
}
So, call setData() with your new items every time you want to update a ListView.
Are there any more questions?

Android ListView Layout inflater

We are working with list views in college at the moment. My lecturer gave us a simple application that displays mail messages in a list and when the user selects one it displays the content of the message in a new activity. I understand pretty much all of what is going on but there are a few grey areas I want to clear up!
Basically I am wondering what this section of code does?
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.inbox_row, null);
}
This method is located within a class that extends ArrayAdapter. Am I right in thinking that it is some form of recycling? for when views go on and off the screen?....
Any help is much appreciated. thanks.
it's exactly what you said, a form of recycling.
Inflating a layout takes a lot of memory and a lot of time, so for the efficiency sake, the system passes to you that just went off the screen and you can simply update its text and images and give them back to the UI.
So for example, if your list view is showing 6 items on its list (due to the height of it), it will only inflate 6 items and during scroll it just keeps recycling them.
there's some extra optimisations tricks that you should use and I'm sure that the video link that the commenter posted will explain them.
edit
that example is an ArrayAdapter of Store items, but you can make it to whatever you need.
the adapter does the match and separation layer between UI and data.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = newView();
// Store is the type of this ArrayAdapter
Store store = getItem(position);
Holder h = (Holder) convertView.getTag();
// And here I get the data and address them to the UI
// as you can see, if the convertView is not null,
// I'm not creating a new one, I'm just changing text, images, etc
h.storeName.setText(store.getStoreName());
h.address.setText(store.getAddressLine1());
h.postcode.setText(store.getPostCode());
h.distance.setText(store.getDistance());
return convertView;
}
// I like to separate in a different method when the convertView is null
// but that's me being organisation obsessive
// but it also makes easy to see which methods are only being called the 1st time
private View newView() {
LayoutInflater inf = LayoutInflater.from(getContext());
View v = inf.inflate(R.layout.map_result_list, null);
Holder h = new Holder();
// here we store that holder inside the view itself
v.setTag(h);
// and only call those findById on this first start
h.storeName = (TextView) v.findViewById(R.id.txtLine1);
h.address = (TextView) v.findViewById(R.id.txtLine2);
h.postcode = (TextView) v.findViewById(R.id.txtLine3);
h.distance = (TextView) v.findViewById(R.id.txtDistance);
return v;
}
// this class is here just to hold reference to the UI elements
// findViewById is a lengthy operation so this is one of the optimisations
private class Holder {
TextView storeName;
TextView address;
TextView postcode;
TextView distance;
}

How to order elements in Grid View

I have a Grid View adapter where all is in this code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v = convertView;
CheckBox check = null;
ImageView pic = null;
if (convertView == null) { // if it's not recycled, initialize some
// attributes
LayoutInflater vi = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.check_box_and_imageview, null);
}
pic = (ImageView)v.findViewById(R.id.imageView);
check = (CheckBox) v.findViewById(R.id.checkBox);
check.setTag(position);
ImageLoader loader = new ImageLoader(mContext);
//this loads image from given url
loader.DisplayImage(url[position], pic);
return v;
}
Here i create a view with check box and image view. There is the problem because image view is different size and gridview make aligment by both size checkbox and image view. But i want to order element by check box.
For better explanation what i want to do will show this pic:
This i want to do. How ever my image views isn't all same size. Now i will illustrate how it looks:
So this don't suite me. I want to make alignment not all checkbox and imageview. But just that check boxes would be under checks boxes.
So isn't this possible to do with gird view ?
Check if the convertView is null:
If it is null it's time to inflate a new View for this GridView's element. You could also use the holder pattern to cache looking for the composing Views(instead of searching with findViewById everytime)(You use the setTag element for the inflated View but it's a piece of data from the Node data element ?!? What do you plan to do with it?!?)

Categories

Resources