ListView addHeaderView provoque une augmentation de la position de un?

97

Vous trouverez ci-dessous un extrait de code avec un ListView. J'ai ajouté un emptyView et un headerView. L'ajout de headerView entraîne une augmentation de la position dans onItemClick.

Donc, sans headerView, le premier élément de la liste aurait la position 0, avec l'en-têteView la position du premier élément de la liste serait 1!

Cela provoque des erreurs dans mon adaptateur, par exemple lors de l'appel de getItem () et de l'utilisation d'autres méthodes, voir ci-dessous. Chose étrange: dans la méthode getView () de l'adaptateur, le premier élément de liste est demandé avec la position 0 même si l'en-têteView est ajouté !!

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ListView list = (ListView) viewSwitcher.findViewById(R.id.list);
    View emptyView = viewSwitcher.findViewById(R.id.empty);
    list.setEmptyView(emptyView);

    View sectionHeading = inflater.inflate(R.layout.heading, list, false);
    TextView sectionHeadingTextView = (TextView) sectionHeading.findViewById(R.id.headingText);
    sectionHeadingTextView.setText(headerText);
    list.addHeaderView(sectionHeading);

    list.setAdapter(listAdapter);

    list.setOnItemClickListener(new OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            listAdapter.getItem(position);
            //Do something with it
        }
    });
}

Quelques méthodes d'adaptateur:

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

@Override
public int getItemViewType(int position) {
    return (position < items.size()) ? ITEM_NORMAL : ITEM_ADVANCED;
}

    @Override
public Product getItem(int position) {
    return items.get(position);
}
@Override
public boolean areAllItemsEnabled() {
    return false;
}

@Override
public boolean isEnabled(int position) {
    return (position < items.size());
}

Est-ce le comportement normal lors de l'ajout d'un headerView? Et comment surmonter les problèmes de mon adaptateur?

d1rk
la source
1
Votre question avait la réponse à ma question. +1
Shashank Degloorkar

Réponses:

113

Je viens de tomber sur ce problème et la meilleure façon semble d'utiliser le ListView.getItemAtPosition(position)au lieu de ListAdapter.getItem(position)car la ListViewversion tient compte des en-têtes, c'est-à-dire: -

Faites ceci à la place:

myListView.getItemAtPosition(position)
Ian Warwick
la source
1
Tellement vrai! onItemClickvous donne parentune raison. Vous pouvez utiliserparent.getItemAtPosition(position)
Brais Gabin
3
Cela n'a pas fonctionné pour moi alors que l'utilisation de la solution d'adaptateur décoré / wrapper @whuandroid fonctionne.
Dori
3
vous devez également lancer l'élément dans ce cas.
njzk2
Cela ne fonctionne pas pour moi même si je lance le parent vers ListView. J'ai juste besoin d'exclure la position == 0 ...
PawelP
Ça ne marche pas pour moi. AdapterViewpasse également simplement au listAdapter ajouté. return (adapter == null || position < 0) ? null : adapter.getItem(position);
philipp
31

Si vous ne vous souciez pas du clic sur l'en-tête, soustrayez le nombre de vues d'en-tête de la position pour obtenir la position de votre adaptateur:

        listView.addHeaderView(inflater.inflate(
                R.layout.my_hdr_layout, null), null, false);
        listView.setAdapter(m_adapter);
        listView.setOnItemClickListener(new OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view,
                int position, long id) {
            position -= listView.getHeaderViewsCount();
            final MyObject object = m_adapter.getItem(position);

        }
    });
farid_z
la source
Cela devrait être la meilleure réponse, je ne me soucie pas de l'en-tête, je me soucie de la liste.
Ninja
Je pense que cette solution ne fonctionne pas pour le dernier OS 7.0
sha
27

Si vous ajoutez un en-tête à un, ListViewcela devient le premier élément de la liste. Vous pouvez compenser cela en remplaçant le getCount()pour renvoyer un élément de moins, mais assurez-vous de gérer le getItem(), getItemId()et ainsi de suite car l'en-tête serait l'élément en position 0.

akshaydashrath
la source
6
Le remplacement de getCount () pour renvoyer un élément de moins ne fonctionne pas, car alors je manque le dernier élément de la liste. S'il n'y a qu'un seul élément dans la liste, getView () n'est jamais appelé. De plus, l'adaptateur est découplé de la vue de liste et ne sait pas si un en-tête a été ajouté à la vue de liste ou non.
d1rk
2
Dans le ListItemClick set position = position - 1 s'il y a un en-tête attaché, je pense que c'est ce que j'ai fait quand j'ai fait face à ce problème particulier, ce n'est pas très soigné, mais la seule façon dont je peux penser
akshaydashrath
3
Je suppose que le moyen le plus simple serait de définir position = position- (nombre de headerviews ajoutés) dans OnItemClickListener comme vous l'avez suggéré. J'ai changé mon adaptateur pour gérer la vue d'ensemble elle-même, donc je n'ai pas besoin d'appeler addHeaderView sur la liste. Bien que l'adaptateur soit devenu un peu plus complexe.
d1rk
22
Le "nombre de headerviews ajoutés" peut être obtenu avec:ListView.getHeaderViewsCount()
John
2
Je ne vois pas en quoi c'est la bonne réponse. @Ian Warwick a posté le bon, même si je suppose un peu tard.
sidon
14

Lors de la mise en œuvre, AdapterView.OnItemClickListenernous soustrayons simplement les en-têtes de la position.

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    position -= mListView.getHeaderViewsCount();
    // what ever else follows...
}
philipp
la source
13

Pas besoin de changer de code. Utilisez simplement le code ci-dessous.

 list.setOnItemClickListener(new OnItemClickListener() {
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
      parent.getAdapter().getItem(position);
         //Do something with it
     }
});
Ramesh Akula
la source
1
Cela nécessitera généralement un casting, car l'élément renvoyé est simplement "Objet".
développeur android
9

N'utilisez pas votre adaptateur, utilisez l'adaptateur «décoré» de getAdapter.

cycDroid
la source
+1 cet IMO est la voie à suivre - l'adaptateur que vous fournissez est emballé avec un autre adaptateur qui prend en compte le décalage d'index lorsque vous fournissez un en-tête. Ce code pour utiliser ceci est donné par @Ramesh ailleurs sur cette page (+1 pour vous aussi!)
Dori
5

Une solution à cela consiste à mapper l'id à l'index. Fondamentalement, faites que id renvoie l'index dans la liste et l'écouteur de clic ferait:

public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            listAdapter.getItem((int) id);
}
Heinrisch
la source
3

Salut, nous pouvons résoudre cela de cette manière, vous créez d'abord votre vue d'en-tête, après l'injection dans votre liste et la dernière étape impossible le setOnClickListener. ses travaux pour moi. toute rétroaction bienvenue

ViewGroup headerView = (ViewGroup) getLayoutInflater().inflate(R.layout.your_header, list,false);
list.addHeaderView(headerView);
headerView.setEnabled(false);
headerView.setOnClickListener(null);
Ezequiel García
la source
C'est en bas de la liste des réponses, mais c'est vraiment utile. Si vous ne faites pas ces changements, alors votre vue d'en-tête restera cliquable, avec des effets d'entraînement, etc.
Scott Ferguson
0

Pas pour ajouter beaucoup de nouveautés que @Ramesh n'a pas ajoutées, mais un contexte supplémentaire:

personList.setOnItemClickListener(new OnItemClickListener() {
    @SuppressWarnings("unchecked")
    public void onItemClick( AdapterView<?> parent, View view, int position, long duration) {
        Map<String, Object> person = (Map<String, Object>) parent.getAdapter().getItem(position);
        if (person != null){

            // If the header was clicked on a null  is returned

            OnPersonUiChangeListener listener = (OnPersonUiChangeListener) getActivity();
            listener.onSelectedPersonChanged(person);
        }
    }
});
Peter Shannon
la source
0

Use getSelectedItemPosition () Renvoie la position de l'élément actuellement sélectionné dans l'ensemble de données de l'adaptateur. Cette méthode n'a pas besoin de se soucier de la taille des en-têtes ou des pieds de page.

luhaiwork
la source
0

Cela pourrait aider quelqu'un, en améliorant la réponse de Farid_z et Philipp, nous pouvons correctement appliquer setItemChecked et setTitle avec quelque chose comme ça

        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
        position += 1;
        mDrawerList.setItemChecked(position, true);
        mDrawerList.setSelection(position);
        position -= 1;
        setTitle(mNavigationDrawerItemTitles[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
truespan
la source