Different Child Layouts for different groups ExpandableListView
Asked Answered
S

1

10

I've been having issues trying to do this. I can't seem to get it right, I want to control each parent + children of that parent. ExpandableListView has been giving me a headache. package comments;

public class CommentsExpandableListAdapter extends BaseExpandableListAdapter {

private Activity context;
private Map<String, List<String>> comments_feed_collection;
private List<String> group_list;



private boolean a_comment = false;
public CommentsExpandableListAdapter(Activity context, List<String> group_list,
                             Map<String, List<String>> comments_feed_collection) {
    this.context = context;
    this.comments_feed_collection = comments_feed_collection;
    this.group_list = group_list;
}


public Object getChild(int groupPosition, int childPosition) {
    return comments_feed_collection.get(group_list.get(groupPosition)).get(childPosition);
}

public long getChildId(int groupPosition, int childPosition) {
    return childPosition;
}

public View getChildView(final int groupPosition, final int childPosition,
                         boolean isLastChild, View convertView, ViewGroup parent) {
    final String incoming_text = (String) getChild(groupPosition, childPosition);
    LayoutInflater inflater = context.getLayoutInflater();


    if (convertView == null) {
        //first view
        if(childPosition==0 && groupPosition==0) {
            convertView = inflater.inflate(R.layout.description_of_ads_expandable_list, null);
            TextView description_child = (TextView) convertView.findViewById(R.id.description_of_ads_expandable_list_child_text_view);
            description_child.setText(incoming_text);
        }else if(childPosition==0 && groupPosition==1){
            //second view view
            convertView = inflater.inflate(R.layout.comments_create_comment, null);
        }else if(childPosition>=1 && groupPosition>=1){
            //thrid view
            convertView = inflater.inflate(R.layout.comments_expandable_list_child, null);
        }
    }
    return convertView;
}

public int getChildrenCount(int groupPosition) {
    return comments_feed_collection.get(group_list.get(groupPosition)).size();
}

public Object getGroup(int groupPosition) {
    return group_list.get(groupPosition);
}
public int getGroupCount() {
    return group_list.size();
}

public long getGroupId(int groupPosition) {
    return groupPosition;
}


public View getGroupView(int groupPosition, boolean isExpanded,
                         View convertView, ViewGroup parent) {
    String incoming_text = (String) getGroup(groupPosition);
    if (convertView == null) {
        LayoutInflater infalInflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = infalInflater.inflate(R.layout.expandable_list_single_item,
                null);
    }
    TextView item = (TextView) convertView.findViewById(R.id.expandable_list_single_item_text_view_group);
    item.setTypeface(null, Typeface.BOLD);
    item.setText(incoming_text);
    return convertView;
}


public boolean hasStableIds() {
    return true;
}


public boolean isChildSelectable(int groupPosition, int childPosition) {
    return true;
}


@Override
public int getChildTypeCount() {
    return 3;
}

@Override
public int getGroupType(int groupPosition) {
    return super.getGroupType(groupPosition);
}

@Override
public int getGroupTypeCount() {
    return 3;
}
}
Snubnosed answered 23/4, 2015 at 14:25 Comment(12)
did you override getChildTypeCount ?Cricket
My first view is being used for my second view's first position, which is wrong, I want the second view to inflate create comment, then show the third view below that. If that makes any senseSnubnosed
but now you also need the same thing for the group. override getGroupTypeCount(), and make it returns 3Cricket
Yes I tried that and it didn't change anythingSnubnosed
Basically the second view NEVER shows up at allSnubnosed
childposition should equal 0 for each time a new group is being rendered right?Snubnosed
This is the implementation of your ExpandableListAdapter right? Could you post the entire class definition. This will help us figure out which Adapter are you extending (SimpleExpandableListAdapter, BaseExpandableListAdapter, other?)Carlenecarleton
extends BaseExpandableListAdapterSnubnosed
Could u post your entire adapter? I think your using it wrong. You should (if not done yet) implement also getChildType(int groupPosition, int childPosition) and getGroupType(int groupPosition) and use thoses methods on getChildViewto determine if it's the desired group/child.Carlenecarleton
Am working on your answer (i got the solution just need to type it out ;) )Carlenecarleton
Really? That's fantastic :)Snubnosed
Let us continue this discussion in chat.Carlenecarleton
C
27

I think your main mistake resides in public View getChildView(...) :

  • The condition if (convertView == null) is actually only reached once and that's when you load the one you marked as first view. So you are inflating the first layout R.layout.description_of_ads_expandable_list and reusing it for all the other lines in your list.
  • The way you are checking for the type of child view to load is dangerous because it's incomplete . What do you think will happend if childPosition==1 && groupPosition==0 ? Booom! NullPointerException if your data source has one more element for the first group.

So first things first; you should fully implement HeterogeneousExpandableList interface if you want to use different layouts for different groups/children of your ExpandableListView.

Then using that interface work on the conditions of which layout should be loaded.

Finally work on the reuse view issue on public View getChildView(...).

Since am a nice guy here is a a snippet that should help you (based on the adapter you post) :

public class CommentsExpandableListAdapter extends BaseExpandableListAdapter {

    // 4 Child types
    private static final int CHILD_TYPE_1 = 0;
    private static final int CHILD_TYPE_2 = 1;
    private static final int CHILD_TYPE_3 = 2;
    private static final int CHILD_TYPE_UNDEFINED = 3;

    // 3 Group types
    private static final int GROUP_TYPE_1 = 0;
    private static final int GROUP_TYPE_2 = 1;
    private static final int  GROUP_TYPE_3 = 2;

    private Activity context;
    private Map<String, List<String>> comments_feed_collection;
    private List<String> group_list;

    public CommentsExpandableListAdapter(Activity context, List<String> group_list,
                                         Map<String, List<String>> comments_feed_collection) {
        this.context = context;
        this.comments_feed_collection = comments_feed_collection;
        this.group_list = group_list;
    }

    public Object getChild(int groupPosition, int childPosition) {
        return comments_feed_collection.get(group_list.get(groupPosition)).get(childPosition);
    }

    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        final String incoming_text = (String) getChild(groupPosition, childPosition);
        LayoutInflater inflater = context.getLayoutInflater();

        int childType = getChildType(groupPosition, childPosition);

        // We need to create a new "cell container"
        if (convertView == null || convertView.getTag() != childType) {
            switch (childType) {
                case CHILD_TYPE_1:
                    convertView = inflater.inflate(R.layout.description_of_ads_expandable_list, null);
                    convertView.setTag(childType);
                    break;
                case CHILD_TYPE_2:
                    convertView = inflater.inflate(R.layout.comments_create_comment, null);
                    convertView.setTag(childType);
                    break;
                case CHILD_TYPE_3:
                    convertView = inflater.inflate(R.layout.comments_expandable_list_child, null);
                    convertView.setTag(childType);
                    break;
                case CHILD_TYPE_UNDEFINED:
                    convertView = inflater.inflate(R.layout.comments_undefined, null);
                    convertView.setTag(childType);
                    break;
                default:
                    // Maybe we should implement a default behaviour but it should be ok we know there are 4 child types right?
                    break;
            }
        }
        // We'll reuse the existing one
        else {
            // There is nothing to do here really we just need to set the content of view which we do in both cases
        }

        switch (childType) {
            case CHILD_TYPE_1:
                TextView description_child = (TextView) convertView.findViewById(R.id.description_of_ads_expandable_list_child_text_view);
                description_child.setText(incoming_text);
                break;
            case CHILD_TYPE_2:
                //Define how to render the data on the CHILD_TYPE_2 layout
                break;
            case CHILD_TYPE_3:
                //Define how to render the data on the CHILD_TYPE_3 layout
                break;
            case CHILD_TYPE_UNDEFINED:
                //Define how to render the data on the CHILD_TYPE_UNDEFINED layout
                break;
        }

        return convertView;
    }

    public int getChildrenCount(int groupPosition) {
        String groupName = group_list.get(groupPosition);
        List<String> groupContent = comments_feed_collection.get(groupName);
        return groupContent.size();
    }

    public Object getGroup(int groupPosition) {
        return group_list.get(groupPosition);
    }
    public int getGroupCount() {
        return group_list.size();
    }

    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        LayoutInflater inflater = context.getLayoutInflater();
        final String incoming_text = (String) getGroup(groupPosition);

        int groupType = getGroupType(groupPosition);

        // We need to create a new "cell container"
        if (convertView == null || convertView.getTag() != groupType) {
            switch (groupType) {
                case GROUP_TYPE_1 :
                    convertView = inflater.inflate(R.layout.expandable_list_single_item, null);
                    break;
                case GROUP_TYPE_2:
                    // Am using the same layout cause am lasy and don't wanna create other ones but theses should be different
                    // or the group type shouldnt exist
                    convertView = inflater.inflate(R.layout.expandable_list_single_item, null);
                    break;
                case GROUP_TYPE_3:
                    // Am using the same layout cause am lasy and don't wanna create other ones but theses should be different
                    // or the group type shouldnt exist
                    convertView = inflater.inflate(R.layout.expandable_list_single_item, null);
                    break;
                default:
                    // Maybe we should implement a default behaviour but it should be ok we know there are 3 group types right?
                    break;
            }
        }
        // We'll reuse the existing one
        else {
            // There is nothing to do here really we just need to set the content of view which we do in both cases
        }

        switch (groupType) {
            case GROUP_TYPE_1 :
                TextView item = (TextView) convertView.findViewById(R.id.expandable_list_single_item_text_view_group);
                item.setTypeface(null, Typeface.BOLD);
                item.setText(incoming_text);
                break;
            case GROUP_TYPE_2:
                //TODO: Define how to render the data on the GROUPE_TYPE_2 layout
                // Since i use the same layout as GROUPE_TYPE_1 i could do the same thing as above but i choose to do nothing
                break;
            case GROUP_TYPE_3:
                //TODO: Define how to render the data on the GROUPE_TYPE_3 layout
                // Since i use the same layout as GROUPE_TYPE_1 i could do the same thing as above but i choose to do nothing
                break;
            default:
                // Maybe we should implement a default behaviour but it should be ok we know there are 3 group types right?
                break;
        }

        return convertView;
    }


    public boolean hasStableIds() {
        return true;
    }


    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return false;
    }


    @Override
    public int getChildTypeCount() {
        return 4; // I defined 4 child types (CHILD_TYPE_1, CHILD_TYPE_2, CHILD_TYPE_3, CHILD_TYPE_UNDEFINED)
    }

    @Override
    public int getGroupTypeCount() {
        return 3; // I defined 3 groups types (GROUP_TYPE_1, GROUP_TYPE_2, GROUP_TYPE_3)
    }

    @Override
    public int getGroupType(int groupPosition) {
        switch (groupPosition) {
            case 0:
                return GROUP_TYPE_1;
            case 1:
                return GROUP_TYPE_1;
            case 2:
                return GROUP_TYPE_2;
            default:
                return GROUP_TYPE_3;
        }
    }

    @Override
    public int getChildType(int groupPosition, int childPosition) {
        switch (groupPosition) {
            case 0:
                switch (childPosition) {
                    case 0:
                        return CHILD_TYPE_1;
                    case 1:
                        return CHILD_TYPE_UNDEFINED;
                    case 2:
                        return CHILD_TYPE_UNDEFINED;
                }
                break;
            case 1:
                switch (childPosition) {
                    case 0:
                        return CHILD_TYPE_2;
                    case 1:
                        return CHILD_TYPE_3;
                    case 2:
                        return CHILD_TYPE_3;
                }
                break;
            default:
                return CHILD_TYPE_UNDEFINED;
        }

        return CHILD_TYPE_UNDEFINED;
    }
}

And this is how it looks like :

CommentsExpandableListAdapter implementation result

Hope this helps, let me know if it does/doesnt.

Carlenecarleton answered 23/4, 2015 at 21:17 Comment(9)
Thank you so much! It's giving me an error for convertView.getTag() != childType Operator != cannot be applied to java.lang.object, intSnubnosed
Weird am executing the code i posted. Try to changing int childType = getChildType(groupPosition, childPosition); for Integer childType = new Integer(getChildType(groupPosition, childPosition)); and/or testing using equals.Carlenecarleton
@Carlenecarleton Hello. Thank you for you answer, its rly helpful. But can you explain, why you write only 2 cases in getChildType. Is it means that in each child group can be only 2 rows???Assumed
Hi @Denis422 i implemented just 2 cases to keep it simple. What you implement on getChildType depends on your needs. In my example i consider that the first 2 groups of my list have different layout and the all the others have the same. That's a condition i choose arbitrarily. You can define your own condition for instance impair position groups use the first layout and pair position groups use the second layout. Hope it helps.Carlenecarleton
Hi @Carlenecarleton When you say we need to fully implement the HeterogeneousExpandableList, where does it need to be implemented, inside the adapter or the activity in which this list view resides? ThanksShayshaya
Hi @return0, it's inside the adapter. Actually in the example i gave the adapter CommentsExpandableListAdapter enherits from BaseExpandableListAdapter which implements HeterogeneousExpandableListCarlenecarleton
Your answer has been a lifesaver man! The most important thing as I noticed is that always ID your child and group types, starting from 0 (zero). I was using the IDs for child and group from the data in the list (some id parameter in data). It was crashing despite having explicitly mentioning childCount = 3. in the end I decided to use when case (Kotlin), to return types based on view type in data (like CHILD_TYPE_1 = 0..CHILD_TYPE_2 = 1..etc ). Voila! it worked!. I will soon share a working example for folks who may end up needing to use this Widget in 2019 and later. Thanks a lot!Yoke
@kalem how can I add Spinner inside the child and how to accessLorenzalorenzana
@Lorenzalorenzana it should be as any other layout element (button/view/...). Define your spinner on the layout xml file, inflate the desired layout child on getChildView(...). Use Spinner spinner = (Spinner) convertView.findViewById(R.id.your_spinner_id); to get the reference to your spinner then set the adapter (see developer.android.com/guide/topics/ui/controls/…). I'd be extra careful when listening to events spinner.setOnItemSelectedListener(activity_not_the_adapter); attache it to the activity not the adapter.Carlenecarleton

© 2022 - 2024 — McMap. All rights reserved.