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.
déplacer adaptateur.getFilter(). Filter (s.toString()); inside onTextChanged() –
J'ai changé cela un peu plus tôt pour tester si cela faisait une différence, mais cela ne fonctionne pas –
une utilisation de s.toString() au lieu de searchEditText.getText() –