BaseExpandableListAdapter's strange behavior - android

I have an ExpandableListView and I'm using an adapter to inflate groups and children rows from layout XMLs. In my child XML I have 3 text views: name, notes and amount. What I want is:
if child's object amount > 1 display it in the appropriate text view; set notes text view's text to child's details field, but if the details string is empty - make the view disappeared.
So I had this code in my adapter:
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final ChildHolder child_holder;
if(convertView == null)
{
convertView = LayoutInflater.from(context).inflate(R.layout.row_selected_item_packed, null);
child_holder = new ChildHolder();
child_holder.tv_si_name = (TextView) convertView.findViewById(R.id.tv_packed_item_name);
child_holder.tv_si_notes = (TextView) convertView.findViewById(R.id.tv_packed_item_notes);
child_holder.tv_si_amount = (TextView) convertView.findViewById(R.id.tv_packed_item_amount);
child_holder.img_packed = (ImageView) convertView.findViewById(R.id.img_packed);
convertView.setTag(child_holder);
} else {
child_holder = (ChildHolder) convertView.getTag();
}
final Selected_Group selected_group = arr_selected_groups.get(groupPosition);
final Selected_Item selected_item = selected_group.getArr_selected_items().get(childPosition);
if(selected_item!=null)
{
child_holder.tv_si_name.setText(selected_item.getItem_name());
//Here starts the problem
child_holder.tv_si_notes.setText(selected_item.getSelected_item_details());
if(selected_item.getSelected_item_details().equals(""))
{
child_holder.tv_si_notes.setVisibility(View.GONE);
}
if(selected_item.getSelected_item_amount()>1)
{
child_holder.tv_si_amount.setText(String.valueOf(selected_item.getSelected_item_amount()));
}
if(selected_item.isSelected_item_packed())
{
child_holder.img_packed.setImageDrawable(context.getResources().getDrawable(R.drawable.check_box_packed));
} else {
child_holder.img_packed.setImageDrawable(context.getResources().getDrawable(R.drawable.check_box_unchecked));
}
}
return convertView;
}
class ChildHolder{
TextView tv_si_name;
TextView tv_si_notes;
TextView tv_si_amount;
ImageView img_packed;
}
Currently I only have 1 child that has both amount>1 (all the rest have 1) and details. But here's what happens:
All the notes text views are gone. If I remove the if statement - they appear and the text is set in the appropriate child.
First I have the amount text exactly in the right child. However if I close the group and reopen it - suddenly another child has that same amount (and if the if statement from #1 is gone - then the details are also being duplicated to that child. If I open another group - I'll see the amount on one of its children too. So eventually if I keep opening and closing groups I'll have amount in every child's amount text view.
Why is it happening and how can it be fixed?

try like this way..
child_holder.tv_si_notes.setVisibility(View.GONE);
if(selected_item.getSelected_item_details().equals(""))
{
child_holder.tv_si_notes.setVisibility(View.GONE);
}
else
{
child_holder.tv_si_notes.setVisibility(View.VISIBLE);
}

Related

Are convertViews being reused in a ListView?

I am using this code to layout my ListView, using a different layout based on some data:
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
ViewHolder viewHolder;
MyInfo myInfo = getItem(i);
String label = myInfo.getLabel();
if (convertView == null) {
if (!"".equals(label)) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.info_grey, null);
Log.d(SapphireApplication.TAG, "GREY, label=" + label);
} else {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.info_plain, null);
Log.d(SapphireApplication.TAG, "PLAIN, label=" + label);
}
viewHolder = new ViewHolder();
viewHolder.tvLabel = convertView.findViewById(R.id.tvLabel);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)convertView.getTag();
}
viewHolder.tvLabel.setText(label);
return convertView;
}
However, the Log.d is never done for some items in the list. Does that mean Android re-uses an existing convertView, causing it (in this case) to use the wrong layout?
Yes. They are being re-used. And that is the reason you are seeing that message log for few items only.
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
if (convertView == null) {
// convertView is null. It means the ListView does not have a view to give to you
// This way, you need to create a new one.
// You will enter here until the ListView has enough Views to fill
// the screen.
// So, just inflate the view and set the View holder here.
// Don't customize your view here (set text, contenet etc)
// So, any log message here will be printed only when the ListView becomes visible (and when you scroll to next item)
// After that, views will be re-used so convertView will no longer be null
} else {
// ListView gave a convertView to you. It means that you are receiving a View
// that was created in the past and it is be re-used now.
// At this moment, convertView still has the content of the old item it was
// representing.
// This view was created in the statement above and after user scrolled the ListView
// it becomes hidden and ready to be re-used.
// Don't customize the view here.. just get the ViewHolder from the View
}
// Here you customize the View. Set content, text, color, background etc
// The ViewHolder is just a helpful class to help you to access
// all View inside the convertView without needing to perform the
// findViewById again.
return convertView;
}
In this basic example however, all convertViews are similar. They were inflated from the same layout.
This works fine for when you have a single view type per line. all items are similar but with different content.
This still works if you have small differences. For example, you can inflate same layout and control the visibility of some of its Views according to the position (position 1 has an image and position 2 don't).
However, there are some cases where you really need to inflate different layouts per row.
For example, the "grey" layout is very different from the "plain" layout. On this case, you need to update you
code as follows:
private static final int GREY = 1;
private static final int PLAIN = 2;
private static final int TOTAL_VIEW_TYPES = 2; // Grey and Plain
#Override
public int getViewTypeCount() {
// Tell the list view that you have two types of Views (Grey and plain)
return TOTAL_VIEW_TYPES;
}
#Override
public int getItemViewType(int position) {
// You must inform view type for given position
String label = myInfo.getLabel();
if (!"".equals(label)) {
return GREY;
} else {
return PLAIN;
}
}
#Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
if (convertView == null) {
// If view is null, you must create the view. But you need to create the
// correct view for given position
if(getItemViewType(position) == GREY) {
// Inflate grey;
} else {
// inflate plain
}
} else {
// convertView is not null. It is being reused.
// Android will give you the proper view here. If you are expecting
// a plain type, that's what you will get. Android won't re-use
// plain layout where you are expecting to have the grey layout.
// It will re-use the proper view for each position (following to the getItemViewType()).
// ListView is very robust.
}
// Update the view here.. Just remember that here you may have two different
// types of view.. grey or plain.
return convertView;
}
The most cool about theses concepts is that they are valid for any view that uses this View<->Adapter relation..
RecyclerView, ListView, Spinner, PagerView etc.

Android Expandable List groups textView random replace

Friends,
I am having a problem with Expandable List - for the first time the list groups are drawn correctly, (respective textViews texts get replaced, e.g. 3 of 9) but when any of groups are clicked the textViews of another group (random) are replaced instead of beeing untouched mm.
What should be done to prevent this?
Here is the adapter class:
public class InterestListAdapter extends BaseExpandableListAdapter
{
...
#Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)
{
if (convertView == null)
{
convertView = LayoutInflater.from(_context).inflate(R.layout.activity_interest_list_group, parent, false);
}
try
{
HashMap<String, String> interest = this._listDataHeader.get(String.valueOf(groupPosition));
String interestId = interest.get("interest_id");
String interestName = interest.get("interest_name");
JSONObject interestsTree = new JSONObject(library.loadString(_context, "userInterestsTree"));
TextView listGroupName = (TextView) convertView.findViewById(R.id.interest_list_group_name);
listGroupName.setText(interestName);
if (this._listDataChild.containsKey(String.valueOf(interestId)))
{
String subinterestAll = String.valueOf(this._listDataChild.get(String.valueOf(interestId)).size());
if (interestsTree.has(interestId))
{
String subinterestChecked = interestsTree.getString(interestId);
TextView subinterestText = (TextView) convertView.findViewById(R.id.interest_list_group_selected);
subinterestText.setText(subinterestChecked + " of " + subinterestAll);
}
}
}
catch (Exception e)
{
Log.e("error", Log.getStackTraceString(e));
}
return convertView;
}
}
I'm rather sure, that you have problems with memory management. When you expand your list some cells become hidden from the screen. After then, when you scroll it, they have to be regenerated. Android OS, to save memory, doesn't generate them from scratch, but instead of that uses the same memory regions, that were used for hidden cells.
It looks like subinterestText is the problematic field. It's being set only if both these ifs are true:
if (this._listDataChild.containsKey(String.valueOf(interestId)))
and
if (interestsTree.has(interestId))
If one of them is false, subinterestText variable won't be set. So, as result, value will be usedfrom previously hidden field - in your case - 2 of 2.
To fix it - always setup all values of your cell in getGroupView method.

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 change the background of ImageView in child view of Expandable ListView at run time

I have an Expandable listView in which data is populated from Xml file after parsing. My custom Adapter namely NewAdapter populate the content in Expandable list. My problem is arises from GetChildView() function where i apply the condition for row that if type is equal to somthing values like 0 or 1 or 2 the at run time the image there in child row should change accordingly. It changed write at first click of group but when i click on second group it shows abnormal behaviour of setting Image. As my Code is following
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
tempChild = (ArrayList<String>) Childtem.get(groupPosition);
Log.d("djdfj", ""+tempChild.size()+"");
TextView text = null;
if (convertView == null)
{
convertView = minflater.inflate(R.layout.child_row, null);
}
Log.d("check in before if block", ""+sitesList.getResourceType().get(1)+"");
text = (TextView) convertView.findViewById(R.id.textView1);
img_mediaType = (ImageView)convertView.findViewById(R.id.childImage1);
img_status_type =(ImageView)convertView.findViewById(R.id.childImage2);
text.setText(tempChild.get(childPosition));
if(childPosition==1 && groupPosition==0)
{
if(sitesList.getResourceType().get(5).equals("6"))
{
Log.d("check in if block",""+sitesList.getResourceType().get(5).equals("6")+"");
img_mediaType.setBackgroundResource(R.drawable.images);
img_status_type.setBackgroundResource(R.drawable.images);
}
}
and these are my Screenshot this is screen after click group one and group Two
Hopefully i can describe my question properly it may be not clear but please suggest me how to change image at runtime in Expandable ListView

Listview not showing consistent data android

I am trying to parse this JSON response onto a ListView in android where I am trying to modify the listview by showing an image depending upon the gender and adding a button for male records.
The logic is working fine, except for the last two entries.John Wayne & Leonardo Dicaprio are having details button, same is expected for Johnny Depp & Ravi Tambda.Please guide me for the same.
Using this code, I am trying to differentiate them according to the genders
if(rowData.mgender.equalsIgnoreCase("male"))
{
imageViewStatus.setImageResource(R.drawable.male);
}
else{
imageViewStatus.setImageResource(R.drawable.female);
button.setVisibility(View.GONE);
}
EDIT 1
public View getView(final int position, View convertView, ViewGroup parent){
ViewHolder holder = null;
TextView title = null;
TextView detail = null;
TextView data=null;
TextView message=null;
ImageView imageViewStatus=null;
Button button=null;
final RowData rowData= getItem(position);
if(null == convertView)
{
convertView = layoutInflater.inflate(R.layout.record, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
button=(Button)holder.getProceedButton();
message=holder.getEmail();
message.setText(rowData.mEmail);
title = holder.getName();
title.setText(rowData.mName);
detail = holder.getAddress();
detail.setText(rowData.mAdress);
data= holder.getPhoneNumber();
data.setText(rowData.mMobile+" "+rowData.mOffice);
imageViewStatus=holder.getImage();
System.out.println("This is the gender "+rowData.mgender);
if(rowData.mgender.equalsIgnoreCase("male"))
{
imageViewStatus.setImageResource(R.drawable.male);
}
else{
imageViewStatus.setImageResource(R.drawable.female);
System.out.println("Button Visibility"+button.getVisibility());
button.setVisibility(View.GONE);
}
return convertView;
}
Keep in mind that ListView children are always reused while you scroll them. When you set a property according to some condition in getView(), you have to revert that property when the condition isn't met. You're hiding the button when the gender is female, but when this view gets reused to populate a male contact, the button is still invisible and you have to set it as visible again.
Check this out:
if(rowData.mgender.equalsIgnoreCase("male"))
{
imageViewStatus.setImageResource(R.drawable.male);
button.setVisibility(View.VISIBLE); // You need to add this line in your code
} else {
imageViewStatus.setImageResource(R.drawable.female);
button.setVisibility(View.GONE);
}

Categories

Resources