2017-05-27 2 views
1

J'essaie de filtrer une vue de liste personnalisée avec un adaptateur personnalisé. J'ai des problèmes avec la duplication des données d'origine et le remettant dans la liste, quand le paramètre de recherche change ou va à vide. Le filtrage fonctionne pour le premier caractère d'entrée, mais s'il est modifié, il ne recherchera plus l'ensemble du jeu de données. Je sais que c'est parce que j'ai besoin d'une liste dupliquée des données d'origine, mais je ne peux pas vraiment le faire fonctionner, parce que je ne sais pas comment l'implémenter correctement parce que j'utilise une classe personnalisée comme mon type de données. J'utilise seulement le nom et la propriété de catégorie de celui-ci, les noms sont les éléments réels et il est également trié par catégories.Filtrage personnalisé de ListView avec un adaptateur de liste personnalisé comprenant des en-têtes de section

I sur la base de mon adaptateur hors de cet exemple: https://gist.github.com/fjfish/3024308

Et voici mon code pour l'adaptateur Liste:

class DataListAdapter extends BaseAdapter implements Filterable { 

    private Context mContext; 
    private List<Object> originalData = null; 
    private List<Object> filteredData = null; 
    private static final int CARRIER = 0; 
    private static final int HEADER = 1; 
    private LayoutInflater inflater; 
    private ItemFilter mFilter = new ItemFilter(); 

    DataListAdapter(Context context, List<Object> input) { 
     this.mContext = context; 
     this.originalData = input; 
     this.filteredData = input; 
     this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    } 

    @Override 
    public int getItemViewType(int position) { 
     if (originalData.get(position) instanceof Carrier) { 
      return CARRIER; 
     } else { 
      return HEADER; 
     } 
    } 

    @Override 
    public int getViewTypeCount() { 
     return 2; 
    } 

    @Override 
    public int getCount() { 
     return originalData.size(); 
    } 

    @Override 
    public Object getItem(int position) { 
     return originalData.get(position); 
    } 

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

    @SuppressLint("InflateParams") 
    @Override 
    public View getView(int position, View convertView, ViewGroup parent) { 
     if (convertView == null) { 
      switch (getItemViewType(position)) { 
       case CARRIER: 
        convertView = inflater.inflate(R.layout.listview_item_data_layout, null); 
        break; 
       case HEADER: 
        convertView = inflater.inflate(R.layout.listview_header_data_layout, null); 
        break; 
      } 
     } 

     switch (getItemViewType(position)) { 
      case CARRIER: 
       TextView name = (TextView) convertView.findViewById(R.id.fragment_data_list_view_carrier_name); 
       name.setText(((Carrier) originalData.get(position)).get_name()); 
       break; 
      case HEADER: 
       TextView category = (TextView) convertView.findViewById(R.id.fragment_data_list_view_category); 
       category.setText((String) originalData.get(position)); 
       break; 
     } 

     return convertView; 
    } 

    @Override 
    public Filter getFilter() { 
     return mFilter; 
    } 

    private class ItemFilter extends Filter { 
     @Override 
     protected FilterResults performFiltering(CharSequence constraint) { 

      DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, null, 1); 
      String filterString = constraint.toString().toLowerCase(); 
      FilterResults results = new FilterResults(); 
      final List<Object> list = originalData; 
      int count = list.size(); 
      final List<Object> nlist = new ArrayList<>(count); 
      String filterableString = ""; 

      for (int i = 0; i < count; i++) { 
       switch (getItemViewType(i)) { 
        case CARRIER: 
         filterableString = ((Carrier)list.get(i)).get_name(); 
         break; 
        case HEADER: 
         filterableString = ""; 
         break; 
       } 
       if(filterableString.toLowerCase().contains(filterString)) { 
        nlist.add(dbHelper.getCarriersWithName(filterableString).get(0)); 
       } 
      } 

      results.values = nlist; 
      results.count = nlist.size(); 

      return results; 
     } 

     @SuppressWarnings("unchecked") 
     @Override 
     protected void publishResults(CharSequence constraint, FilterResults results) { 
      if(results.count == 0) { 
       notifyDataSetInvalidated(); 
      } else { 
       originalData = (List<Object>)results.values; 
       notifyDataSetChanged(); 
      } 

     } 
    } 
} 

Mon activité principale ressemble évidemment comme celui-ci, qui devrait être bien. Le problème réside dans la liste de données filtrée, que je n'arrive pas à utiliser.

List<Object> combinedCategoryCarrierList = dbHelper.getCombinedCategoryCarrierList(); 
adapter = new DataListAdapter(mContext, combinedCategoryCarrierList); 
listView.setAdapter(adapter); 

listView.setTextFilterEnabled(true); 

searchEditText = (EditText) view.findViewById(R.id.fragment_data_search); 
searchEditText.addTextChangedListener(new TextWatcher() { 
    @Override 
    public void beforeTextChanged(CharSequence s, int start, int count, int after) { 

    } 

    @Override 
    public void onTextChanged(CharSequence s, int start, int before, int count) { 

    } 

    @Override 
    public void afterTextChanged(Editable s) { 
     adapter.getFilter().filter(searchEditText.getText().toString()); 
    } 
}); 

Je serais très heureux si quelqu'un peut me montrer un exemple de la façon de le faire avec des types de données personnalisés et les en-têtes de section combinées. Ou même changer mon code :) Je ne peux pas vraiment trouver des exemples où tout cela s'applique.

Edit:The screen looks like this, so I want to keep the category headers when filtering.

+0

déplacer adaptateur.getFilter(). Filter (s.toString()); inside onTextChanged() –

+0

J'ai changé cela un peu plus tôt pour tester si cela faisait une différence, mais cela ne fonctionne pas –

+0

une utilisation de s.toString() au lieu de searchEditText.getText() –

Répondre

1

Je n'ai pas trouvé une solution à mon problème d'origine, mais je suis venu avec une meilleure approche de la situation. Je ne savais pas qu'il y avait un ExpandableListView disponible dans Android. Ceci est fondamentalement un ListView, mais les éléments sont divisés en groupes et leurs enfants qui sont extensibles et rétractables, donc exactement ce que je voulais.

Voici comment je mis en œuvre avec les filtres et les groupes de travail:

Alors, pour commencer, voici mon fichier de mise en page principale. Veuillez noter que j'utilise Fragments, ce qui explique pourquoi le code est un peu différent en ce qui concerne l'obtention du contexte par exemple. La fonctionnalité du composant reste la même.

<?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" > 

    <EditText 
     android:id="@+id/fragment_data_search" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:inputType="text" 
     android:hint="@string/data_search_hint" 
     android:layout_marginTop="8dp" 
     android:layout_marginBottom="8dp" 
     android:layout_marginStart="10dp" 
     android:layout_marginEnd="10dp" /> 

    <ExpandableListView 
     android:id="@+id/fragment_data_expandable_list_view" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:groupIndicator="@null" /> 

</LinearLayout> 

Vous aurez également besoin de deux fichiers de disposition pour vos éléments d'en-tête/de groupe et pour vos éléments enfants. Mon article d'en-tête a un TextView qui affiche le nom de la catégorie et un ImageView qui affiche un + ou - pour montrer si la catégorie est réduite ou développée.

Voici mon fichier de mise en page d'en-tête:

<?xml version="1.0" encoding="utf-8"?> 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="horizontal" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:background="@color/colorAccent" 
    android:descendantFocusability="blocksDescendants" > 

    <TextView 
     android:id="@+id/fragment_data_list_view_category" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:layout_alignParentStart="true" 
     android:gravity="start" 
     android:textStyle="bold" 
     android:textSize="18sp" 
     android:paddingStart="16dp" 
     android:paddingEnd="16dp" 
     android:paddingBottom="8dp" 
     android:paddingTop="8dp" 
     android:textColor="@android:color/primary_text_light" 
     android:text="@string/placeholder_header_listview" 
     android:maxLines="1" 
     android:ellipsize="end" /> 

    <ImageView 
     android:id="@+id/fragment_data_list_view_category_icon" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_gravity="end" 
     android:paddingStart="16dp" 
     android:paddingEnd="16dp" 
     android:paddingBottom="8dp" 
     android:paddingTop="8dp" 
     android:contentDescription="@string/content_description_list_view_header" 
     android:src="@drawable/ic_remove_black_24dp" 
     android:tag="maximized"/> 

</RelativeLayout> 

La propriété android:descendantFocusability="blocksDescendants" Correction d'un bug quand je tenté de mettre un onItemClickListener. Si vous avez ce problème, essayez d'utiliser RelativeLayout pour votre mise en page enfant si ce n'est déjà fait. Il l'a corrigé pour moi, le onClickItemListener ne s'est pas exécuté avec un LinearLayout.

Et voici mon fichier de mise en page pour les éléments enfants:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:orientation="horizontal" 
    android:paddingStart="16dp" 
    android:paddingEnd="16dp" 
    android:paddingTop="8dp" 
    android:paddingBottom="8dp" 
    android:descendantFocusability="blocksDescendants" > 

     <TextView 
      android:id="@+id/fragment_data_list_view_carrier_name" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:text="@string/placeholder_item_listview" 
      android:textSize="18sp" 
      android:textStyle="normal" 
      android:textColor="@android:color/primary_text_light" 
      android:maxLines="1" 
      android:ellipsize="end" /> 

</RelativeLayout> 

Le code suivant est de ma classe de fragment, qui gère toute la logique de la ExpandableListView:

public class Fragment_Data extends Fragment { 

    private Context mContext; 

    private ExpandableListView expandableListView; 
    private List<String> categories_list; 
    private HashMap<String, List<Carrier>> carriers_list; 
    private DataExpandableListAdapter adapter; 

    private DatabaseHelper dbHelper; 

    @Override 
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 
     super.onViewCreated(view, savedInstanceState); 
     getActivity().setTitle(R.string.nav_item_data); 
    } 

Cette première partie montre la déclaration des variables nécessaires et la méthode nécessaire onViewCreated. La classe Carrier est un objet personnalisé avec des propriétés telles que le nom, la catégorie, etc. Le DatabaseHelper est également une classe personnalisée qui handley ma base de données et obtient toutes les données pour moi, qui est moulé dans les objets Carrier. Vous pouvez bien sûr utiliser tout ce que vous aimez comme types de données.

@Nullable 
@Override 
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 

    View view = inflater.inflate(R.layout.fragment_data_layout, container, false); 
    mContext = getContext(); 
    expandableListView = (ExpandableListView) view.findViewById(R.id.fragment_data_expandable_list_view); 
    dbHelper = new DatabaseHelper(mContext, null, null, 1); 

    adapter = new DataExpandableListAdapter(mContext, categories_list, carriers_list); 

    displayList(); 

    expandAllGroups(); 

    EditText searchEditText = (EditText) view.findViewById(R.id.fragment_data_search); 
    searchEditText.addTextChangedListener(new TextWatcher() { 
     @Override 
     public void beforeTextChanged(CharSequence s, int start, int count, int after) { 

     } 

     @Override 
     public void onTextChanged(CharSequence s, int start, int before, int count) { 
      adapter.filterData(s.toString()); 
      expandAllGroups(); 
     } 

     @Override 
     public void afterTextChanged(Editable s) { 

     } 
    }); 

    expandableListView.setOnItemLongClickListener(deleteSelectedItem); 
    expandableListView.setOnChildClickListener(editSelectedItem); 

    return view; 
} 

Les offres de méthode onCreate avec toutes les choses importantes comme le réglage de la carte, ce qui gonfle la mise en page et la mise en événements onClick pour les articles et un événement onTextChange pour le champ de recherche.

private void expandAllGroups() { 
    for(int i = 0; i < adapter.getGroupCount(); i++) { 
     expandableListView.expandGroup(i); 
    } 
} 

private void displayList() { 
    prepareListData(); 

    adapter = new DataExpandableListAdapter(mContext, categories_list, carriers_list); 
    expandableListView.setAdapter(adapter); 

    expandAllGroups(); 
} 

private void prepareListData() { 
    categories_list = new ArrayList<>(); 
    carriers_list = new HashMap<>(); 

    categories_list = dbHelper.getCategoryList(); 

    for(int i = 0; i < categories_list.size(); i++) { 
     List<Carrier> carrierList = dbHelper.getCarriersWithCategory(categories_list.get(i)); 
     carriers_list.put(categories_list.get(i), carrierList); 
    } 
} 

Avec expandAllGroups() vous pouvez simplement développer tous les groupes, parce qu'ils sont réduits par défaut. Le displayList() définit simplement l'adaptateur pour ExpandableListView et appelle prepareListData(), qui remplit à la fois la liste de catégorie (groupe) et la liste de porteuse (enfant). Notez que la liste des enfants est un hashmap avec la clé étant la catégorie et la valeur d'une liste de porteuse en elle-même, donc l'adaptateur sait quels éléments enfants appartiennent à quel parent.

Voici le code pour le Adapter:

class DataExpandableListAdapter extends BaseExpandableListAdapter { 

private Context mContext; 
private List<String> list_categories = new ArrayList<>(); 
private List<String> list_categories_original = new ArrayList<>(); 
private HashMap<String, List<Carrier>> list_carriers = new HashMap<>(); 
private HashMap<String, List<Carrier>> list_carriers_original = new HashMap<>(); 

DataExpandableListAdapter(Context context, List<String> categories, HashMap<String, List<Carrier>> carriers) { 
    this.mContext = context; 
    this.list_categories = categories; 
    this.list_categories_original = categories; 
    this.list_carriers = carriers; 
    this.list_carriers_original = carriers; 
} 

Vous devez avoir une copie de vos deux listes originales, si vous souhaitez utiliser le filtrage. Ceci est utilisé pour restaurer toutes les données lorsque la requête de recherche est vide ou à nouveau ou simplement différente. Le filtre supprime tous les éléments qui ne correspondent pas à la liste d'origine.

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

@Override 
public int getChildrenCount(int groupPosition) { 
    return this.list_carriers.get(this.list_categories.get(groupPosition)).size(); 
} 

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

@Override 
public Object getChild(int groupPosition, int childPosition) { 
    return this.list_carriers.get(this.list_categories.get(groupPosition)).get(childPosition); 
} 

@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 boolean isChildSelectable(int groupPosition, int childPosition) { 
     return true; 
    } 

Ces méthodes doivent être écrasées lorsque vous développez le BaseExpandableListAdapter. Vous pouvez remplacer toutes les instructions return null; par quelque chose de similaire, en fonction de vos données.

@SuppressLint("InflateParams") 
@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.listview_header_data_layout, null); 
    } 

    TextView lblListHeader = (TextView) convertView.findViewById(R.id.fragment_data_list_view_category); 
    lblListHeader.setText(headerTitle); 

    ImageView expandIcon = (ImageView) convertView.findViewById(R.id.fragment_data_list_view_category_icon); 
    if(isExpanded) { 
     expandIcon.setImageResource(R.drawable.ic_remove_black_24dp); 
    } else { 
     expandIcon.setImageResource(R.drawable.ic_add_black_24dp); 
    } 

    return convertView; 
} 

Cette méthode surchargée simplement la mise en page gonfle pour chaque élément d'en-tête/groupe/catégorie et définit ce texte et l'image en fonction de l'état du groupe, si elle est réduit ou développé.

@SuppressLint("InflateParams") 
@Override 
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { 

     final String carrierName = ((Carrier)getChild(groupPosition, childPosition)).get_name(); 

     if (convertView == null) { 
      LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      convertView = inflater.inflate(R.layout.listview_item_data_layout, null); 
     } 

     TextView txtListChild = (TextView) convertView.findViewById(R.id.fragment_data_list_view_carrier_name); 

     txtListChild.setText(carrierName); 
     return convertView; 
} 

Même chose avec les éléments enfants. J'utilise cette méthode personnalisée pour filtrer tous les éléments dont j'ai besoin correspondant à la requête de recherche. N'oubliez pas que cette méthode est appelée chaque fois que le texte de l'EditText change. Cela peut être très déroutant, mais cela doit être compliqué dans mon cas à cause de ma structure de données. Cela pourrait être plus facile dans votre cas.

Ce que cela fait essentiellement, c'est qu'il vérifie d'abord si la requête de recherche est vide.Et s'il est vide, il redéfinit les deux listes dans les listes de "sauvegarde" que j'ai assignées dans le constructeur. J'appelle alors notifyDataSetInvalidated(); pour dire à l'adaptateur que son contenu sera rempli. Cela peut marcher aussi avec notifyDataSetChanged();, je n'ai pas testé cela, mais cela devrait être fait puisque nous avons remis les anciennes listes à leur ancien état.

Maintenant, si la requête de recherche n'est pas vide, je passe en revue toutes les catégories et je vois si cette catégorie spécifique contient des éléments qui correspondent à la requête de recherche. Si tel est le cas, cet élément est ajouté à une nouvelle liste d'enfants et sa catégorie/parent sera également ajoutée à une nouvelle liste de parents, si ce n'est déjà fait.

Et last but not least, la méthode vérifie si les deux listes ne sont pas vides. Si elles ne sont pas vides, les listes originales sont vidées et les nouvelles données filtrées sont mises en place et l'adaptateur est averti en appelant notifyDataSetChanged();

J'espère que cela aidera tout le monde.

+0

Dommage que je n'ai qu'un seul vote - une excellente réponse! – 0X0nosugar

+0

Haha merci :) –