J'ai pensé à des solutions moins qu'élégantes pour résoudre ce problème, mais je sais qu'il me manque quelque chose.
Ma onItemSelected
se déclenche immédiatement sans aucune interaction avec l'utilisateur, ce qui constitue un comportement indésirable. Je souhaite que l'interface utilisateur attend jusqu'à ce que l'utilisateur sélectionne quelque chose avant de faire quoi que ce soit.
J'ai même essayé de configurer l'auditeur dans la onResume()
, espérant que cela aiderait, mais ce n'est pas le cas.
Comment puis-je empêcher le déclenchement avant que l'utilisateur ne puisse toucher au contrôle?
public class CMSHome extends Activity {
private Spinner spinner;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Heres my spinner ///////////////////////////////////////////
spinner = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.pm_list, Android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(Android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
};
public void onResume() {
super.onResume();
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}
public class MyOnItemSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent,
View view, int pos, long id) {
Intent i = new Intent(CMSHome.this, ListProjects.class);
i.putExtra("bEmpID", parent.getItemAtPosition(pos).toString());
startActivity(i);
Toast.makeText(parent.getContext(), "The pm is " +
parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
}
public void onNothingSelected(AdapterView parent) {
// Do nothing.
}
}
}
Je me serais attendu à ce que votre solution fonctionne. Je pensais que l'événement de sélection ne se déclencherait pas si vous définissez l'adaptateur avant de configurer l'auditeur.
Cela étant dit, un simple drapeau booléen vous permettrait de détecter le premier événement de sélection non autorisé et de l'ignorer.
L'utilisation de Runnables est complètement incorrecte.
Utilisez setSelection(position, false);
dans la sélection initiale avant setOnItemSelectedListener(listener)
De cette façon, vous définissez votre sélection sans animation, ce qui entraîne l'appel de l'écouteur sélectionné sur l'élément. Mais l'auditeur est nul et rien n'est exécuté. Ensuite, votre auditeur est assigné.
Alors suivez cette séquence exacte:
Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
En vous référant à la réponse de Dan Dyer, essayez d’enregistrer la OnSelectListener
dans une méthode post(Runnable)
:
spinner.post(new Runnable() {
public void run() {
spinner.setOnItemSelectedListener(listener);
}
});
En faisant cela pour moi, le comportement souhaité a finalement eu lieu.
Dans ce cas, cela signifie également que l'auditeur ne se déclenche que sur un élément modifié.
J'ai créé une petite méthode utilitaire pour changer la sélection Spinner
sans avertir l'utilisateur:
private void setSpinnerSelectionWithoutCallingListener(final Spinner spinner, final int selection) {
final OnItemSelectedListener l = spinner.getOnItemSelectedListener();
spinner.setOnItemSelectedListener(null);
spinner.post(new Runnable() {
@Override
public void run() {
spinner.setSelection(selection);
spinner.post(new Runnable() {
@Override
public void run() {
spinner.setOnItemSelectedListener(l);
}
});
}
});
}
Il désactive l'écouteur, modifie la sélection et réactive ensuite l'écouteur.
L'astuce est que les appels sont asynchrones pour le thread d'interface utilisateur, vous devez donc le faire dans des publications de gestionnaire consécutives.
Malheureusement, il semble que les deux solutions les plus couramment suggérées à ce problème, à savoir le comptage des occurrences de rappel et la publication d'un fichier Runnable pour le définir ultérieurement, peuvent échouer lorsque, par exemple, les options d'accessibilité sont activées. Voici une classe d'assistance qui traite de ces problèmes. De plus amples explications sont dans le bloc de commentaires.
import Android.view.View;
import Android.widget.AdapterView;
import Android.widget.AdapterView.OnItemSelectedListener;
import Android.widget.Spinner;
import Android.widget.SpinnerAdapter;
/**
* Spinner Helper class that works around some common issues
* with the stock Android Spinner
*
* A Spinner will normally call it's OnItemSelectedListener
* when you use setSelection(...) in your initialization code.
* This is usually unwanted behavior, and a common work-around
* is to use spinner.post(...) with a Runnable to assign the
* OnItemSelectedListener after layout.
*
* If you do not call setSelection(...) manually, the callback
* may be called with the first item in the adapter you have
* set. The common work-around for that is to count callbacks.
*
* While these workarounds usually *seem* to work, the callback
* may still be called repeatedly for other reasons while the
* selection hasn't actually changed. This will happen for
* example, if the user has accessibility options enabled -
* which is more common than you might think as several apps
* use this for different purposes, like detecting which
* notifications are active.
*
* Ideally, your OnItemSelectedListener callback should be
* coded defensively so that no problem would occur even
* if the callback was called repeatedly with the same values
* without any user interaction, so no workarounds are needed.
*
* This class does that for you. It keeps track of the values
* you have set with the setSelection(...) methods, and
* proxies the OnItemSelectedListener callback so your callback
* only gets called if the selected item's position differs
* from the one you have set by code, or the first item if you
* did not set it.
*
* This also means that if the user actually clicks the item
* that was previously selected by code (or the first item
* if you didn't set a selection by code), the callback will
* not fire.
*
* To implement, replace current occurrences of:
*
* Spinner spinner =
* (Spinner)findViewById(R.id.xxx);
*
* with:
*
* SpinnerHelper spinner =
* new SpinnerHelper(findViewById(R.id.xxx))
*
* SpinnerHelper proxies the (my) most used calls to Spinner
* but not all of them. Should a method not be available, use:
*
* spinner.getSpinner().someMethod(...)
*
* Or just add the proxy method yourself :)
*
* (Quickly) Tested on devices from 2.3.6 through 4.2.2
*
* @author Jorrit "Chainfire" Jongma
* @license WTFPL (do whatever you want with this, nobody cares)
*/
public class SpinnerHelper implements OnItemSelectedListener {
private final Spinner spinner;
private int lastPosition = -1;
private OnItemSelectedListener proxiedItemSelectedListener = null;
public SpinnerHelper(Object spinner) {
this.spinner = (spinner != null) ? (Spinner)spinner : null;
}
public Spinner getSpinner() {
return spinner;
}
public void setSelection(int position) {
lastPosition = Math.max(-1, position);
spinner.setSelection(position);
}
public void setSelection(int position, boolean animate) {
lastPosition = Math.max(-1, position);
spinner.setSelection(position, animate);
}
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
proxiedItemSelectedListener = listener;
spinner.setOnItemSelectedListener(listener == null ? null : this);
}
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position != lastPosition) {
lastPosition = position;
if (proxiedItemSelectedListener != null) {
proxiedItemSelectedListener.onItemSelected(
parent, view, position, id
);
}
}
}
public void onNothingSelected(AdapterView<?> parent) {
if (-1 != lastPosition) {
lastPosition = -1;
if (proxiedItemSelectedListener != null) {
proxiedItemSelectedListener.onNothingSelected(
parent
);
}
}
}
public void setAdapter(SpinnerAdapter adapter) {
if (adapter.getCount() > 0) {
lastPosition = 0;
}
spinner.setAdapter(adapter);
}
public SpinnerAdapter getAdapter() { return spinner.getAdapter(); }
public int getCount() { return spinner.getCount(); }
public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }
public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
public Object getSelectedItem() { return spinner.getSelectedItem(); }
public long getSelectedItemId() { return spinner.getSelectedItemId(); }
public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
public boolean isEnabled() { return spinner.isEnabled(); }
}
J'ai eu BEAUCOUP de problèmes avec le tir au fusil quand je ne voulais pas, et toutes les réponses ici ne sont pas fiables. Ils travaillent - mais seulement parfois. Vous rencontrerez éventuellement des scénarios où ils échoueront et introduiront des bogues dans votre code.
Ce qui a fonctionné pour moi a été de stocker le dernier index sélectionné dans une variable et de l’évaluer dans le programme d’écoute. S'il est identique au nouvel index sélectionné, ne faites rien et revenez, sinon continuez avec l'écouteur. Faire ceci:
//Declare a int member variable and initialize to 0 (at the top of your class)
private int mLastSpinnerPosition = 0;
//then evaluate it in your listener
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(mLastSpinnerPosition == i){
return; //do nothing
}
mLastSpinnerPosition = i;
//do the rest of your code now
}
Faites-moi confiance quand je dis cela, c'est de loin la solution la plus fiable. Un bidouillage, mais ça marche!
J'étais dans une situation similaire et j'ai une solution simple qui fonctionne pour moi.
Il semble que les méthodes setSelection(int position)
et setSelected(int position, boolean animate)
aient une implémentation interne différente.
Lorsque vous utilisez la deuxième méthode setSelected(int position, boolean animate)
avec le paramètre false animate, vous obtenez la sélection sans déclencher l'écouteur onItemSelected
.
Juste pour préciser comment utiliser onTouchListener afin de faire la distinction entre les appels automatiques à setOnItemSelectedListener (qui font partie de l’initialisation de l’activité, etc.) et les appels déclenchés par une interaction réelle de l’utilisateur. a constaté que cela fonctionnait bien avec le moins de lignes de code.
Il suffit de définir un champ booléen pour votre activité/fragment, par exemple:
private Boolean spinnerTouched = false;
Ensuite, juste avant de définir setOnItemSelectedListener de votre spinner, définissez un onTouchListener:
spinner.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("Real touch felt.");
spinnerTouched = true;
return false;
}
});
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
...
if (spinnerTouched){
//Do the stuff you only want triggered by real user interaction.
}
spinnerTouched = false;
spinner.setSelection(Adapter.NO_SELECTION, false);
Après avoir tiré mes cheveux pendant un long moment, j'ai créé ma propre classe de spinner. J'ai ajouté une méthode qui déconnecte et connecte l'auditeur de manière appropriée.
public class SaneSpinner extends Spinner {
public SaneSpinner(Context context) {
super(context);
}
public SaneSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SaneSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// set the ceaseFireOnItemClickEvent argument to true to avoid firing an event
public void setSelection(int position, boolean animate, boolean ceaseFireOnItemClickEvent) {
OnItemSelectedListener l = getOnItemSelectedListener();
if (ceaseFireOnItemClickEvent) {
setOnItemSelectedListener(null);
}
super.setSelection(position, animate);
if (ceaseFireOnItemClickEvent) {
setOnItemSelectedListener(l);
}
}
}
Utilisez-le dans votre XML comme ceci:
<my.package.name.SaneSpinner
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:id="@+id/mySaneSpinner"
Android:entries="@array/supportedCurrenciesFullName"
Android:layout_weight="2" />
Tout ce que vous avez à faire est de récupérer l'instance de SaneSpinner après l'inflation et de sélectionner la composition de l'appel comme ceci:
mMySaneSpinner.setSelection(1, true, true);
Avec cela, aucun événement n'est déclenché et l'interaction de l'utilisateur n'est pas interrompue. Cela a beaucoup réduit la complexité de mon code. Cela devrait être inclus dans le stock Android puisqu'il s'agit vraiment d'un PITA.
Aucun événement indésirable de la phase de mise en page si vous différez l'ajout de l'auditeur jusqu'à la fin de la mise en page:
spinner.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Ensure you call it only once works for JELLY_BEAN and later
spinner.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// add the listener
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// check if pos has changed
// then do your work
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
}
});
J'ai eu une réponse très simple, 100% sûr que cela fonctionne:
boolean Touched=false; // this a a global variable
public void changetouchvalue()
{
Touched=true;
}
// this code is written just before onItemSelectedListener
spinner.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("Real touch felt.");
changetouchvalue();
return false;
}
});
//inside your spinner.SetonItemSelectedListener , you have a function named OnItemSelected iside that function write the following code
if(Touched)
{
// the code u want to do in touch event
}
J'ai trouvé une solution beaucoup plus élégante à cela. Cela implique de compter combien de fois le ArrayAdapter (dans votre cas "adaptateur") a été appelé. Disons que vous avez 1 spinner et que vous appelez:
int iCountAdapterCalls = 0;
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.pm_list, Android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(Android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
Déclarez un compteur int après la méthode onCreate puis dans la méthode onItemSelected (). Mettez une condition "if" pour vérifier le nombre d'appels de atapter. Dans votre cas, vous l'avez appelé juste une fois, alors:
if(iCountAdapterCalls < 1)
{
iCountAdapterCalls++;
//This section executes in onCreate, during the initialization
}
else
{
//This section corresponds to user clicks, after the initialization
}
Cela se produira si vous effectuez une sélection dans le code en tant que;
mSpinner.setSelection(0);
Au lieu d'utiliser l'instruction ci-dessus
mSpinner.setSelection(0,false);//just simply do not animate it.
Edit: Cette méthode ne fonctionne pas pour Mi Android Version Mi UI.
Ma petite contribution est une variation de certaines des solutions ci-dessus qui m’a bien plu à quelques reprises.
Déclarez une variable entière comme valeur par défaut (ou la dernière valeur utilisée enregistrée dans les préférences) . Utilisez spinner.setSelection (myDefault) pour définir cette valeur avant que l'écouteur ne soit enregistré. est égal à la valeur que vous avez attribuée avant d'exécuter tout code supplémentaire.
Cela présente l'avantage supplémentaire de ne pas exécuter de code si l'utilisateur sélectionne à nouveau la même valeur.
Après avoir eu le même problème, je suis arrivé à cette solution en utilisant des balises . L'idée sous-jacente est simple: chaque fois que le compteur est modifié par programme, vérifiez que la balise reflète la position sélectionnée. Dans l'écouteur, vous vérifiez si la position sélectionnée est égale à la balise. Si tel est le cas, la sélection de disque a été modifiée par programme.
Ci-dessous ma nouvelle classe "spinner proxy":
package com.samplepackage;
import com.samplepackage.R;
import Android.widget.Spinner;
public class SpinnerFixed {
private Spinner mSpinner;
public SpinnerFixed(View spinner) {
mSpinner = (Spinner)spinner;
mSpinner.setTag(R.id.spinner_pos, -2);
}
public boolean isUiTriggered() {
int tag = ((Integer)mSpinner.getTag(R.id.spinner_pos)).intValue();
int pos = mSpinner.getSelectedItemPosition();
mSpinner.setTag(R.id.spinner_pos, pos);
return (tag != -2 && tag != pos);
}
public void setSelection(int position) {
mSpinner.setTag(R.id.spinner_pos, position);
mSpinner.setSelection(position);
}
public void setSelection(int position, boolean animate) {
mSpinner.setTag(R.id.spinner_pos, position);
mSpinner.setSelection(position, animate);
}
// If you need to proxy more methods, use "Generate Delegate Methods"
// from the context menu in Eclipse.
}
Vous aurez également besoin d'un fichier XML avec la balise setup dans votre répertoire Values
. J'ai nommé mon fichier spinner_tag.xml
, mais c'est à vous de décider . Cela ressemble à ceci:
<resources xmlns:Android="http://schemas.Android.com/apk/res/Android">
<item name="spinner_pos" type="id" />
</resources>
Maintenant remplacer
Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);
dans votre code avec
SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));
Et donnez à votre gestionnaire quelque chose comme ça:
myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (myspinner.isUiTriggered()) {
// Code you want to execute only on UI selects of the spinner
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
La fonction isUiTriggered()
retournera true si et seulement si l'utilisateur a changé le compteur. Notez que cette fonction a un effet secondaire: elle définira la balise. Un deuxième appel dans le même appel d'écoute renverra toujours false
.
Ce wrapper résoudra également le problème de l'appel de l'auditeur lors de la création de la présentation.
Amusez-vous, .__ Jens.
Puisque rien ne fonctionnait pour moi et que j’ai plus d’une visière à mon avis (et que mon humble avis sur moi qui tient une carte booléenne est une overkill), j’utilise la balise pour compter les clics:
spinner.setTag(0);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Integer selections = (Integer) parent.getTag();
if (selections > 0) {
// real selection
}
parent.setTag(++selections); // (or even just '1')
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
Beaucoup de réponses déjà, voici la mienne.
J'étends AppCompatSpinner
et ajoute une méthode pgmSetSelection(int pos)
qui permet de définir une sélection par programme sans déclencher de rappel de sélection. J'ai codé cela avec RxJava pour que les événements de sélection soient livrés via une variable Observable
.
package com.controlj.view;
import Android.content.Context;
import Android.util.AttributeSet;
import Android.view.View;
import Android.widget.AdapterView;
import io.reactivex.Observable;
/**
* Created by clyde on 22/11/17.
*/
public class FilteredSpinner extends Android.support.v7.widget.AppCompatSpinner {
private int lastSelection = INVALID_POSITION;
public void pgmSetSelection(int i) {
lastSelection = i;
setSelection(i);
}
/**
* Observe item selections within this spinner. Events will not be delivered if they were triggered
* by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
*
* @return an Observable delivering selection events
*/
public Observable<Integer> observeSelections() {
return Observable.create(emitter -> {
setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if(i != lastSelection) {
lastSelection = i;
emitter.onNext(i);
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
onItemSelected(adapterView, null, INVALID_POSITION, 0);
}
});
});
}
public FilteredSpinner(Context context) {
super(context);
}
public FilteredSpinner(Context context, int mode) {
super(context, mode);
}
public FilteredSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
super(context, attrs, defStyleAttr, mode);
}
}
Un exemple de son utilisation, appelé dans onCreateView()
dans une Fragment
par exemple:
mySpinner = view.findViewById(R.id.history);
mySpinner.observeSelections()
.subscribe(this::setSelection);
où setSelection()
est une méthode de la vue englobante qui ressemble à ceci et qui est appelée à la fois à partir d'événements de sélection d'utilisateurs via la variable Observable
et également ailleurs, de sorte que la logique de gestion des sélections est commune aux deux méthodes de sélection.
private void setSelection(int position) {
if(adapter.isEmpty())
position = INVALID_POSITION;
else if(position >= adapter.getCount())
position = adapter.getCount() - 1;
MyData result = null;
mySpinner.pgmSetSelection(position);
if(position != INVALID_POSITION) {
result = adapter.getItem(position);
}
display(result); // show the selected item somewhere
}
Je voudrais stocker l'index initial lors de la création de l'objet onClickListener.
int thisInitialIndex = 0;//change as needed
myspinner.setSelection(thisInitialIndex);
myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
int initIndex = thisInitialIndex;
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (id != initIndex) { //if selectedIndex is the same as initial value
// your real onselecteditemchange event
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
Je risque de répondre trop tard au message, mais j’ai réussi à le faire en utilisant la bibliothèque de liaisons de données Android Android Databinding . J'ai créé une liaison personnalisée pour m'assurer que le programme d'écoute n'est pas appelé jusqu'à ce que l'élément sélectionné soit modifié. Ainsi, même si l'utilisateur sélectionne toujours la même position, l'événement n'est pas déclenché.
Fichier XML de mise en page
<layout>
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_marginTop="@dimen/activity_vertical_margin"
xmlns:app="http://schemas.Android.com/apk/res-auto">
<Spinner
Android:id="@+id/spinner"
Android:layout_width="150dp"
Android:layout_height="wrap_content"
Android:spinnerMode="dropdown"
Android:layout_below="@id/member_img"
Android:layout_marginTop="@dimen/activity_vertical_margin"
Android:background="@drawable/member_btn"
Android:padding="@dimen/activity_horizontal_margin"
Android:layout_marginStart="@dimen/activity_horizontal_margin"
Android:textColor="@color/colorAccent"
app:position="@{0}"
/>
</RelativeLayout>
</layout>
app:position
est l'endroit où vous passez la position pour être sélectionné.
Liaison personnalisée
@BindingAdapter(value={ "position"}, requireAll=false)
public static void setSpinnerAdapter(Spinner spinner, int selected)
{
final int [] selectedposition= new int[1];
selectedposition[0]=selected;
// custom adapter or you can set default adapter
CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
spinner.setAdapter(customSpinnerAdapter);
spinner.setSelection(selected,false);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String item = parent.getItemAtPosition(position).toString();
if( position!=selectedposition[0]) {
selectedposition[0]=position;
// do your stuff here
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
Vous pouvez en savoir plus sur la liaison de données personnalisée ici Android Custom Setter
REMARQUE
N'oubliez pas d'activer la liaison de données dans votre fichier Gradle
Android {
....
dataBinding {
enabled = true
}
}
Incluez vos fichiers de mise en page dans les balises <layout>
Je dois utiliser mSpinner
dans ViewHolder pour que l'indicateur mOldPosition
soit défini dans la classe interne anonyme.
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
int mOldPosition = mSpinner.getSelectedItemPosition();
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
if (mOldPosition != position) {
mOldPosition = position;
//Do something
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
//Do something
}
});
J'essaierais d'appeler
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
après avoir appelé setAdapter (). Essayez également d'appeler avant l'adaptateur.
Vous avez toujours la solution pour les sous-classes, où vous pouvez envelopper un indicateur booléen dans votre méthode setAdapter surchargée pour ignorer l'événement.
if () {
spinner.setSelection(0);// No reaction to create spinner !!!
} else {
spinner.setSelection(intPosition);
}
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) {
// real selection
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
La solution avec un drapeau booléen ou un compteur ne m'a pas aidé, car lors d'un changement d'orientation, les appels de onItemSelected () "survolaient" le drapeau ou le compteur.
J'ai sous-classé Android.widget.Spinner
et fait de petits ajouts. Les parties pertinentes sont ci-dessous. Cette solution a fonctionné pour moi.
private void setHandleOnItemSelected()
{
final StackTraceElement [] elements = Thread.currentThread().getStackTrace();
for (int index = 1; index < elements.length; index++)
{
handleOnItemSelected = elements[index].toString().indexOf("PerformClick") != -1; //$NON-NLS-1$
if (handleOnItemSelected)
{
break;
}
}
}
@Override
public void setSelection(int position, boolean animate)
{
super.setSelection(position, animate);
setHandleOnItemSelected();
}
@Override
public void setSelection(int position)
{
super.setSelection(position);
setHandleOnItemSelected();
}
public boolean shouldHandleOnItemSelected()
{
return handleOnItemSelected;
}
Ce n'est pas une solution élégante non plus. En fait, c'est plutôt Rube-Goldberg, mais cela semble fonctionner. Je m'assure que le spinner a été utilisé au moins une fois en étendant l'adaptateur de matrice et en remplaçant son getDropDownView. Dans la nouvelle méthode getDropDownView, j'ai un indicateur booléen configuré pour indiquer que le menu déroulant a été utilisé au moins une fois. J'ignore les appels à l'écouteur jusqu'à ce que le drapeau soit défini.
MainActivity.onCreate ():
ActionBar ab = getActionBar();
ab.setDisplayShowTitleEnabled(false);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ab.setListNavigationCallbacks(null, null);
ArrayList<String> abList = new ArrayList<String>();
abList.add("line 1");
...
ArAd abAdapt = new ArAd (this
, Android.R.layout.simple_list_item_1
, Android.R.id.text1, abList);
ab.setListNavigationCallbacks(abAdapt, MainActivity.this);
adaptateur de tableau surchargé:
private static boolean viewed = false;
private class ArAd extends ArrayAdapter<String> {
private ArAd(Activity a
, int layoutId, int resId, ArrayList<String> list) {
super(a, layoutId, resId, list);
viewed = false;
}
@Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
viewed = true;
return super.getDropDownView(position, convertView, parent);
}
}
auditeur modifié:
@Override
public boolean onNavigationItemSelected(
int itemPosition, long itemId) {
if (viewed) {
...
}
return false;
}
si vous devez recréer une activité à la volée, par exemple: changer de thème, un simple drapeau/compteur
utiliser la fonction onUserInteraction () pour détecter l'activité de l'utilisateur,
référence: https://stackoverflow.com/a/25070696/4772917
C'est ma solution finale et facile à utiliser:
public class ManualSelectedSpinner extends Spinner {
//get a reference for the internal listener
private OnItemSelectedListener mListener;
public ManualSelectedSpinner(Context context) {
super(context);
}
public ManualSelectedSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ManualSelectedSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
mListener = listener;
super.setOnItemSelectedListener(listener);
}
public void setSelectionWithoutInformListener(int position){
super.setOnItemSelectedListener(null);
super.setSelection(position);
super.setOnItemSelectedListener(mListener);
}
public void setSelectionWithoutInformListener(int position, boolean animate){
super.setOnItemSelectedListener(null);
super.setSelection(position, animate);
super.setOnItemSelectedListener(mListener);
}
}
Utilisez setSelection(...)
par défaut pour le comportement par défaut ou setSelectionWithoutInformListener(...)
pour sélectionner un élément dans le compteur sans déclencher de rappel OnItemSelectedListener.
Ma solution utilise onTouchListener
mais ne limite pas son utilisation. Il crée un wrapper pour onTouchListener
si nécessaire où setup onItemSelectedListener
.
public class Spinner extends Android.widget.Spinner {
/* ...constructors... */
private OnTouchListener onTouchListener;
private OnItemSelectedListener onItemSelectedListener;
@Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
onItemSelectedListener = listener;
super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
}
@Override
public void setOnTouchListener(OnTouchListener listener) {
onTouchListener = listener;
super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
}
private OnTouchListener wrapTouchListener(final OnTouchListener onTouchListener, final OnItemSelectedListener onItemSelectedListener) {
return onItemSelectedListener != null ? new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Spinner.super.setOnItemSelectedListener(onItemSelectedListener);
return onTouchListener != null && onTouchListener.onTouch(view, motionEvent);
}
} : onTouchListener;
}
}
J'ai fait avec le moyen le plus simple:
private AdapterView.OnItemSelectedListener listener;
private Spinner spinner;
onCreate ();
spinner = (Spinner) findViewById(R.id.spinner);
listener = new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
Log.i("H - Spinner selected position", position);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
};
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
spinner.setOnItemSelectedListener(listener);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
Terminé