Skip to main content

Expandable ListView in Android on Complex Layout


Hello,
Last day while at work I encountered an issue such as when I try to implement a expandable listview the override method "@getChildView" is not being called. When I looked around, I found out that the issue is mainly related to the expandable listview being inside a scrollview or any such complex layouts. I fixed the issue after searching through some answers on stack overflow, I have provided the links of the original answer at the end of the post
Response will be of the format
 {  
  "movie_details": [  
   {  
    "name": "Fight Club",  
    "type": "Movie",  
    "id": 1  
   },  
   {  
    "name": "13 reasons why",  
    "type": "Series",  
    "id": 2  
   },  
   {  
    "name": "Dunkirk",  
    "type": "Movie",  
    "id": 3  
   },  
   {  
    "name": "Game of thrones",  
    "type": "Series",  
    "id": 4  
   },  
   {  
    "name": "Godfather",  
    "type": "Movie",  
    "id": 5  
   }  
  ]  
 }  
We are going to create an expandablelistview on the basis of "Movies" and "Series". We will check for the type and classify the list according to it
Let's first create two layouts each for child and parent and we will name them as "child_layout.xml" and "parent_layout.xml"
child_layout.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   android:orientation="vertical"  
   android:padding="10dp"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent">  
   <TextView  
     android:id="@+id/txt_movie_name"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:textSize="18sp"  
     android:text="GodFather"/>  
   <TextView  
     android:id="@+id/txt_id"  
     android:layout_marginTop="5dp"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:textSize="18sp"  
     android:text="10"/>  
 </LinearLayout>  
parent_layout.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:orientation="vertical"  
   android:padding="10dp" >  
   <TextView  
     android:id="@+id/txt_speciality_header"  
     android:text="Movies"  
     android:textSize="15sp"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:paddingLeft="25dp"/>  
 </LinearLayout>  
I used POJO classes to create the expected response and used GSON parsing for parse the JSON Object to Java Object. I called the method "setSortedList" and passed the ArrayList along with it.
  private void setSortedList(DetailsModel movie_details) {  
     if (movie_details.getMovie_details() == null || movie_details.getMovie_details().size() == 0) {  
       return;  
     }  
     Log.e("movdets", new Gson().toJson(movie_details.getMovie_details()));  
     List<String> listDataHeader = new ArrayList<>();  
     listDataHeader.add("Movies");  
     listDataHeader.add("Series");  
     HashMap<String, List<MovieDetails>> listDataChild = new HashMap<>();  
     List<MovieDetails> series = new ArrayList<>();  
     List<MovieDetails> movies = new ArrayList<>();  
     for (MovieDetails child : movie_details.getMovie_details()) {  
       if (child.getType().equals("Movie")) {  
         movies.add(child);  
       }  
       else {  
         series.add(child);  
       }  
     }  
     listDataChild.put("Movies", movies);  
     listDataChild.put("Series", series);  
     Log.e("movdets", new Gson().toJson(movies));  
     Log.e("serdets", new Gson().toJson(series));  
     expandableListViewAdapter =  
         new ExpandableListViewAdapter(MainActivity.this, listDataHeader, listDataChild, movies.size(),  
             series.size());  
     expandableListView.setAdapter(expandableListViewAdapter);  
     expandableListView.expandGroup(0);  
     expandableListView.expandGroup(1);  
   }  
You can see that , we are classifying the list based on it's type only. If it's a "Movie" type then it will be added to the "Movies" list, if not it will be added to the "Series"list. Also we are creating a constructor of the ExpandableListViewAdapter and passing the context, listDataHeader, listDatachild and size of the both the list along with it. I hope you must have added an ExpandableListView in the main_activity.xml file.
Let's create the ExpandableListViewAdapter now.
ExpandableListViewAdapter
 public class ExpandableListViewAdapter extends BaseExpandableListAdapter {  
   private Context mContext;  
   private List<String> mListDataHeader;  
   private HashMap<String, List<MovieDetails>> mListDataChild;  
   private LayoutInflater mLayoutInflater;  
   private int mMovieSize;  
   private int mSeriesSize;  
   public ExpandableListViewAdapter(Context context, List<String> listDataHeader, HashMap<String, List<MovieDetails>> listDataChild, int movie_size, int series_size) {  
     this.mContext = context;  
     this.mListDataHeader = listDataHeader;  
     this.mListDataChild = listDataChild;  
     this.mMovieSize = movie_size;  
     this.mSeriesSize = series_size;  
     if (context != null) {  
       mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
     }  
     Log.e("child", new Gson().toJson(listDataChild));  
   }  
   @Override  
   public int getGroupCount() {  
     return mListDataHeader.size();  
   }  
   @Override  
   public int getChildrenCount(int groupPosition) {  
     return this.mListDataChild.get(this.mListDataHeader.get(groupPosition)).size();  
   }  
   @Override  
   public Object getGroup(int groupPosition) {  
     return mListDataHeader.get(groupPosition);  
   }  
   @Override  
   public Object getChild(int groupPosition, int childPosititon) {  
     return this.mListDataChild.get(this.mListDataHeader.get(groupPosition)).get(childPosititon);  
   }  
   @Override  
   public long getGroupId(int groupPosition) {  
     return groupPosition;  
   }  
   @Override  
   public long getChildId(int groupPosition, int childPosition) {  
     return childPosition;  
   }  
   @Override  
   public boolean hasStableIds() {  
     return true;  
   }  
   @Override  
   public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {  
     String headerTitle = (String) getGroup(groupPosition);  
     if (convertView == null) {  
       LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
       convertView = inflater.inflate(R.layout.parent_layout, parent, false);  
     }  
     TextView lblListHeader = (TextView) convertView.findViewById(R.id.txt_speciality_header);  
     lblListHeader.setTypeface(null, Typeface.BOLD);  
     if (groupPosition == 0) {  
       lblListHeader.setText(headerTitle + " (" + mMovieSize + ")");  
     } else {  
       lblListHeader.setText(headerTitle + " (" + mSeriesSize + ")");  
     }  
     return convertView;  
   }  
   @Override  
   public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView,  
                ViewGroup parent) {  
     final MovieDetails movieDetails = (MovieDetails) getChild(groupPosition, childPosition);  
     if (convertView == null) {  
       convertView = mLayoutInflater.inflate(R.layout.child_layout, parent, false);  
     }  
     TextView name = (TextView)convertView.findViewById(R.id.txt_movie_name);  
     TextView id = (TextView)convertView.findViewById(R.id.txt_id);  
     /* TextView name = ButterKnife.findById(convertView,R.id.txt_movie_name);  
     TextView id = ButterKnife.findById(convertView,R.id.txt_id);*/  
     name.setText(movieDetails.getName());  
     id.setText(String.valueOf(movieDetails.getId()));  
     return convertView;  
   }  
   @Override  
   public boolean isChildSelectable(int i, int i1) {  
     return true;  
   }  
 }  
Now let's run the app and see what happens. Normally, if your main_activity.xml doesn't contains any scrollview or any other complex layout structure, app will run without any problems. But since it does have a scrollview in it, the override method "getchildview" won't be called. when you debug it properly you can find out that the method isn't called at all.
Only the "getGroupView" is called
After some research I found out that, the best and quickest solution for this problem is to write a custom ExpandableListView. We will name it as "ExpandExpandableListView"
ExpandExpandableListView
 public class ExpandExpandableListView extends ExpandableListView {  
   boolean expanded = true;  
   public ExpandExpandableListView(Context context) {  
     super(context);  
   }  
   public ExpandExpandableListView(Context context, AttributeSet attrs) {  
     super(context, attrs);  
   }  
   public ExpandExpandableListView(Context context, AttributeSet attrs, int defStyle) {  
     super(context, attrs, defStyle);  
   }  
   public boolean isExpanded()  
   {  
     return expanded;  
   }  
   @Override  
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
     if (isExpanded())  
     {  
       // Calculate entire height by providing a very large height hint.  
       // View.MEASURED_SIZE_MASK represents the largest height possible.  
       int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);  
       super.onMeasure(widthMeasureSpec, expandSpec);  
       ViewGroup.LayoutParams params = getLayoutParams();  
       params.height = getMeasuredHeight();  
     }else{  
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
     }  
   }  
   public void setExpanded(boolean expanded)  
   {  
     this.expanded = expanded;  
   }  
 }  
The original stackoverflow answer I tried out. Providing the link
stackoverflow answer

Now change your main_activity.xml like
 <?xml version="1.0" encoding="utf-8"?>  
 <ScrollView  
   xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:app="http://schemas.android.com/apk/res-auto"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   tools:context="org.bestdoc.jitha.tvseriesnandmovies.MainActivity">  
   <LinearLayout  
     android:orientation="vertical"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent">  
     <org.bestdoc.jitha.tvseriesnandmovies.ExpandExpandableListView  
       android:id="@+id/exp_list_view"  
       android:layout_width="match_parent"  
       android:layout_height="match_parent">  
     </org.bestdoc.jitha.tvseriesnandmovies.ExpandExpandableListView>  
   </LinearLayout>  
 </ScrollView>  
Now run the app again and it'll work without any bugs this time
Complete project is available on GitHub
Go to github

Comments

Popular posts from this blog

Barcode Scanner in Android using zxing Library

Hi Everyone, Last week I tried to implement a barcode scanner using the zxing library. Writing this blog on this topic because I found the tutorials about barcode scanner using zxing is either pretty much older or confusing. Let's start by importing these two libraries in our Android project. compile 'com.journeyapps:zxing-android-embedded:3.0.2@aar' compile 'com.google.zxing:core:3.2.0' Practically , we only use the back camera for scanning the barcode because it has more clarity and auto-focus feature. But , I will also show how to implement the same with the front camera. Create a new layout and add two buttons for opening the barcode scanner with front camera and back camera. activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http

Customize Run Time Permission Dialog in Android

Hello. We all know how run time permissions came into effect from Android 6.0 ( Marshmallow ). The advantage is that it's more user friendly and it gives the user an idea about why he has to give such permissions for the app. Such as giving his contact details , his location details etc. But, even-though there is one flaw in it. Maybe exactly not a flaw, but we can't really customize that run-time permission dialog or it's contents. Android OS won't allow us to customize it's default settings in this case. So, the only option now remaining is to pop up a dialog just before the original run-time permission dialog. You can then describe to your user what exactly you are going to do by accessing such permissions. I have found many answers on StackOverflow and I combined those answers to my required one. What we are going to do today is asking the permission of user for accessing his location. Simply said, asking him to turn on his GPS. There is a method called che