title: Expandable ListView is very slow in android os
body: <p>My expandable <code>listview</code> scroll very slow and take a while when I click on the parent category until I see the <code>child</code> view.</p>
  
  <p>Group List Activity:</p>
  
  <pre><code>public class GroupsListActivity extends Activity {
  
      String loggedUserId = Model.getInstance().getLoggedUserId();
  
      List&lt;String&gt; groupsList;
      static ExpandableListView expandableListView;
      HashMap&lt;String, List&lt;Group&gt;&gt; groupCategories = new HashMap&lt;String, List&lt;Group&gt;&gt;();
      static ProgressBar spinner;
      static TextView textLoading;
      ImageButton createCategoryButton;
      static Adapter adapter;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          // Set layout for this activity
          setContentView(R.layout.expandable_list);
  
          // Set actionbar title
          getActionBar().show();
          getActionBar().setTitle(Html.fromHtml("&lt;font color='#fffffff'&gt;Groups&lt;/font&gt;"));
  
          if (loggedUserId != null)
              Log.d("TAG", "My Groups for user ID: " + loggedUserId);
  
          // Connect between buttons to layout id
          expandableListView = (ExpandableListView) findViewById(R.id.exp_list);
          spinner = (ProgressBar) findViewById(R.id.spinner);
          createCategoryButton = (ImageButton) findViewById(R.id.createCategory);
          textLoading = (TextView) findViewById(R.id.textLoading);
  
          // Loading data to expandable group list asynchronously
          AsyncTask&lt;String, String, HashMap&lt;String, List&lt;Group&gt;&gt;&gt; task = new AsyncTask&lt;String, String, HashMap&lt;String, List&lt;Group&gt;&gt;&gt;() {
              @Override
              protected HashMap&lt;String, List&lt;Group&gt;&gt; doInBackground(String... params) {
                  return DataProvider.getInfo();
              }
  
              @Override
              protected void onPostExecute(HashMap&lt;String, List&lt;Group&gt;&gt; listHashMap) {
                  super.onPostExecute(listHashMap);
  
                  // Setting adapter and creating group list
                  groupCategories = listHashMap;
                  groupsList = new ArrayList&lt;String&gt;(groupCategories.keySet());
                  adapter = new Adapter(GroupsListActivity.this, groupCategories, groupsList, GroupsListActivity.this);
                  expandableListView.setAdapter(adapter);
  
                  // Hide spinner after loading
                  spinner.setVisibility(View.GONE);
                  textLoading.setVisibility(View.GONE);
              }
          };
          task.execute();
  
          // Setting listener for group click
          expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
              @Override
              public boolean onChildClick(ExpandableListView parent, View v, int parentPosition, int childPosition, long id) {
                  // After selecting a group on row - open contacts list for this group
                  expandableListView.setEnabled(false);
                  openContactListForGroup(groupCategories.get(groupsList.get(parentPosition)).get(childPosition).getGroupID());
                  return true;
              }
          });
  
          // Setting listener for create group click
          createCategoryButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  createCategoryButton.setEnabled(false);
                  onCategoryCreate(GroupsListActivity.this, createCategoryButton);
              }
          });
      }
  
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          // Inflate the menu; this adds items to the action bar if it is present.
          getMenuInflater().inflate(R.menu.menu_actionbar_groups, menu);
          return true;
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          switch (item.getItemId()) {
              case R.id.action_create:
                  onCreate();
                  return true;
  
              case R.id.action_search:
                  onSearch();
                  return true;
  
              case R.id.action_favorites:
                  onFavorites();
                  return true;
  
              case R.id.action_settings:
                  onSettings();
                  return true;
  
              default:
                  return super.onOptionsItemSelected(item);
          }
      }
  
      // Menu methods
      private void onCreate() {
          Log.d("TAG", "Create button was pressed");
          Intent i = new
                  Intent(getApplicationContext(),
                  CreateGroupActivity.class);
  
          startActivity(i);
          overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
      }
  
      private void onSearch() {
          Log.d("TAG", "Search button was pressed");
          Intent i = new
                  Intent(getApplicationContext(),
                  SearchActivity.class);
  
          startActivity(i);
          overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
      }
  
      private void onFavorites() {
          Log.d("TAG", "Favorites button was pressed");
          Intent i = new
                  Intent(getApplicationContext(),
                  FavoritesListActivity.class);
  
          startActivity(i);
          overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
      }
  
      private void onSettings() {
          Log.d("TAG", "Settings button was pressed");
  
          // Settings activity
          Intent i = new
                  Intent(getApplicationContext(),
                  SettingsActivity.class);
  
          startActivity(i);
          overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
      }
  
      // Methods to handle action buttons
      private void onCategoryCreate(final Activity activity, final ImageButton createCategoryButton) {
          final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
          createCategoryButton.setEnabled(true);
  
          final String title = "Create a new category";
          String message = "Type a name for your new category";
  
          // Set dialog edit_text
          final EditText categoryNameTextView = new EditText(activity);
          categoryNameTextView.setHint("Type your category name");
          LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                  LinearLayout.LayoutParams.MATCH_PARENT,
                  LinearLayout.LayoutParams.MATCH_PARENT);
          categoryNameTextView.setLayoutParams(lp);
          builder.setView(categoryNameTextView);
  
          // Set dialog title and message
          if (title != null)
              builder.setTitle(Html.fromHtml("&lt;font color='#dc1c1c'&gt;" + title + "&lt;/font&gt;")).setMessage(message);
  
          // Set dialog buttons
          builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                  final String newCategoryName = categoryNameTextView.getText().toString();
  
                  // Check if contains only spaces
                  if (!(newCategoryName.trim().length() &gt; 0))
                      Toast.makeText(activity, "Type at least 1 letter to create the category", Toast.LENGTH_LONG).show();
  
                      // Check if category name already exists
                  else if (groupsList.contains(newCategoryName))
                      Toast.makeText(activity, newCategoryName + " already exist. Please type another category name", Toast.LENGTH_LONG).show();
  
                  else {
                      // Create a new category in server and add user to a sample group
                      adapter.getCategoriesList().add(newCategoryName);
                      adapter.getGroupsList().put(newCategoryName, Collections.&lt;Group&gt;emptyList());
  
                      // Update adapter and show toast to user
                      GroupsListActivity.updateAdapter();
                      Toast.makeText(activity, "You created " + newCategoryName + " category", Toast.LENGTH_LONG).show();
                  }
              }
          });
          builder.setNegativeButton(
                  "Cancel",
                  new DialogInterface.OnClickListener() {
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                          dialog.dismiss();
                      }
                  });
  
          builder.setIcon(R.mipmap.edit);
          builder.show();
      }
  
      public void openContactListForGroup(String groupId) {
          // Contacts List activity
          Intent i = new
                  Intent(getApplicationContext(),
                  ContactsListActivity.class);
  
          // Pass to details activity the logged group id and start activity
          Bundle b = new Bundle();
          b.putString("groupId", groupId);
          i.putExtras(b);
  
          startActivity(i);
          overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
      }
  
      // Static methods to use from other activities
      public static void updateAdapter() {
          spinner.setVisibility(View.VISIBLE);
          adapter.notifyDataSetChanged();
  
          // Hide spinner after adapter finish the update
          expandableListView.post(new Runnable() {
              @Override
              public void run() {
                  spinner.setVisibility(View.GONE);
              }
          });
      }
  
      public static void addGroupToList(String groupId) {
          Model.getInstance().getGroup(groupId, new Model.groupReturnedListener() {
              @Override
              public void addGroupToLocal(Group group) {
                  // Add group to category Others in Group List Activity
                  if (adapter.getGroupsList().get("Others").size() == 0) {
                      // Add group to empty list
                      List&lt;Group&gt; list = new LinkedList&lt;Group&gt;();
                      list.add(group);
                      adapter.getGroupsList().put("Others", list);
                      adapter.notifyDataSetChanged();
                  } else {
                      // Add group to an existing list
                      adapter.getGroupsList().get("Others").add(group);
                      adapter.notifyDataSetChanged();
                  }
              }
          });
      }
  
      public static void removeGroupFromList(String groupId) {
          int position = -1;
  
          // Get category position
          String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(groupId);
          List&lt;Group&gt; data = adapter.getGroupsList().get(oldCategoryName);
  
          // Search for group position
          for (Group group : data) {
              if (group.getGroupID().equals(groupId)) {
                  position = data.indexOf(group);
                  break;
              }
          }
  
          // Groups was found
          if (position != -1) {
              data.remove(position);
              adapter.notifyDataSetChanged();
          }
      }
  
      public static void updateGroupFromList(Group group) {
          int position = -1;
  
          // Get category position
          String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(group.getGroupID());
          List&lt;Group&gt; data = adapter.getGroupsList().get(oldCategoryName);
  
          // Search for group position
          for (Group groupIterator : data) {
              if (groupIterator.getGroupID().equals(group.getGroupID())) {
                  position = data.indexOf(groupIterator);
                  break;
              }
          }
  
          // Groups was found
          if (position != -1) {
              data.remove(position);
              data.add(group);
              adapter.notifyDataSetChanged();
          }
      }
  
      // Other methods
      @Override
      protected void onResume() {
          super.onResume();
          expandableListView.setEnabled(true);
      }
  
      @Override
      public void onBackPressed() {
          ExitDialog exitDialog = new ExitDialog(GroupsListActivity.this);
          exitDialog.show();
      }
  }
  </code></pre>
  
  <p>This is the activity of the Adapter where I load the expandable <code>listview</code> :</p>
  
  <pre><code>   @Override
      public View getGroupView(final int parent, boolean isExpanded, View convertView, ViewGroup parentView) {
          final String categoryName = (String)getGroup(parent);
  
          ParentViewHolder pHolder = null;
  
          if(convertView == null) {
              pHolder = new ParentViewHolder();
              LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              convertView = inflater.inflate(R.layout.expandable_list_parent, parentView, false);
  
              // Connect between buttons to layout id
              pHolder.categoryNameTextView = (TextView) convertView.findViewById(R.id.categoryName);
              pHolder.editCategory = (ImageButton) convertView.findViewById(R.id.editCategory);
              pHolder.deleteCategory = (ImageButton) convertView.findViewById(R.id.deleteCategory);
              convertView.setTag(pHolder);
          }
          else {
              pHolder = (ParentViewHolder) convertView.getTag();
          }
  
          // Hide edit and delete button for category name Others
          if(categoriesList.get(parent).equals("Others")){
              pHolder.editCategory.setVisibility(View.GONE);
              pHolder.deleteCategory.setVisibility(View.GONE);
          }
  
          else {
              pHolder.editCategory.setVisibility(View.VISIBLE);
              pHolder.deleteCategory.setVisibility(View.VISIBLE);
          }
  
          // Set category name on row
          pHolder.categoryNameTextView.setTypeface(null, Typeface.BOLD);
          pHolder.categoryNameTextView.setText(categoryName + ": " + getChildrenCount(parent));
  
          // Set edit category button listener
          final ParentViewHolder finalPHolder = pHolder;
          pHolder.editCategory.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  finalPHolder.editCategory.setEnabled(false);
                  editCategoryName(activity, finalPHolder.categoryNameTextView.getText().toString().toString().split(": ")[0], finalPHolder.editCategory, parent);
              }
          });
  
          // Set delete category button listener
          pHolder.deleteCategory.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  finalPHolder.deleteCategory.setEnabled(false);
                  deleteCategory(activity, categoryName, finalPHolder.deleteCategory);
              }
          });
  
          return convertView;
      }
  
      @Override
      public View getChildView(final int parent, final int child, boolean lastChild, View convertView, ViewGroup parentView) {
          final Group group = (Group)getChild(parent, child);
  
          ChildViewHolder cHolder = null;
  
          if(convertView == null){
              cHolder = new ChildViewHolder();
              LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              convertView = inflater.inflate(R.layout.expandable_list_child, parentView, false);
  
              // Connect between buttons to layout id
              cHolder.groupImage = (ImageView) convertView.findViewById(R.id.groupImage);
              cHolder.groupName = (TextView) convertView.findViewById(R.id.groupName);
              cHolder.moveCategory = (ImageButton) convertView.findViewById(R.id.moveCategory);
              cHolder.groupFavoritesButton = (ImageButton) convertView.findViewById(R.id.groupFavorites);
              cHolder.groupLeaveGroupButton = (Button) convertView.findViewById(R.id.groupLeave);
              cHolder.groupImageProgressbar = (ProgressBar) convertView.findViewById(R.id.groupImageProgressBar);
              convertView.setTag(cHolder);
          } else {
              cHolder = (ChildViewHolder) convertView.getTag();
          }
  
          // Set group name on row
          cHolder.groupName.setText(group.getName());
  
          // Load group image
          cHolder.groupImageProgressbar.setVisibility(View.VISIBLE);
          final ChildViewHolder finalHolder = cHolder;
          Model.getInstance().getGroupImage(group.getImageName(), new Model.LoadImageListener() {
              @Override
              public void onResult(Bitmap imageBmp) {
                  finalHolder.groupImage.setImageBitmap(imageBmp);
                  finalHolder.groupImageProgressbar.setVisibility(View.GONE);
                  finalHolder.groupImage.setVisibility(View.VISIBLE);
              }
          });
  
          // Set move category button listener
          cHolder.moveCategory.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  finalHolder.moveCategory.setEnabled(false);
                  showDialogMoveCategory(activity, group.getGroupID(), finalHolder.moveCategory);
              }
          });
  
  
          // After click on group image - open profile for this group
          cHolder.groupImage.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  onGroupSelected(group.getGroupID());
              }
          });
  
          // Setting favorite Button Image
          boolean isFavorite = Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID());
  
          if(isFavorite)
              cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on);
          else
              cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off);
  
          // Setting favorite Button Action
          cHolder.groupFavoritesButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  // Add group to favorites
                  if (!Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID())) {
                      finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on);
                      Toast.makeText(activity,
                              "The group " + group.getName() + " was added to favorites", Toast.LENGTH_SHORT).show();
                      Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "true");
  
                  } else {
                      // Delete group from favorites
                      finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off);
                      Toast.makeText(activity,
                              "The group " + group.getName() + " was removed from favorites", Toast.LENGTH_SHORT).show();
                      Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "false");
                  }
              }
          });
  
          // After click on group action - leave group
          cHolder.groupLeaveGroupButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  finalHolder.groupLeaveGroupButton.setEnabled(false);
                  showDialogLeaveGroup(activity, "Are you sure ?", "This action will remove yourself from the group " + group.getName(), group.getGroupID(), parent, child);
                  finalHolder.groupLeaveGroupButton.setEnabled(true);
              }
          });
  
          return convertView;
      }
  </code></pre>
  
  <p>The loading image method:</p>
  
  <pre><code>public void getGroupImage(final String imageName, final LoadImageListener listener) {
          AsyncTask&lt;String, String, Bitmap&gt; task = new AsyncTask&lt;String, String, Bitmap&gt;() {
              @Override
              protected Bitmap doInBackground(String... params) {
                  Bitmap bmp = loadImageFromFile(imageName);              //first try to find the image on the device
                  //  Bitmap bmp = null;
                  if (bmp == null) {                                      //if image not found - try downloading it from parse
                      bmp = modelParse.getGroupImage(imageName);
                      if (bmp != null)
                          saveImageToFile(bmp, imageName);    //save the image locally for next time *****
                  }
                  Bitmap scaledBitmap = scaleDown(bmp, 200, true);
                  return scaledBitmap;
              }
  
              @Override
              protected void onPostExecute(Bitmap result) {
                  listener.onResult(result);
              }
          };
          task.execute();
      }
  
   private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
          FileOutputStream fos;
          OutputStream out = null;
          try {
              File dir = context.getExternalFilesDir(null);
              out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
              imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
              out.close();
          } catch (FileNotFoundException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  
      private Bitmap loadImageFromFile(String fileName) {
          Bitmap bitmap = null;
          try {
              File dir = context.getExternalFilesDir(null);
              InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
              bitmap = BitmapFactory.decodeStream(inputStream);
  
          } catch (FileNotFoundException e) {
              e.printStackTrace();
          }
          return bitmap;
      }
  </code></pre>