4

I'm trying to create an ExpandableListView in my app. It has 2 groups: the first is colours, and the second is symbols. The first group clicked on works fine. The second group, however, shows the rows from the first group (if the second group has more items in it, then the 'extra' ones will be correct).

Eg let's say the 'colours' are white, black, red and blue, and the symbols are '/' and '.'.

If I start the activity and click on colours, then they appear correctly. If I then click on 'symbols', I see white and black.

If I click 'symbols' first, then I see '/' and '.', but when I then click on colours I see '/', '.', red, blue.

I searched online and have established that I need to use ViewHolders to avoid it reusing the same view when I change groups. I haven't been able to implement it though. At first it didn't make any difference, and the current version is crashing out when I click the second group. I think part of the issue is that I have a different child layout for each group (ie the symbols are shown differently from the colours).

Currently here's what I have (I've shown what I think are the relevant bits; if I've left out anything important, I can add it in):

public class ColourSymbolKeyAdapter extends BaseExpandableListAdapter {

    private Context context;
    private HashMap<String, List<KeyItem>> childDataSource;
    private List<String> parentDataSource;

    public ColourSymbolKeyAdapter(Context context,
                                  List<String> childParent,
                                  HashMap<String, List<KeyItem>> child) {

        this.context = context;
        this.parentDataSource = childParent;
        this.childDataSource = child;
    }

... Left out various override functions ...

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

        View v = convertView;
        GroupViewHolder holder;

        if(v == null) {
            LayoutInflater inflater =
                    (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(R.layout.expandablelist_parent, parent, false);
            holder = new GroupViewHolder();

            holder.mGroupName = (TextView) v.findViewById(R.id.textViewParent);
            v.setTag(holder);
        }else {
            holder = (GroupViewHolder) v.getTag();
        }

        String parentHeader = (String) getGroup(groupPosition);
        holder.mGroupName.setText(parentHeader);
        return v;
    }

    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {

        View row = convertView;
        KeyItem childItem = (KeyItem) getChild(groupPosition, childPosition);
        ColourViewHolder colourviewholder;
        SymbolViewHolder symbolviewholder;

        LayoutInflater inflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if(childItem.getPatternColour() == null) { // This is a symbol row

            if(row == null) {
                symbolviewholder = new SymbolViewHolder();
                row = inflater.inflate(R.layout.expandablelist_child_symbol, parent, false);
                symbolviewholder.mChildName = (TextView) row.findViewById(R.id.symbol_desc);
                symbolviewholder.mSymbolCell = (SymbolCell) row.findViewById(R.id.symbol_cell);
                row.setTag(symbolviewholder);
            }else {
                symbolviewholder = (SymbolViewHolder) row.getTag();
            }

            String drawableName = childItem.getPatternSymbol().getDrawable();
            final int resourceId = context.getResources().getIdentifier(drawableName, "drawable", context.getPackageName());
            Drawable drawable;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                drawable = context.getResources().getDrawable(resourceId, context.getTheme());
            } else {
                drawable = context.getResources().getDrawable(resourceId);
            }

            symbolviewholder.mSymbolCell.setDrawable(drawable);
            symbolviewholder.mChildName.setText(childItem.getPatternSymbol().getSymbolDescription());

        }else { // This is a colour row
            if(row == null) {
                colourviewholder = new ColourViewHolder();
                row = inflater.inflate(R.layout.expandablelist_child_colour, parent, false);
                colourviewholder.mChildName = (TextView) row.findViewById(R.id.colour_name);
                colourviewholder.mChildDesc = (TextView) row.findViewById(R.id.colour_desc);
                colourviewholder.mColourCell = (ColourCell) row.findViewById(R.id.colour_cell);
                row.setTag(colourviewholder);
            }else {
                colourviewholder = (ColourViewHolder) row.getTag();
            }

            colourviewholder.mColourCell.setColour(childItem.getPatternColour());
            colourviewholder.mChildName.setText(childItem.getPatternColour().getName());
            colourviewholder.mChildDesc.setText(childItem.getPatternColour().getDescription());
        }

        return row;
    }

    public final class GroupViewHolder {

        TextView mGroupName;
    }

    public final class ColourViewHolder {

        ColourCell mColourCell;
        TextView mChildName, mChildDesc;
    }

    public final class SymbolViewHolder {

        SymbolCell mSymbolCell;
        TextView mChildName;
    }
}

Layout for the parent (expandablelist_parent.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/parentView">

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/textViewParent"
        android:textColor="#000000"
        android:textAppearance="@style/ParagraphBold">
    </TextView>

</LinearLayout>

Layout for the colour rows (expandablelist_child_colour.xml):

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/small_padding"
    android:id="@+id/childViewColour"
    android:orientation="horizontal">

    <com.myname.appname.ColourCell xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="@dimen/grid_cell_column_width"
        android:layout_height="@dimen/grid_cell_column_width"
        android:id="@+id/colour_cell"
        android:layout_marginRight="@dimen/small_padding" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="@style/ParagraphBold"
            android:textColor="@color/colorPrimaryDark"
            android:id="@+id/colour_name">
        </TextView>

        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="@style/Paragraph"
            android:textColor="@color/colorPrimaryDark"
            android:id="@+id/colour_desc">
        </TextView>

    </LinearLayout>

</LinearLayout>

and for the symbol rows (expandablelist_child_symbol.xml):

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/childViewSymbol"
    android:padding="@dimen/small_padding">

    <com.myname.appname.SymbolCell xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="@dimen/grid_cell_column_width"
        android:layout_height="@dimen/grid_cell_column_width"
        android:textAppearance="@style/Paragraph"
        android:id="@+id/symbol_cell"
        android:layout_marginRight="@dimen/small_padding" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textColor="@color/colorPrimaryDark"
        android:id="@+id/symbol_desc">
    </TextView>

</LinearLayout>

If I click the 'colour' group first, and then the 'symbol' group, it crashes out on the line: symbolviewholder = (SymbolViewHolder) row.getTag().

halfer
  • 19,824
  • 17
  • 99
  • 186
Sharon
  • 3,471
  • 13
  • 60
  • 93
  • Can you add part of the exception's stack trace, please? – Paulo Avelar Aug 26 '16 at 11:26
  • Try to use new row view every time, don't check that if row == null and all create every time new row and check – Vickyexpert Aug 26 '16 at 12:30
  • @Vickyexpert Creating a new row each time disables view recycling which is bad. It also makes ViewHolders useless. – BladeCoder Aug 26 '16 at 13:06
  • just wanted to flag this question for being a duplicate, while the bounty prevents it: http://stackoverflow.com/questions/26419161/expandable-list-with-recyclerview – Martin Zeitler Aug 26 '16 at 13:47
  • for reference: https://www.bignerdranch.com/blog/expand-a-recyclerview-in-four-steps/ – Martin Zeitler Aug 26 '16 at 13:55
  • can you share the wireframe/screenshot of expandable list for better understanding – Rohit Raich Aug 27 '16 at 10:16
  • Unrelated to your issue: I suggest you to avoid using getResources().getIdentifier() to retrieve the drawable id. Better create a utility function to map a symbol name to a drawable id with a switch statement and a default case. – BladeCoder Aug 27 '16 at 10:33

4 Answers4

2

Since you have 2 different child layouts, you need to override getChildTypeCount() and getChildType() so the adapter will receive the proper view type to reuse. Otherwise, in some cases you will get a ClassCastException when trying to retrieve your ViewHolder.

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

@Override
public int getChildType(int groupPosition, int childPosition) {
    KeyItem childItem = (KeyItem) getChild(groupPosition, childPosition);
    return (childItem.getPatternColour() == null) ? 0 : 1;
}

For more info, see HeterogeneousExpandableList documentation.

BladeCoder
  • 12,779
  • 3
  • 59
  • 51
  • Thank you. I'll try this out - gave it the bounty as it pointed me to documentation that was helpful in understanding. I can't try it out at the moment as I'm having adb debug bridge trouble, but wanted to award the bounty before it expired. – Sharon Sep 01 '16 at 10:57
  • 1
    Thanks, let me know if it worked for you or if you need more information. – BladeCoder Sep 01 '16 at 23:14
  • It worked! Thank you! Makes much more sense now too! – Sharon Sep 07 '16 at 17:02
1

I think it will be better to use Expendable Recyclerview to solve your problem using custom viewholder.for more information see the link. https://guides.codepath.com/android/Heterogenous-Layouts-inside-RecyclerView

1

You can easily achieve it with this library, there is a full example here.

Basically you group your items into sections:

class MySection extends StatelessSection {

    String header;
    List<String> list;
    boolean expanded = true;

    public MySection(String header, List<String> list) {
        // call constructor with layout resources for this Section header and items 
        super(R.layout.section_header, R.layout.section_item);
        this.myHeader = header;
        this.myList = list;
    }

    @Override
    public int getContentItemsTotal() {
        return expanded? list.size() : 0;
    }

    @Override
    public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
        return new HeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
        final HeaderViewHolder headerHolder = (HeaderViewHolder) holder;

        headerHolder.tvTitle.setText(title);

        headerHolder.rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                expanded = !expanded;
                headerHolder.imgArrow.setImageResource(
                        expanded ? R.drawable.ic_keyboard_arrow_up_black_18dp : R.drawable.ic_keyboard_arrow_down_black_18dp
                );
                sectionAdapter.notifyDataSetChanged();
            }
        });
    }

    @Override
    public RecyclerView.ViewHolder getItemViewHolder(View view) {
        // return a custom instance of ViewHolder for the items of this section
        return new MyItemViewHolder(view);
    }

    @Override
    public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
        MyItemViewHolder itemHolder = (MyItemViewHolder) holder;

        // bind your view here
        itemHolder.tvItem.setText(list.get(position));
    }
}

Then create instance of your sections and set up your adapter:

// Create an instance of SectionedRecyclerViewAdapter 
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();

// Add your Sections
sectionAdapter.addSection(new MySection("Colours", Arrays.asList(new String[] {"white", "red", "black", "blue" })));
sectionAdapter.addSection(new MySection("Symbols", Arrays.asList(new String[] {"/", "." })));

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
Gustavo Pagani
  • 6,583
  • 5
  • 40
  • 71
1

Try this library.You can repalce ExpandableListView with it

CoXier
  • 162
  • 1
  • 10