Comment utiliser Android Spinner comme une liste déroulante
il m'a fallu un certain temps pour obtenir ma tête autour de L'Androïde Spinner. Après plusieurs tentatives infructueuses de mise en œuvre, et après avoir lu de nombreuses questions partiellement similaires à la mienne mais sans réponses satisfaisantes , et certains sans aucune réponse du tout, par exemple ici et ici , je comprends enfin qu'un "spinner" dans Android n'est pas censé être la même chose qu'une "liste déroulante" à partir d'applications de bureau, ou un select en HTML. Cependant, ce dont mon application (et je devine que les applications de toutes les autres affiches dont les questions sont similaires) a besoin est quelque chose qui fonctionne comme une boîte de dépose-vers le bas, pas comme un spinner.
mes deux problèmes sont avec ce que j'ai d'abord considéré comme des idiosynchrasies l'OnItemSelectedListener (j'ai vu ces questions séparées sur ce site mais pas comme une seule):
- une première sélection du premier élément de la liste est déclenchée automatiquement sans l'utilisateur de l'interaction.
- Lorsque l'élément qui est sélectionné est à nouveau sélectionnée par l'utilisateur, il est ignoré.
maintenant je me rends compte que, quand vous y pensez, il est logique que cela se produise sur un spinner - il doit commencer avec une valeur par défaut sélectionnée, et vous le lancer seulement pour changer cette valeur, pas pour "re-select" une valeur - la documentation dit en fait: "ce callback est invoqué seulement lorsque la position nouvellement sélectionnée est différente de la position précédemment sélectionnée". Et j'ai vu des réponses suggérant que vous mettiez un drapeau pour ignorer la première sélection automatique - je suppose que je pourrais vivre avec ça s'il n'y a pas d'autre moyen.
mais puisque ce que je veux vraiment est une liste déroulante qui se comporte comme une liste déroulante devrait (et comme les utilisateurs peuvent et doivent s'attendre), ce dont j'ai besoin est quelque chose comme a Spinner qui se comporte comme un drop-down, comme un combo-box. Je ne me soucie pas de toute pré-sélection automatique (qui devrait se produire sans déclencher mon auditeur), et je veux savoir sur chaque sélection, même si elle est la même que précédemment (après tout, l'utilisateur sélectionné le même article à nouveau).
So... y a-t-il quelque chose dans Android qui peut faire cela, ou quelque Solution pour faire Qu'un Spinner se comporte comme une liste déroulante? Si il est une question comme celle-ci sur ce site que je n'ai pas trouvé, et qui a une réponse satisfaisante, s'il vous plaît laissez-moi savoir (dans ce cas, je m'excuse sincèrement de répéter la question).
6 réponses
+1 à la réponse de David. Cependant, voici une suggestion d'implémentation qui n'implique pas de copier-coller du code source (qui, soit dit en passant, ressemble exactement au même que David a posté en 2.3 ainsi que ):
@Override
void setSelectionInt(int position, boolean animate) {
mOldSelectedPosition = INVALID_POSITION;
super.setSelectionInt(position, animate);
}
de cette façon, vous allez tromper la méthode de parent en pensant que c'est une nouvelle position à chaque fois.
alternativement, vous pouvez essayer de définir la position à invalide lorsque le spinner est cliqué et le réglage de retour onNothingSelected
. Ce n'est pas aussi agréable, parce que l'utilisateur ne verra pas quel élément est sélectionné pendant que la boîte de dialogue est en place.
Ok, je pense que j'ai trouvé une solution pour ma propre situation avec L'aide de la réponse de David et de Félix (je crois que David a aidé Félix', qui à son tour a aidé le mien). J'ai pensé que je pourrais le poster ici avec un échantillon de code au cas où quelqu'un d'autre trouve cette approche utile aussi bien. Il résout également mes deux problèmes (à la fois la sélection automatique non désirée et le déclencheur de re-sélection désiré).
ce que j'ai fait est ajouté un " s'il vous plaît sélectionner "article factice comme premier article dans ma liste (d'abord juste pour contourner le problème de sélection automatique afin que je puisse ignorer quand il a été sélectionné sans interaction avec l'utilisateur), et puis, quand un autre article est sélectionné et j'ai manipulé la sélection, je simplement réinitialiser la broche à l'article factice (qui est ignoré). En y repensant, j'aurais dû y penser il y a longtemps avant de décider de poster ma question sur ce site, mais les choses sont toujours plus évidents en rétrospective... et j'ai découvert que le fait d'écrire ma question m'aidait en fait à réfléchir à ce que je voulais accomplir.
évidemment, si avoir un objet fictif ne correspond pas à votre situation, cela pourrait ne pas être la solution idéale pour vous, mais puisque ce que je voulais était de déclencher une action lorsque l'Utilisateur a sélectionné une valeur (et avoir la valeur reste sélectionnée n'est pas nécessaire dans mon cas spécifique), cela fonctionne très bien. Je vais essayer d'ajouter un code simplifié exemple (peut pas compiler comme tel, j'ai arraché quelques bits de mon code de travail et renommé les choses avant de coller, mais j'espère que vous obtiendrez l'idée) ci-dessous.
tout D'abord, la liste activité (dans mon cas) contenant le spinner, appelons-la Mylistactivité:
public class MyListActivity extends ListActivity {
private Spinner mySpinner;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: other code as required...
mySpinner = (Spinner) findViewById(R.id.mySpinner);
mySpinner.setAdapter(new MySpinnerAdapter(this));
mySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> aParentView,
View aView, int aPosition, long anId) {
if (aPosition == 0) {
Log.d(getClass().getName(), "Ignoring selection of dummy list item...");
} else {
Log.d(getClass().getName(), "Handling selection of actual list item...");
// TODO: insert code to handle selection
resetSelection();
}
}
@Override
public void onNothingSelected(AdapterView<?> anAdapterView) {
// do nothing
}
});
}
/**
* Reset the filter spinner selection to 0 - which is ignored in
* onItemSelected() - so that a subsequent selection of another item is
* triggered, regardless of whether it's the same item that was selected
* previously.
*/
protected void resetSelection() {
Log.d(getClass().getName(), "Resetting selection to 0 (i.e. 'please select' item).");
mySpinner.setSelection(0);
}
}
et le code adaptateur spinner pourrait ressembler à quelque chose comme ceci (pourrait en fait être une classe intérieure dans l'activité de liste ci-dessus si vous préférez):
public class MySpinnerAdapter extends BaseAdapter implements SpinnerAdapter {
private List<MyListItem> items; // replace MyListItem with your model object type
private Context context;
public MySpinnerAdapter(Context aContext) {
context = aContext;
items = new ArrayList<MyListItem>();
items.add(null); // add first dummy item - selection of this will be ignored
// TODO: add other items;
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int aPosition) {
return items.get(aPosition);
}
@Override
public long getItemId(int aPosition) {
return aPosition;
}
@Override
public View getView(int aPosition, View aView, ViewGroup aParent) {
TextView text = new TextView(context);
if (aPosition == 0) {
text.setText("-- Please select --"); // text for first dummy item
} else {
text.setText(items.get(aPosition).toString());
// or use whatever model attribute you'd like displayed instead of toString()
}
return text;
}
}
je suppose (Je n'ai pas essayé cela) le même effet pourrait être obtenu en utilisant setSelected(false)
au lieu de setSelection(0)
, mais re-réglage à "s'il vous plaît sélectionner" convient à mes fins. Et, "regarde, Ma, pas de drapeau!"(Bien que je suppose qu'ignorer 0
sélections n'est pas si dissemblable.)
heureusement, cela peut aider quelqu'un d'autre là-bas avec un cas d'utilisation similaire. :- ) Pour d'autres cas D'utilisation, la réponse de Felix peut être plus appropriée (Merci Felix!).
Look. Je ne sais pas si cela vous aidera, mais puisque vous semblez fatigué de chercher une réponse sans grand succès, cette idée peut vous aider, qui sait...
la classe Spinner
est dérivée de AbsSpinner
. À l'intérieur de cela, il y a cette méthode:
void setSelectionInt(int position, boolean animate) {
if (position != mOldSelectedPosition) {
mBlockLayoutRequests = true;
int delta = position - mSelectedPosition;
setNextSelectedPositionInt(position);
layout(delta, animate);
mBlockLayoutRequests = false;
}
}
C'est une image tirée de 1.5 source . Peut-être que vous pourriez vérifier cette source, voir comment Spinner/AbsSpinner fonctionne, et peut-être étendre cette classe juste assez pour saisir la méthode appropriée et pas vérifier si position != mOldSelectedPosition
.
je veux dire... c'est un énorme "peut-être" avec beaucoup de "si" (gestion des versions android vient à l'esprit, etc.), mais puisque vous semblez frustré (et J'ai été là avec Android de nombreuses fois), peut-être que cela peut vous donner un peu de "lumière". Et je suppose qu'il n'y a pas d'autres réponses évidentes en regardant votre recherche précédente.
je vous souhaite bonne chance!
modifier le fil est utile si vous voulez avoir plusieurs sélections simultanément dans la même activité. Si vous avez seulement envie à l'utilisateur d'avoir une sélection hiérarchique, par exemple:
Que voulez-vous manger?
Fruits
- pommes
- bananes
- Oranges
Fast Food
- Burgers
- frites
- Hot dogs,
puis le ExpandableListView pourrait être mieux pour vous. Il permet à l'utilisateur de naviguer dans la hiérarchie des différents groupes et de choisir un élément enfant. Ce serait semblable à avoir plusieurs Toupies pour l'utilisateur de choisir - si vous n'avez pas le désir d'une sélection simultanée, ce qui est.
Voici une solution de rechange pour faire la distinction entre toute modification programmatique (intentionnelle ou non) et toute modification initiée par l'utilisateur:
créez votre auditeur pour le spinner à la fois comme OnTouchListener et OnItemSelectedListener
public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
boolean userSelect = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
userSelect = true;
return false;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (userSelect) {
// Your selection handling code here
userSelect = false;
}
}
}
ajouter l'auditeur au spinner s'enregistrant pour les deux types d'événements
SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
ce ne serait pas gérer le cas dans lequel la ré-sélection du même article par l'utilisateur ne déclenche pas la méthode onItemSelected (que je n'ai pas observée), mais je suppose que cela pourrait être géré en ajoutant du code à la méthode onTouch.
quoi qu'il en soit, les problèmes qu'Amos a soulignés me rendaient fou avant de penser à cette solution, donc j'ai pensé que je partagerais aussi largement que possible. Il y a beaucoup de fils qui discutent de cela, mais je n'ai vu qu'une autre solution jusqu'à présent qui est similaire à ceci: https://stackoverflow.com/a/25070696/4556980 .
j'ai travaillé à travers plusieurs des questions mentionnées dans ce fil avant de réaliser que le PopupMenu widget est ce que je voulais vraiment. Cela a été facile à mettre en œuvre sans les hacks et les workarounds nécessaires pour changer la fonctionnalité d'un Spinner. PopupMenu était relativement nouveau lorsque ce fil a été lancé en 2011, mais j'espère que cela aide quelqu'un à la recherche de fonctionnalités similaires maintenant.