8

I have a ListView where every row is an image+text. I have implemented this list as a seperate list using a ViewHolder to optimize scrolling. Now I'm trying to use the same visual effect but this time as part of an Expandable ListView. I did that and while visually it looks the same, naturally the scrolling is sticky...So the question is:

How can I use the ViewHolder technique in an Expandable ListView?

I imagine that something must be done within th getChildView() method but I'm not experienced enough with this technique to figure out the details by my self. Here is the Adapter and the child layout. Any help will be greatly appreciated !!

 public class MyExpandableListAdapter2 extends BaseExpandableListAdapter {



  private final SparseArray<Group> groups;
  public LayoutInflater inflater;
  public Activity activity;

  public MyExpandableListAdapter2(Activity act, SparseArray<Group> groups) {
    activity = act;
    this.groups = groups;
    inflater = act.getLayoutInflater();
  }

  @Override
  public Object getChild(int groupPosition, int childPosition) {
    return groups.get(groupPosition).children.get(childPosition);
  }

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

  @Override
  public View getChildView(int groupPosition, final int childPosition,
      boolean isLastChild, View convertView, ViewGroup parent) {
        final String children = (String) getChild(groupPosition, childPosition);
        convertView = inflater.inflate(R.layout.listrow_details2, null);
        text = (TextView) convertView.findViewById(R.id.expandable_list_child_view2);

        // Complicated code where I create a bitmap programmatically and set
        // it as drawable on the TextView along with the appropriate text.

    return convertView;
  }

  @Override
  public int getChildrenCount(int groupPosition) {
    return groups.get(groupPosition).children.size();
  }

  @Override
  public Object getGroup(int groupPosition) {
    return groups.get(groupPosition);
  }

  @Override
  public int getGroupCount() {
    return groups.size();
  }

  @Override
  public void onGroupCollapsed(int groupPosition) {
    super.onGroupCollapsed(groupPosition);
  }

  @Override
  public void onGroupExpanded(int groupPosition) {
    super.onGroupExpanded(groupPosition);
  }

  @Override
  public long getGroupId(int groupPosition) {
    return 0;
  }

  @Override
  public View getGroupView(int groupPosition, boolean isExpanded,
      View convertView, ViewGroup parent) {
    if (convertView == null) {
      convertView = inflater.inflate(R.layout.listrow_group2, null);
    }
    Group group = (Group) getGroup(groupPosition);
    ((TextView) convertView).setText(group.string);
    ((TextView)  convertView).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_launcher, 0, 0, 0);



    //((TextView) convertView).setChecked(isExpanded);

    return convertView;
  }

  @Override
  public boolean hasStableIds() {
    return false;
  }

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

} 

child layout: listrow_details2.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingLeft="40dp" >

    <TextView
        android:id="@+id/expandable_list_child_view2"
        android:clickable="true"
        android:background="@layout/transparent_text_selector"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="5dp"
        android:gravity="top|left"
        android:text="@string/hello_world"
        android:textSize="14sp" >
    </TextView>

   <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#d3d3d3" />

</LinearLayout> 
Anonymous
  • 4,470
  • 3
  • 36
  • 67
  • view holder has minimal impact on performance, i dont know why do people really use it – pskink Mar 22 '14 at 21:22
  • 2
    From what i've read it improves performance by 15%...Other than that I saw a difference in scrolling smoothness between the two approaches in my case, so I guess it makes enough of an impact to go into the trouble... – Anonymous Mar 22 '14 at 22:09
  • come on, SimpleCursorAdapter does not use any holder, yet it is fast enough – pskink Mar 22 '14 at 22:19

1 Answers1

11

You're going to want to use 2 different view holders. First, let's define a ViewHolder class.

public class ViewHolder
{
    private HashMap<Integer, View> storedViews = new HashMap<Integer, View>();

    public ViewHolder()
    {
    }

    /**
     * 
     * @param view
     *            The view to add; to reference this view later, simply refer to its id.
     * @return This instance to allow for chaining.
     */
    public ViewHolder addView(View view)
    {
        int id = view.getId();
        storedViews.put(id, view);
        return this;
    }

    public View getView(int id)
    {
        return storedViews.get(id);
    }
}

We want to use the ViewHolder in the getGroupView method and in the getChildView method.

    @Override
    public View getChildView(int groupPosition, final int position,
           boolean isLastChild, View convertView, ViewGroup parent)
    {
        View row = convertView;
        if (row == null)
        {
            row = inflater.inflate(R.layout.my_layout_for_row, parent, false);
            View myView = row.findViewById(R.id.myView);
            ViewHolder holder = new ViewHolder();
            holder.addView(myView);
            row.setTag(holder);
        }

        // Get the stored ViewHolder that also contains our views
        ViewHolder holder = (ViewHolder) row.getTag();
        View myView = holder.getView(R.id.myView);
        return row;
    }

We do the same thing for the groupView method.

@Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)
    {
        View v = convertView;
        if (v == null)
        {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(R.layout.my_layout, parent, false);
            ViewHolder holder = new ViewHolder();
            holder.addView(v.findViewById(R.id.myView));
            v.setTag(holder);
        }

        ViewHolder holder = (ViewHolder) v.getTag();
        // Do whatever you need to with the group view
        return v;
    }

EDIT: Perhaps a little bit of explanation is warranted for how ViewHolder actually works.

The code inside the if(row == null) is only for initializing the views for the first time. This view gets passed around in the convertView field every time a new row wants to know what to what layout to use. We set the ViewHolder on this view that gets passed around so we don't have to call findViewById every time or inflate the layout, both of which are relatively expensive operations. So, when the convert view isn't null, it doesn't go in that if-statement and all we care about is setting custom values for the views contained inside the ViewHolder.

JustSoAmazing
  • 747
  • 4
  • 8
  • So the part where I execute my "complicated code" that creates the bitmaps and sets the text is done inside the if(v==null)? – Anonymous Mar 22 '14 at 22:07
  • Not quite. That section inside the if-statement is only for initializing the views for the first time. This view gets passed around in the convertView field every time a new row wants to know what to what layout to use. We set the ViewHolder on this view that gets passed around so we don't have to call findViewById every time or inflate the layout, both of which are relatively expensive operations. So, when the convert view isn't null, it doesn't go in that if-statement and all we care about is after it. – JustSoAmazing Mar 22 '14 at 23:29
  • When i use the accepted answer i get the contents of the edittext duplicated in my other edittext that is within another group, how can i solve that? – Joseph Aug 05 '17 at 18:01
  • Great this gave me an idea for an implementation i wanted to have using Expandable listview with heterogeneous groups and children in Kotin using Databinding. Cool stuff! – sud007 May 12 '19 at 10:51
  • This is going to create a new instance of the ViewHolder before a view is added. So each ViewHolder will have a HashMap containg one view at maximum. Would make more sense to have the instance of ViewHolder as a member variable of the list adapter. – t7bdh3hdhb Jul 26 '19 at 15:57