Sharon
Sharon

Reputation: 3909

How do I use Viewholder with ExpandableListView?

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().

Upvotes: 4

Views: 2867

Answers (4)

CoXier
CoXier

Reputation: 162

Try this library.You can repalce ExpandableListView with it

Upvotes: 1

Gustavo Pagani
Gustavo Pagani

Reputation: 6988

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);

Upvotes: 1

Mehedi hassan piash
Mehedi hassan piash

Reputation: 31

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

Upvotes: 1

BladeCoder
BladeCoder

Reputation: 12929

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.

Upvotes: 2

Related Questions