Monday 24 February 2014

Three Level Expandable ListView

How to create a 3 Level Expandable ListView

Recently I had to implement a 3 Level ExpandableListView. So, like most programmers I went searching for an existing solution. After a quick google search there appeared to be quite a few attempts at creating one. I say attempts because none of the ones I found on various blogs or github worked quite right.

After having a look at the source of the ExpandableListView it appears all that's happening behind the scenes is a fancy asynchronous filtering process for the list data. If the ExpandableListView is just a fancy filtering wrapper around a ListView there's no reason we can't make our own to use more levels.

To do this I used a regular ListView and BaseAdapter, a wrapper class, some Interfaces and an AsyncTask to do the filtering.

Here's how it works:

  • iterate over a list of all items and determine if it should be shown. 
  • it should be shown if its the top level item (it's parent is null) or every ancestor is expanded (its parent is expanded and so is its parent's parent and so on).
  • if it should be shown we add it to a filteredList and its this filteredList that is used by the ListView to determine what to show.
Let's take a look at the code.

First the NLevelListItem interface. Just a straight forward interface defining some methods we'll use later.

package com.twocentscode.nexpandable;

import android.view.View;

public interface NLevelListItem {

 public boolean isExpanded();
 public void toggle();
 public NLevelListItem getParent();
 public View getView();
}


Next is another interface, the NLevelView. This interface is what we'll use to get the View for each level.

package com.twocentscode.nexpandable;

import android.view.View;

public interface NLevelView {

 public View getView(NLevelItem item);
}


The NLevelItem, this implements the above NLevelListItem interface. Since this is just the concrete implementation of the interface the 2 things to note here is the Object wrappedObject and the NLevelView nLevelView. The wrappedObject is what will hold the data that backs the ListView views and the nLevelView will get the View for the BaseAdapter.

package com.twocentscode.nexpandable;

import android.view.View;

public class NLevelItem implements NLevelListItem {
 
 private Object wrappedObject;
 private NLevelItem parent;
 private NLevelView nLevelView;
 private boolean isExpanded = false;
 
 public NLevelItem(Object wrappedObject, NLevelItem parent, NLevelView nLevelView) {
  this.wrappedObject = wrappedObject;
  this.parent = parent;
  this.nLevelView = nLevelView;
 }
 
 public Object getWrappedObject() {
  return wrappedObject;
 }
 
 @Override
 public boolean isExpanded() {
  return isExpanded;
 }
 @Override
 public NLevelListItem getParent() {
  return parent;
 }
 @Override
 public View getView() {
  return nLevelView.getView(this);
 }
 @Override
 public void toggle() {
  isExpanded = !isExpanded;
 }
}


And now for the complex part, the NLevelAdapter. Its mostly just a standard BaseAdapter, except for the AsyncFilter class. The AsyncFilter does the filtering in the background, it simply iterates through the list of all NLevelItems and adds the top level items and any items whose ancestors are all expanded.

package com.twocentscode.nexpandable;

import java.util.ArrayList;
import java.util.List;

import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class NLevelAdapter extends BaseAdapter { 

 List<NLevelItem> list;
 List<NLevelListItem> filtered;
 public void setFiltered(ArrayList<NLevelListItem> filtered) {
  this.filtered = filtered;
  
 }
 public NLevelAdapter(List<NLevelItem> list) {
  this.list = list;
  this.filtered = filterItems();
 }
 
 @Override
 public int getCount() {
  return filtered.size();
 }

 @Override
 public NLevelListItem getItem(int arg0) {
  return filtered.get(arg0);
 }

 @Override
 public long getItemId(int arg0) {
  return 0;
 }

 @Override
 public View getView(int arg0, View arg1, ViewGroup arg2) {
  
  return getItem(arg0).getView();
 }

 public NLevelFilter getFilter() {
  return new NLevelFilter();
 }
 

 class NLevelFilter {

  public void filter() {
   new AsyncFilter().execute();
  }
  
  class AsyncFilter extends AsyncTask<Void, Void, ArrayList<NLevelListItem>> {

   @Override
   protected ArrayList<NLevelListItem> doInBackground(Void... arg0) {

    return (ArrayList<NLevelListItem>) filterItems();
   }
   
   @Override
   protected void onPostExecute(ArrayList<NLevelListItem> result) {
    setFiltered(result);
    NLevelAdapter.this.notifyDataSetChanged();
   }
  } 
  

 }
 
 public List<NLevelListItem> filterItems() {
  List<NLevelListItem> tempfiltered = new ArrayList<NLevelListItem>();
  OUTER: for (NLevelListItem item : list) {
   //add expanded items and top level items
   //if parent is null then its a top level item
   if(item.getParent() == null) {
    tempfiltered.add(item);
   } else {
    //go through each ancestor to make sure they are all expanded
    NLevelListItem parent = item;
    while ((parent = parent.getParent())!= null) {
     if (!parent.isExpanded()){
      //one parent was not expanded
      //skip the rest and continue the OUTER for loop
      continue OUTER;
     }
    }
    tempfiltered.add(item);
   }
  }
  
  return tempfiltered;
 }

 public void toggle(int arg2) {
  filtered.get(arg2).toggle();
 }
}


Now there's only two thinga left: how we build the list for the NLevelAdapter and how we provide the View for the getView() method in the adapter. So let's take a look at the MainActivity.

Here we create the data for the ListView, we create 5 'grandparents' (top level) NLevelItems, then for each grandparent we create a random number (between 1 and 5) of 'parents' (second level) NLevelItems and then for each parent we create a random number (between 1 and 6) of 'children' (third level) NLevelItems.

For each of the NLevelItems we pass in an anonymous instance of NLevelView, this is what supplies the View for the adapter for the each of the NLevelItems (grandparent, parent and children objects). For each of the different Views I'm using the same xml layout and just changing the background color for the different types, green for grandparents, yellow for parents and gray for children.

package com.twocentscode.nexpandable;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.example.expandable3.R;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

 List<NLevelItem> list;
 ListView listView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  listView = (ListView) findViewById(R.id.listView1);
  list = new ArrayList<NLevelItem>();
  Random rng = new Random();
  final LayoutInflater inflater = LayoutInflater.from(this);
  for (int i = 0; i < 5; i++) {
   
   final NLevelItem grandParent = new NLevelItem(new SomeObject("GrandParent "+i),null, new NLevelView() {
    
    @Override
    public View getView(NLevelItem item) {
     View view = inflater.inflate(R.layout.list_item, null);
     TextView tv = (TextView) view.findViewById(R.id.textView);
     tv.setBackgroundColor(Color.GREEN);
     String name = (String) ((SomeObject) item.getWrappedObject()).getName();
     tv.setText(name);
     return view;
    }
   });
   list.add(grandParent);
   int numChildren = rng.nextInt(4) + 1;
   for (int j = 0; j < numChildren; j++) {
    NLevelItem parent = new NLevelItem(new SomeObject("Parent "+j),grandParent, new NLevelView() {
     
     @Override
     public View getView(NLevelItem item) {
      View view = inflater.inflate(R.layout.list_item, null);
      TextView tv = (TextView) view.findViewById(R.id.textView);
      tv.setBackgroundColor(Color.YELLOW);
      String name = (String) ((SomeObject) item.getWrappedObject()).getName();
      tv.setText(name);
      return view;
     }
    });
  
    list.add(parent);
    int grandChildren = rng.nextInt(5)+1;
    for( int k = 0; k < grandChildren; k++) {
     NLevelItem child = new NLevelItem(new SomeObject("child "+k),parent, new NLevelView() {
      
      @Override
      public View getView(NLevelItem item) {
       View view = inflater.inflate(R.layout.list_item, null);
       TextView tv = (TextView) view.findViewById(R.id.textView);
       tv.setBackgroundColor(Color.GRAY);
       String name = (String) ((SomeObject) item.getWrappedObject()).getName();
       tv.setText(name);
       return view;
      }
     });
    
     list.add(child);
    }
   }
  }
  
  NLevelAdapter adapter = new NLevelAdapter(list);
  listView.setAdapter(adapter);
  listView.setOnItemClickListener(new OnItemClickListener() {

   @Override
   public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
     long arg3) {
    ((NLevelAdapter)listView.getAdapter()).toggle(arg2);
    ((NLevelAdapter)listView.getAdapter()).getFilter().filter();
    
   }
  });
 }
 
 class SomeObject {
  public String name;

  public SomeObject(String name) {
   this.name = name;
  }
  public String getName() {
   return name;
  }
 }

}


And finally the xml files used. activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="NLevelExpandable ListView" />

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_below="@+id/textView1" >

    </ListView>

</RelativeLayout>


And list_item.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="wrap_content"
    android:orientation="vertical" 
    android:id="@+id/listItemContainer">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

</LinearLayout>

You might have noticed everything is named NLevel... the reason being this code should handle an infinite number of level (in theory). Just nest more items as shown the MainActivity and more levels should be created. If you want you can grab the code from github.

Last but not least some screen shots.



68 comments:

  1. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. You could change isExpanded=false to isExpanded=true in NLevelItem class which would mean all NLevelItems would be expanded by default.

      Delete
    2. This comment has been removed by the author.

      Delete
  2. Hi! I would know if there is a method to take item's text of the listview, and if is possible to take also it parent's text.
    Thanks
    I hope in an answer

    ReplyDelete
  3. How should xml file look like if I want in the last child item to have image on the left and 2 textview on the right of image?

    ReplyDelete
  4. why my listen item not response if i get grandparent more than 3

    ReplyDelete
  5. thanks, is there way to expanded one list only the other collapses

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Hi,
    I need to keep check boxes at all the three levels and keep the states of checked and unchecked.
    When children are checked, parent must be checked and if all parents are checked, grandparent should be checked. The opposite case implies if grandparent is unchecked, then all parents and children must also be unchecked.
    Could you please advise how this can be achieved. Currently, I have created a second level list with its own adapter (sAdapter) as child view in grandparent adapter (pAdapter). The children are being populated as child view from sAdapter. However, I'm facing two issues here:
    1. The parents are being populated twice where grandparent.get(groupPosition).getParents().size > 1.
    2. When I uncheck any child, parent is being notified ( and changing check box status), but grandparent does not know about this change. I tried passing pAdapter to sAdapter and notify change to it. But that didn't help.

    ReplyDelete
    Replies
    1. Hi,

      1. I don't really understand what you've done here, but you shouldn't need more than 1 adapter for multiple levels.
      2. The check boxes, each NLevelItem has a reference to its parent so you can pass the check box change up to the parent. eg item.getParent().someNewMethodToHandleCheckBox() then call notifyDatasetChanged

      Delete
  8. When I add new grandparents, parents and children once i expand the grandparent list the parent list does not expand under the grandparent list i clicked on but the very last grandparent list how can i fix this

    ReplyDelete
  9. I'm sorry I don't quite understand your problem, can you post some of your code?

    ReplyDelete
  10. I can't post the code because i keep getting a message telling me my HTML cannot be accepted: Must be at most 4,096 characters

    I want my code to look like

    Advanced Search
    Grandparent1
    Parent1
    GrandParent2
    Parent2
    Child1
    Child2
    Child3
    Parent3
    Child4
    Child5
    Child6
    Parent4
    Child7
    Child8
    Child9
    Grandparent3
    Parent5
    Parent6
    Parent7
    Parent8
    Parent9
    Parent10

    but when i click on grandparent1 and others what i get is

    Grandparent1
    Grandparent2
    Grandparent3
    Parent1
    Parent2
    Child1
    Child2
    Child3
    Parent3
    Child4
    Child5
    Child6
    Parent4
    Child7
    Child8
    Child9
    Parent5
    Parent6
    Parent7
    Parent8
    Parent9
    Parent10

    ReplyDelete
    Replies
    1. I have the same problem.It is because of adding of data is not correct in your arraylist..

      Delete
  11. Sorry for the late reply I have been away from my pc. It looks like all the parents have been given the same grandparent as their own parent. So you should take a look at where you're creating your items. Are you using the same code I wrote in the MainActivity or did you write your own, if so can you post the part where you create the children, parents, grandparents, if you can't post the code you could try use pastebin.com and send me the pastebin link

    ReplyDelete
  12. how to add childclick listener to this??

    ReplyDelete
    Replies
    1. It uses a standard ListView so you can use the standard OnItemClickListener

      listView.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView parent, View view, int position,
      long id) {


      }
      });

      Delete
    2. Thanks for the reply.But the position of the child is not correct with respect to its parent because of its adding procedure.I want exact grand child position.Please help.

      Delete
    3. Ok, there are 2 lists, the 'filtered' and the 'list', when you say exact grand child position do you mean in the 'list' which holds every item or the 'filtered' which holds only grandparents and any expanded items. The position in the onClickItem is the position in the 'filtered' list. If you need the position in the 'list' then you'd have to get the item first using

      NLevelItem item = adapter.getItem(position);

      then get the index of the item in the 'list' using indexOf

      int absolutePosition = adapter.getList().indexOf(item);

      Is that what you're trying to do?

      Delete
    4. I want to detect the grand child for putting some intent.But in this case when
      I clicked grand parent also gets the click event.I only want the child click..

      Delete
    5. I have just ran the code and everything is working as expected, I'm not getting multiple click events in the onItemClickListener
      All clicks are returning the correct index in the filtered array. Can you post the code in your click listener?

      Delete
    6. ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

      @Override
      public void onItemClick(AdapterView arg0, View arg1, int arg2,
      long arg3) {
      ((NLevelAdapter) listView.getAdapter()).toggle(arg2);
      ((NLevelAdapter) listView.getAdapter()).getFilter().filter();
      NLevelListItem item = adapter.getItem(arg2);
      //int absolutePosition = adapter.getList().indexOf(item);

      NLevelItem item = adapter.getItem(position);
      //int absolutePosition = adapter.getList().indexOf(item);
      }
      });
      int absolutePosition = adapter.getList().indexOf(item); is not working.".getList()" method not working..

      Delete
    7. There is no getList() in the adapter, you'll have to add it yourself.

      Add to the NLevelAdapter
      public List getList() {
      return this.list;
      }

      Delete
    8. Ok..thanks.But I have child id array and child name array.By using child name array I created grand child using this array.
      example.
      Grand parent:Fabrics (it is created by grandparent array,It is have ids array too. example for fabrics id=3234)
      its child: Matching Cloth materials (it is created by child array,It is have ids array too. example..)
      grand child: Cotton Lining cloth (it is created by grandchild array,It is have ids array too. example id=65467)
      I want that id 65467,When clicking on its grand child. Also Each grand child has its own id.
      In the current case "absolutePosition" getting the position in an increment manner.that is for grand parent it is "1".and its child it is "2".
      Is there a way to get its id of child when clicking it?.
      Anyway Thanks for Your efforts and reply..:)

      Delete
    9. Basically getting the correct position is the way to get correct id from the array

      Delete
    10. Sorry I'm not following your example and I think i would need to see your code to help you.

      If your comfortable with showing me your code you can post your code to pastebin (be sure to create an account and log in if you want to be able to delete the paste after) and send me the link or if your code is on github you can send me the link and i'll have a look

      Delete
    11. I am absolutely greatful to show you my code
      here is my link:http://pastebin.com/raw/d21XdabE

      Delete
    12. The link is set to private so I can't view, you'll need to make it public

      Delete
    13. This comment has been removed by the author.

      Delete
    14. I made it as public..
      http://pastebin.com/raw/d21XdabE

      Delete
    15. ok, I'll take a look, I'm in work at the moment so it'll be a while before I'm able to go through your code but I'll respond as soon as I can.

      Delete
    16. Alright here's what I've done, in the click listener

      @Override
      public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) {
      ((NLevelAdapter) listView.getAdapter()).toggle(arg2);
      ((NLevelAdapter) listView.getAdapter()).getFilter().filter();

      //because NLevelItem implements NLevelListItem we can cast the NLevelListItem back to NLevelItem
      NLevelItem item = (NLevelItem)adapter.getItem(arg2);

      //now we have the NLevelItem, we can get the wrapped object
      //we need to cast that to the type we passed in the constructor
      //in this case the SomeObject class
      //now we can get the id of our object
      //eg Clicking Cotton Lining Cloth will give the id 300018
      //{ "third_category_id":"300018","third_category_name":"Cotton Lining cloth", "second_category_id":"200005" },
      //which looks correct to me
      String id = ((SomeObject)item.getWrappedObject()).getId();

      Log.i("DEBUG", "item="+item.getWrappedObject().toString());
      Log.i("DEBUG", "id of clicked="+id);


      }
      });

      So one thing to note is that you're not passing in the ids for the parent/grandparent in the constructor for the parent/grandparent SomeObjects so the id will be null for those, you will need to pass in the id if you want to get their ids.

      If you need to get the parent of a clicked object then you can use this line
      NLevelItem parent = (NLevelItem) item.getParent();

      Also I added a toString() method to the SomeObject class
      public String toString() {
      return "id="+id+"name="+name;
      }

      Delete
    17. I am new to android.And you helped me a lot.Thank you very much.It is working perfectly..:).

      Delete
    18. How to put click listener to child only

      Delete
    19. Add a level to constructor. then check for level
      class ListObject
      {
      public String name;
      public int level;
      }

      NLevelItem grandParent = new NLevelItem(new ListObject(model.get(i).LocName, 0, i, -1, -1), null, new NLevelView()

      this i comes from for loop and defines its level.

      inside
      listView.setOnItemClickListener(new OnItemClickListener()
      {
      @Override
      public void onItemClick(final AdapterView parent, final View view, final int position, long id)
      {

      NLevelItem item = (NLevelItem) adapter.getItem(position);
      final ListObject listObject = (ListObject) item.getWrappedObject();

      if (listObject.getLevel() == 2)
      {
      change level as you need.

      Delete
    20. new ListObject(model.get(i).LocName, 0, i,

      sorry 0 after LocName is the level not i.

      Delete
  13. This comment has been removed by the author.

    ReplyDelete
  14. I want a indicator to show that whether it expand or not.I added a image view to list item.But I can't change the image when expanded.How can I do this?

    ReplyDelete
    Replies
    1. You would change the image loaded in the imageview in the getView of the expanded item.

      @Override
      public View getView(NLevelItem item) {
      View view = inflater.inflate(R.layout.list_item, null);
      TextView tv = (TextView) view.findViewById(R.id.textView);

      tv.setBackgroundColor(Color.YELLOW);

      if (item.isExpanded) {
      tv.setBackgroundColor(Color.BLACK);//or in your case instead of background colors, change the image in the imageView
      }

      String name = (String) ((SomeObject) item.getWrappedObject()).getName();
      tv.setText(name);
      return view;
      }
      });

      Delete
    2. Its working fine thanks..:)

      Delete
    3. in setOnItemClickListener

      if (item.isExpanded())
      {
      view.findViewById(R.id.select_icon).setBackgroundResource(R.drawable.list_item_selector_icon_down);
      }
      else
      {
      view.findViewById(R.id.select_icon).setBackgroundResource(R.drawable.list_item_selector_icon_up);
      }

      does the trick.

      Delete
  15. If I need two touch points.That is Can I put a click listener on text view other than onItemclicklistener?.That is the List view has a click listener and the text in it has also the click listener seperatly?

    ReplyDelete
  16. Yes you can click listeners to views inside a row, however there can be unexpected side effects with certain Views but Views such as a button should be fine though

    ReplyDelete
  17. Is that possible after OnItemclick listener?,If yes how?

    ReplyDelete
  18. just add a Button to the View layout and set a click listener using setOnCLickListener

    ReplyDelete
  19. I need clicked item name in each level how to do this??

    ReplyDelete
    Replies
    1. In the OnItemClickListener

      NLevelItem item = (NLevelItem)adapter.getItem(arg2);
      String name = ((SomeObject)item.getWrappedObject()).getName();

      Delete
  20. if one listview open other listview collapsed, how can we achieve this and if grandparent litview collapsed parent listview automatically collapsed how we can do this? please replay me as soon as possible.

    ReplyDelete
  21. how collapse listview parent and grandparent manually

    ReplyDelete
  22. hi
    how to add + label to specific parent item that have a child group like this

    parent(no childs)
    + parent (have childs)
    +(child) (have childs)
    + parent (have childs)
    (child) (not childs)
    (child) (not childs)
    +(child) (have childs)
    (child) (have childs)

    ReplyDelete
  23. hello,

    Could you plz let me know how can i provide the different names for grandparent,parent and child ?

    Thanks

    ReplyDelete
  24. Hello ,

    How to implement search Functionality in the expandable list with four level. Searchable item should be shown as they are in the level.

    for example is we search for text "abc" which is at third level then search view should shown like following -

    grandparent
    ---- parent
    -----child
    ----- abc

    Thanks.

    ReplyDelete
    Replies
    1. Please,
      Could anyone suggest me how to do search functionality in NLevelItem expandable listview? your answer would be appreciable.
      Thanks.

      Delete
    2. Depends on what you want to display?
      If you search for abc, do you want to also see the parent and grandparent of abc?
      Do you only want to search for the child or do you want results where abc matches in the parent or grandparents name?

      Regardless of what you need, you can do this with a class similar to the NLevelFilter class and a new filterItems method where the filter is based on whether the name matches your search terms, then add the appropriate items to the filtered List.

      Delete
    3. yes, if i search for abc, then i want to show parent and grandparent with it. basically i want a single filter method which works for all level search. and if an item found, then all of its parent should be visible in the search result. Is it possible to create a single filter method for all level search? If possible, could you please suggest/give me that method?

      Thanks.

      Delete
    4. This is just a rough draft, you'll have to do some work to it to get it to work. Create a new Filter class similar to the NLevelFilter and a new AsyncTask to do the search filter.

      class NLevelSearchFilter {

      public void filter(String searchTerms) {
      new AsyncSearchFilter(searchTerms).execute();
      }

      class AsyncSearchFilter extends AsyncTask> {
      String searchTerms;

      public AsyncSearchFilter(String searchTerms) {
      this.searchTerms = searchTerms;
      }

      @Override
      protected ArrayList doInBackground(Void... arg0) {

      return (ArrayList) searchFilter(searchTerms);
      }

      @Override
      protected void onPostExecute(ArrayList result) {
      setFiltered(result);
      NLevelAdapter.this.notifyDataSetChanged();
      }
      }


      }

      public List searchFilter(String searchTerms) {
      for (NLevelListItem item : list) {

      NLevelItem temp = (NLevelItem)item;

      if (((SomeObject)temp.getWrappedObject()).getName().contains(searchTerms)) {
      //item matches the search term
      //expand all parents
      NLevelListItem parent = item;

      while ((parent = parent.getParent())!= null) {
      if (!parent.isExpanded()){
      parent.toggle();
      }
      }
      }
      }

      //now that any item that matched our search terms has been expanded
      //we can now use the normal filter to populate the data
      return filterItems();
      }

      Then you can perform the filtering by calling
      ((NLevelAdapter)listView.getAdapter()).getSearchFilter().filter("abc");

      Delete
  25. how do i set and change imageview in the grandparent such as expand icon and collapse icon?
    i tried but its reflecting only in the last grandparent(i used grandparent.isExpanded() to check and set) but grandparent reflects to only the last added grandparent. i need to set it for all grandparents.
    Reply fast!
    i need grandparent \/(imageview) when not expanded and then grandparent/\(imageview) when expanded.
    and if possible for parent and child also!

    ReplyDelete
  26. I can not click the item, so how can I click on the item in the list.
    My list is as follows.

    header
    - item1
    -- chile1
    -- chile2
    -item2
    -- chile1
    ---d1
    ---d2
    header2
    header3
    -item1


    and clicked code:
    final NLevelAdapter adapter = new NLevelAdapter(list);
    listView.setAdapter(adapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView arg0, View arg1, int arg2,
    long arg3) {
    ((NLevelAdapter) listView.getAdapter()).toggle(arg2);
    ((NLevelAdapter) listView.getAdapter()).getFilter().filter();

    NLevelItem item = (NLevelItem)adapter.getItem(arg2);
    String row = ((SomeObject)item.getWrappedObject()).getName();

    /* if (?) {
    Intent intent = new Intent(Marjaa_Categories_Activity.this, Marjaa_single_Activity.class);
    intent.putExtra("catID", catID);
    intent.putExtra("cat_id", row);
    startActivity(intent);
    }*/





    }
    });

    ReplyDelete
  27. Hi Chris,

    Very good code. Thank you and well done.

    I have a problem with scrolling.
    when I click on row I add some dynamic content (textviews) to a row inside the xml like

    View view = inflater.inflate(R.layout.fourth_row, null);
    LinearLayout row_information = (LinearLayout) view.findViewById(R.id.row_information);

    TextView tv = new TextView(MainActivity.this);
    tv.setText(getResources().getString(R.string.days) + ": " + lDays);
    tv.setTextColor(getResources().getColor(R.color.white));
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    params.setMargins(10, 10, 10, 10);
    row_information.addView(tv);

    this is working normally. When I scroll up or down this textview disappears. I need to implement ViewHolder pattern here to remember their contents.
    but at this point

    new NLevelView()
    {
    @Override
    public View getView(NLevelItem item)
    {

    if(item.getView() == null)
    {
    Log.e("ALP", "view is null");
    } else
    {
    Log.e("ALP", "view is NOT null");
    }

    this stucks...

    normal BaseAdapter code:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

    ViewHolder holder;

    if (convertView == null) {
    convertView = mInflater.inflate(R.layout.list_entry, null);
    holder = new ViewHolder();
    holder.nameTextView = (TextView) convertView.findViewById(R.id.person_name);
    holder.surnameTextView = (TextView) convertView.findViewById(R.id.person_surname);
    holder.personImageView = (ImageView) convertView.findViewById(R.id.person_image);
    convertView.setTag(holder);
    }
    else {
    holder = (ViewHolder) convertView.getTag();
    }

    Person person = getItem(position);

    holder.nameTextView.setText(person.getName());
    holder.surnameTextView.setText(person.getSurname());
    //holder.personImageView.setImageBitmap(person.getImage());

    return convertView;
    }

    there is no convertview or view at the item.getView() code.

    how can we do this?

    ReplyDelete
  28. Hi ,
    thank you so much for the explanation ,i was able to understand this nLevel expandable list view.Iam very new to android.
    I was working on this and iam facing an issue,it would be a great help if you could help me resolve.

    The problem is:
    I have a textview associated with every list item,only a few lines are shown at first.
    On click of the text description,it will show me the full text.
    the issue is:
    i have my parent and first level of children visible at the first look.
    Now if i click the text description it will show the full text.
    on clicking the listview,it is supposed to hide the children listview alone,but it is taking back my textview also to inital state,that is only few lines are shown.i want it to retain the state of textview

    ReplyDelete
  29. how to api calling this app

    ReplyDelete
  30. Thanks, this is great. It works like a charm.

    ReplyDelete
  31. Hi, So I successfully created a 4 Level list using your code. I have used custom layouts for the each level with each level having add and delete buttons where the delete button deletes the item and its subitems and the add button adds children to the parent item. I am using the following modification to the getView() method in the NLevelAdapter.java class https://pastebin.com/CBFR9L6H but unfortunately the onClickListener is being only triggered for the first two levels of the list and the other two levels are being ignored.

    ReplyDelete
  32. Expanding the listview is awesome and tq for coding...but when i expand one the other should be collapse can u help me

    ReplyDelete
  33. Many days of work, saved my life. Thanks

    ReplyDelete
  34. how to expand all the grandparent and parent items and set the height of the listview dynamically from actitvity ? Pls help me.

    ReplyDelete
    Replies
    1. used toggle() method on both of them, done.. Really great code....

      Delete