Reputation: 1092
I am using an expandable list view to display options and sub options respectively in a menu. The data coming in is correct, but oddly the listview seems to call the getChild and getChildView twice for each data entry and thus it is added to the view twice. It iterates through and then seems to iterate through again.
Example:
Categories: -Toys -Electronics -Cars -Toys -Electronics -Cars
Expected: Categories: -Toys -Electronics -Cars
I am really new to Android and Java so I am probably making a rookie mistake. I have stepped through to try and find a point where it would be duplicating the data, but all I could find is that getChild/getChildView is being called twice.
Here is the expandableListView adapter class:
public class ExpandableListAdapter extends BaseExpandableListAdapter {
private Context context;
private ArrayList<String> filterHeaderList;
// child data in format of header title, child title
private HashMap<String, ArrayList<JSONObject>> filterOptionsMap;
public ExpandableListAdapter(Context context, ArrayList<String> filterHeaders, HashMap<String, ArrayList<JSONObject>> filterChildListMap) {
this.context = context;
this.filterHeaderList = filterHeaders;
this.filterOptionsMap = filterChildListMap;
}
@Override
public String[] getChild(int groupPosition, int childPosititon) {
JSONObject childObject = this.filterOptionsMap.get(this.filterHeaderList.get(groupPosition)).get(childPosititon);
if (childObject != null) {
Iterator<?> it = childObject.keys();
String key = null;
String value = null;
if (it.hasNext()) {
key = it.next().toString();
try {
value = childObject.getString(key);
} catch (JSONException e) {
e.printStackTrace();
}
String[] data = new String[2];
data[0] = key;
data[1] = value;
return data;
} else {
return null;
}
}
return null;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
String childNum = null;
String childText = null;
final String[] childData = (String[]) getChild(groupPosition, childPosition);
if (childData != null) {
childNum = childData[0];
childText = childData[1];
}
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.ps_filter_sort_filter_expandable_child, null);
}
TextView tvChildTitle = (TextView) convertView.findViewById(R.id.ps_filter_sort_filter_expandable_child_title);
tvChildTitle.setText(childText);
tvChildTitle.setTag(R.id.ps_filter_sort_filter_expandable_child_title + groupPosition + childPosition, childNum);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return this.filterOptionsMap.get(this.filterHeaderList.get(groupPosition)).size();
}
@Override
public String getGroup(int groupPosition) {
return this.filterHeaderList.get(groupPosition);
}
@Override
public int getGroupCount() {
return this.filterHeaderList.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
String headerTitle = (String) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.ps_filter_sort_filter_expandable_parent, null);
}
TextView tvGroupTitle = (TextView) convertView.findViewById(R.id.ps_filter_sort_filter_expandable_parent_title);
tvGroupTitle.setTypeface(null, Typeface.BOLD);
tvGroupTitle.setText(headerTitle);
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
I then call it like this:
filterListAdapter = new ExpandableListAdapter(this.getActivity(), filterHeaderList, filterOptionsMap);
// setting list adapter
expListView.setAdapter(filterListAdapter);
filterListAdapter.notifyDataSetChanged();
There are reasons the data is organized in this fashion which I'd rather not get into. What I can say is that the HashMap "filterOptionsMap" is correct and there is no duplication in it or the "filterHeaderList"
Any help would be greatly appreciated. I have not been able to find someone having a similar issue and I've been banging my head on the keyboard over this.
Thank you.
P.S. You also need to tap the group heading twice to get it to collapse, but once to open. I don't know if it is related or expected behavior, but I would hope to have it be a single tap to toggle it open or closed. Bonus points for help with that as well.
As requested here is some additional code related to the HashMap:
private void prepareFilterListData(ArrayList<String> headerList, JSONObject[] categoryList, JSONObject[] sellerList, JSONObject[] typeList) {
filterHeaderArrayList = headerList;
filterOptionsMap = new HashMap<String, ArrayList<JSONObject>>();
// Adding child data
ArrayList<JSONObject> categories = new ArrayList<JSONObject>();
if (categoryList != null && categoryList.length > 0) {
for (JSONObject category : categoryList) {
categories.add(category);
}
}
ArrayList<JSONObject> sellers = new ArrayList<JSONObject>();
if (sellerList != null && sellerList.length > 0) {
for (JSONObject seller : sellerList) {
sellers.add(seller);
}
}
ArrayList<JSONObject> types = new ArrayList<JSONObject>();
if (typeList != null && typeList.length > 0) {
for (JSONObject type : typeList) {
types.add(type);
}
}
filterOptionsMap.put(filterHeaderArrayList.get(0), categories);
filterOptionsMap.put(filterHeaderArrayList.get(1), sellers);
filterOptionsMap.put(filterHeaderArrayList.get(2), types);
}
filterHeaderList = Directory.getFilterOptions();
filterCategoryList = Directory.getFilterCategories();
filterSellerList = Directory.getFilterSellers();
filterTypeList = Directory.getFilterTypes();
// Forms data structures from given input
if (filterHeaderList != null && filterCategoryList != null && filterSellerList != null && filterTypeList != null) {
prepareFilterListData(filterHeaderList, filterCategoryList, filterSellerList, filterTypeList);
private JSONObject[] getFilterTypes(JSONObject productSearchSettings, String filterTypes) {
JSONArray typesObj;
JSONObject[] types;
try {
typesObj = productSearchSettings.optJSONArray(filterTypes);
if (typesObj != null) {
types = new JSONObject[typesObj.length()];
for (int i = 0; i < typesObj.length(); i++) {
String key = "type" + String.valueOf(i);
String val = (String) typesObj.get(i).toString();
types[i] = new JSONObject().put(key, val);
}
return types;
} else {
return null;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private JSONObject[] getFilterSellers(JSONObject productSearchSettings, String filterSellers) {
JSONObject sellersObj = null;
JSONObject[] sellers;
try {
sellersObj = productSearchSettings.getJSONObject(filterSellers);
if (sellersObj != null) {
sellers = new JSONObject[sellersObj.length()];
Iterator<?> it = sellersObj.keys();
int i = 0;
while (it.hasNext()) {
String key = (String) String.valueOf(it.next().toString());
String val = sellersObj.getString(key);
sellers[i] = new JSONObject().put(key, val);
i++;
}
return sellers;
} else {
return null;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private JSONObject[] getFilterCategories(JSONObject productSearchSettings, String filterCategories) {
JSONObject categoriesObj = null;
JSONObject[] categories;
try {
categoriesObj = productSearchSettings.getJSONObject(filterCategories);
if (categoriesObj != null) {
categories = new JSONObject[categoriesObj.length()];
Iterator<?> it = categoriesObj.keys();
int i = 0;
while (it.hasNext()) {
String key = (String) String.valueOf(it.next().toString());
String val = categoriesObj.getString(key);
categories[i] = new JSONObject().put(key, val);
i++;
}
return categories;
} else {
return null;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
As mentioned before, however the hashmap going into the adapter is correct. I verified through debugging.
Upvotes: 3
Views: 3158
Reputation: 1459
this worked for me: inside onGroupClick, remove call to parent.expandGroup(groupPosition), and make it return false;
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
parent.expandGroup(groupPosition); // delete this
Log.d(TAG, "onGroupClick: ");
return false;
}
});
Upvotes: 0
Reputation: 1752
I'm sure that the lack of "return true"
could be the reason in your system, but since it didn't work for me, I add an answer as I've found a cause:
Note: You cannot use the value wrap_content for the android:layout_height attribute of a ExpandableListView in XML if the parent's size is also not strictly specified (for example, if the parent were ScrollView you could not specify wrap_content since it also can be any length. However, you can use wrap_content if the ExpandableListView parent has a specific size, such as 100 pixels.
My ExpandableListView was in fact inside a LinearLayout with height="wrap_content"
. Set it to match_parent
and, voila, problem solved.
I'm not 100% sure but it could be that, with undefined height, the getChildView is called twice because of the re-sizing of the parent.
And it could be (I know it for personal experience, changing several parameters during testing is usually not a good idea as it creates misunderstandings on the causes) that you also set the height to match_parent
while you added a return true
and thought that the solution was the latter.
It would be helpful for all if you could just unset the height of the container of your ExpandableListView OR set the return to false
and see which is the real cause. If both, well, we've found 2 causes.
Upvotes: 4
Reputation: 1092
I finally found it. I really appreciate the feedback and your help.
In the click event for the group, I was not returning true to indicate the click was handled. I was using the click event to toggle the expansion.
I don't know why exactly that would cause duplication and also require two clicks to close the group but toggling that return for the click event on the group made all the difference.
Upvotes: 7
Reputation: 6821
Hmmm...Sorry to say, I'm still not seeing anything to cause that behavior. Generally this could be caused by unknowingly adding new content. Usually because one modifies the data externally from the adapter.
In your case filterHeaderList
in the adapter and the list returned by Directory.getFilterOptions()
are the same list. So if you modify that Directory.getFilterOptions()
list, then you'll also inadvertently modify the adapter. Of course that method could just be generating a new list on the fly, so this may not even be a concern for you.
If you are positive the filterHeaderList
is correct during the getChildView and getGroupView invocations, I'm not sure what else could be wrong. The custom group/child views would not cause this.
All I can suggest is resorting to some more aggressive debugging measures. Basically start stripping that Activity/Fragment down to the bare bones. Eliminate as many variables as possible so that you are just doing the most simplest case...loading an ExpandableListView and adapter with some data.
You could also try feeding in some fake data. Just try one child item with one group to see if it's still doubling everything.
Upvotes: 1