Comment empêcher l'activité de se charger deux fois en appuyant sur le bouton


93

J'essaie d'empêcher l'activité de se charger deux fois si j'appuie deux fois sur le bouton immédiatement après le premier clic.

J'ai une activité qui se charge en cliquant sur un bouton, par exemple

 myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
       //Load another activity
    }
});

Maintenant que l'activité à charger a des appels réseau, le chargement prend un peu de temps (MVC). Je montre une vue de chargement pour cela, mais si j'appuie deux fois sur le bouton avant cela, je peux voir l'activité chargée deux fois.

Est-ce que quelqu'un sait comment éviter cela?


Vous pouvez désactiver le bouton après avoir ouvert l'activité ... et lorsque l'activité est terminée, le réactiver ... vous pouvez détecter la fin de la deuxième activité en appelant la fonction onActivityResult
Maneesh

Désactivez le bouton lors du premier clic et réactivez-le ultérieurement uniquement lorsque vous souhaitez que le bouton soit à nouveau cliqué.
JimmyB

la désactivation ne fonctionne pas de manière simple si la déclaration suivante concerne un long processus ou un démarrage d'activité ... Pour désactiver le bouton, vous devez créer un fil de discussion séparé ...
Awais Tariq


Réponses:


69

Dans l'écouteur d'événements du bouton, désactivez le bouton et affichez une autre activité.

    Button b = (Button) view;
    b.setEnabled(false);

    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);

Remplacer onResume()pour réactiver le bouton.

@Override
    protected void onResume() {
        super.onResume();

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setEnabled(true);
    }

1
C'est la bonne approche. Il gérera même les états sélectionnés pour vous (si vous les fournissez) et tous les «goodies» de Material Design que vous attendez d'un simple widget standard. Je ne peux pas croire que les gens utilisent des minuteries pour cela. Ensuite, vous commencez à voir d'étranges bibliothèques pour gérer des choses comme celles-ci…
Martin Marconcini

157

Ajoutez ceci à votre Activitydéfinition dans AndroidManifest.xml...

android:launchMode = "singleTop"

Par exemple:

<activity
            android:name=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar"
            android:launchMode = "singleTop"/>

ok je suppose que vous faites un long traitement après avoir commencé une nouvelle activité. C'est pourquoi l'écran devient noir. Maintenant, si vous voulez éviter cet écran noir, vous devez afficher une boîte de dialogue de progression au début de l'activité et effectuer le long traitement dans un thread séparé (ie UI Thread ou Simply use async class). Une fois votre traitement terminé, masquez cette boîte de dialogue. C'est la meilleure solution à ma connaissance et je l'ai utilisée plusieurs fois ... :)
Awais Tariq

J'ai le dialogue à montrer. Mais oui, j'ai une méthode de pointage Web dans onCreate. Mais est-ce la seule solution? Parce qu'à ce stade, je veux gérer sans changer de fil et tout. Alors, connaissez-vous d'autres moyens possibles. Et j'ai le bouton dans mon adaptateur de liste et j'ai déclaré la méthode pour cela dans le xml, pas par programme
tejas

2
quoi d'autre est-il possible ??? D'une manière ou d'une autre, vous devez implémenter le threading pour obtenir une application lisse ... Essayez-le mec ..;) Il suffit de mettre tout le code actuel dans une méthode et d'appeler cette méthode à partir d'un thread séparé au même endroit où vous avez écrit plus tôt ... Cela augmentera à peine de cinq à six lignes de code ..
Awais Tariq

19
Cela empêche deux instances de l'activité d'exister, mais cela n'empêche pas le code de s'exécuter deux fois, de manière incorrecte. La réponse acceptée est meilleure, malgré moins de votes positifs.
lilbyrdie

18
C'est faux, cela fait que l'activité n'existe jamais deux fois, même dans des tâches différentes. La bonne façon serait android:launchMode = "singleTop", qui réalise l'effet sans briser le multitâche Android. La documentation indique que la plupart des applications ne doivent pas utiliser l' singleInstanceoption.
Nohus

37

Vous pouvez utiliser les indicateurs d'intention comme ceci.

Intent intent = new Intent(Class.class);    
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(intent);

Une seule activité sera ouverte en haut de la pile d'historique.


4
Cette réponse, combinée à la réponse la plus votée, semble fonctionner le mieux. Utilisez cet indicateur dans le manifeste de l'activité:, de android:launchMode = "singleTop"cette façon, il est résolu sans avoir à ajouter l'indicateur à chaque intention.
Nohus

1
cela n'est pas utile lorsque vous avez besoin d'activités imbriquées, car vous ne pouvez pas avoir deux activités de même type.
Behnam Heydari

4
Cela ne fonctionne pas dans le cas de startActivityForResult
raj

27

Puisque SO ne me permet pas de commenter d'autres réponses, je dois polluer ce fil avec une nouvelle réponse.

Réponses courantes au problème "L'activité s'ouvre deux fois" et mes expériences avec ces solutions (Android 7.1.1):

  1. Désactiver le bouton qui démarre l'activité: fonctionne mais semble un peu maladroit. Si vous avez plusieurs façons de démarrer l'activité dans votre application (par exemple un bouton dans la barre d'action ET en cliquant sur un élément dans une vue de liste), vous devez garder une trace de l'état activé / désactivé de plusieurs éléments de l'interface graphique. De plus, il n'est pas très pratique de désactiver les éléments cliqués dans une vue de liste, par exemple. Donc, pas une approche très universelle.
  2. launchMode = "singleInstance": ne fonctionne pas avec startActivityForResult (), interrompt la navigation avec startActivity (), non recommandé pour les applications régulières par la documentation du manifeste Android.
  3. launchMode = "singleTask": ne fonctionne pas avec startActivityForResult (), non recommandé pour les applications régulières par la documentation du manifeste Android.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: bouton de retour en arrière.
  5. FLAG_ACTIVITY_SINGLE_TOP: Ne fonctionne pas, l'activité est toujours ouverte deux fois.
  6. FLAG_ACTIVITY_CLEAR_TOP: C'est le seul qui fonctionne pour moi.

EDIT: C'était pour démarrer des activités avec startActivity (). Lors de l'utilisation de startActivityForResult (), je dois définir à la fois FLAG_ACTIVITY_SINGLE_TOP et FLAG_ACTIVITY_CLEAR_TOP.


FLAG_ACTIVITY_CLEAR_TOP: C'est le seul qui fonctionne pour moi sur Android 7.1.1
Mingjiang Shi

1
J'utilise "FLAG_ACTIVITY_REORDER_TO_FRONT" et cela fonctionne très bien et le bouton Retour agit également normalement. Qu'entendez-vous exactement par «bouton de retour en arrière»? Pourriez-vous clarifier cela?
Mirmuhsin Sodiqov le

J'ai trouvé que le drapeau "REORDER" avait un bug ... et il n'était pas réorganisé dans KitKat. Cependant, je l'ai vérifié dans Lollipop and Pie, cela fonctionne bien.
Mirmuhsin Sodiqov le

7

Cela ne fonctionnait que pour moi quand startActivity(intent)

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

1
@raj avez-vous essayé en ajoutant ceci android:launchMode = "singleInstance"dans le fichier Manifest de votre tag d'activité?
Shylendra Madda

5

Utilisez singleInstance pour éviter toute activité à invoquer deux fois.

<activity
            android:name=".MainActivity"
            android:label="@string/activity"
            android:launchMode = "singleInstance" />

4

Disons que @wannik a raison, mais si nous avons plus d'un bouton appelant le même auditeur d'action et que je clique sur deux boutons une fois presque en même temps avant de commencer l'activité suivante ...

Donc c'est bien si vous avez le champ private boolean mIsClicked = false;et dans l'auditeur:

if(!mIsClicked)
{
    mIsClicked = true;
    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);
}

Et onResume()nous devons retourner l'état:

@Override
protected void onResume() {
    super.onResume();

    mIsClicked = false;
}

Quelle est la différence entre ma réponse et celle de @ wannik?

Si vous définissez enabled sur false dans l'écouteur de son appel, voir l'autre bouton utilisant le même auditeur sera toujours activé. Donc, pour être sûr que l'action de l'auditeur n'est pas appelée deux fois, vous devez avoir quelque chose de global qui désactive tous les appels de l'auditeur (peu importe s'il s'agit d'une nouvelle instance ou non)

Quelle est la différence entre ma réponse et les autres?

Ils pensent de la bonne manière mais ils ne pensent pas à un retour futur à la même instance de l'activité d'appel :)


servoper, merci pour vos recherches. Cette question a déjà été résolue, mais votre réponse semble également prometteuse pour la situation que vous avez évoquée. Laissez-moi essayer et venez avec le résultat :)
tejas

1
J'ai ce problème dans l'un de mes jeux. J'ai des baloons "sélectionner le niveau" qui ont le même auditeur et les vues sont juste différentes par balises. Donc, si je choisis rapidement deux ballons, cela commence deux activités. Je sais que parce que la nouvelle activité commence le son .. et dans ce cas le son est joué deux fois ... mais vous pouvez le vérifier en cliquant en arrière, ce qui vous ramènera à l'activité précédente
Sir NIkolay Cesar Le premier

1
Cela ne suffit pas. Vous devez également utiliser un synchronized(mIsClicked) {...}pour être sûr à 100%.
Monstieur

@Monstieur vous n'avez pas besoin d'un bloc synchronisé car c'est tout le fil conducteur…
Martin Marconcini

@MartinMarconcini Ce n'est pas parce qu'il est en sécurité dans une activité Android que c'est un bon code. S'il s'agissait d'une classe autonome, elle devrait être documentée comme n'étant pas thread-safe.
Monstieur

4

Pour cette situation, je vais opter pour l'un des deux approchés, singleTaskdans manifest.xml OU un drapeau dans le onResume()&onDestroy() méthodes méthodes respectivement.

Pour la première solution: je préfère utiliser singleTaskpour l'activité dans le manifeste plutôt que singleInstance, selon l'utilisation, singleInstancej'ai compris que dans certaines occasions, l'activité créant une nouvelle instance distincte pour elle-même, ce qui entraîne une fenêtre d'applications distinctes dans les applications en cours d'exécution dans bcakground et en plus des allocations de mémoire supplémentaires qui entraîneraient une très mauvaise expérience utilisateur lorsque l'utilisateur ouvre la vue des applications pour choisir une application à reprendre. Donc, le meilleur moyen est d'avoir l'activité définie dans le manifest.xml comme suit:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"</activity>

vous pouvez vérifier les modes de lancement d'activité ici .


Pour la deuxième solution, il suffit de définir une variable statique ou une variable de préférence, par exemple:

public class MainActivity extends Activity{
    public static boolean isRunning = false;

    @Override
    public void onResume() {
        super.onResume();
        // now the activity is running
        isRunning = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // now the activity will be available again
        isRunning = false;
    }

}

et de l'autre côté lorsque vous souhaitez lancer cette activité, il suffit de cocher:

private void launchMainActivity(){
    if(MainActivity.isRunning)
        return;
    Intent intent = new Intent(ThisActivity.this, MainActivity.class);
    startActivity(intent);
}

3

Je pense que vous allez résoudre le problème de la mauvaise façon. En général , il est une mauvaise idée pour une activité à faire des demandes Web de longue date dans l' une de ses méthodes de cycle de vie de démarrage ( onCreate(), onResume(), etc.). En réalité, ces méthodes devraient simplement être utilisées pour instancier et initialiser les objets que votre activité utilisera et devraient donc être relativement rapides.

Si vous devez effectuer une requête Web, faites-le dans un fil d'arrière-plan de votre activité nouvellement lancée (et affichez la boîte de dialogue de chargement dans la nouvelle activité). Une fois le thread de demande d'arrière-plan terminé, il peut mettre à jour l'activité et masquer la boîte de dialogue.

Cela signifie alors que votre nouvelle activité doit être lancée immédiatement et empêcher le double clic d'être possible.


3

J'espère que cela t'aides:

 protected static final int DELAY_TIME = 100;

// to prevent double click issue, disable button after click and enable it after 100ms
protected Handler mClickHandler = new Handler() {

    public void handleMessage(Message msg) {

        findViewById(msg.what).setClickable(true);
        super.handleMessage(msg);
    }
};

@Override
public void onClick(View v) {
    int id = v.getId();
    v.setClickable(false);
    mClickHandler.sendEmptyMessageDelayed(id, DELAY_TIME);
    // startActivity()
}`

2

Une autre solution très très simple si vous ne voulez pas l'utiliser onActivityResult()est de désactiver le bouton pendant 2 secondes (ou le temps que vous voulez), ce n'est pas idéal, mais peut résoudre en partie le problème dans certains cas et le code est simple:

   final Button btn = ...
   btn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            //start activity here...
            btn.setEnabled(false);   //disable button

            //post a message to run in UI Thread after a delay in milliseconds
            btn.postDelayed(new Runnable() {
                public void run() {
                    btn.setEnabled(true);    //enable button again
                }
            },1000);    //1 second in this case...
        }
    });

2

// variable pour suivre l'heure de l'événement

private long mLastClickTime = 0;

2.Dans onClick, vérifiez que si l'heure actuelle et le décalage horaire du dernier clic sont inférieurs à i seconde, ne faites rien (retour) sinon allez pour l'événement de clic

 @Override
public void onClick(View v) {
    // Preventing multiple clicks, using threshold of 1 second
    if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
        return;
          }
    mLastClickTime = SystemClock.elapsedRealtime();
            // Handle button clicks
            if (v == R.id.imageView2) {
        // Do ur stuff.
         }
            else if (v == R.id.imageView2) {
        // Do ur stuff.
         }
      }
 }

1

Maintenez simplement un indicateur dans la méthode Button onClick comme:

public booléen oneTimeLoadActivity = false;

    myButton.setOnClickListener(new View.OnClickListener() {
          public void onClick(View view) {
               if(!oneTimeLoadActivity){
                    //start your new activity.
                   oneTimeLoadActivity = true;
                    }
        }
    });

0

Si vous utilisez onActivityResult, vous pouvez utiliser une variable pour enregistrer l'état.

private Boolean activityOpenInProgress = false;

myButton.setOnClickListener(new View.OnClickListener() {
  public void onClick(View view) {
    if( activityOpenInProgress )
      return;

    activityOpenInProgress = true;
   //Load another activity with startActivityForResult with required request code
  }
});

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if( requestCode == thatYouSentToOpenActivity ){
    activityOpenInProgress = false;
  }
}

Fonctionne sur le bouton de retour enfoncé aussi car le code de demande est renvoyé lors de l'événement.


0

ajouter le mode de lancement en tant que tâche unique dans le manifeste pour éviter que l'activité s'ouvre deux fois en cliquant

<activity
        android:name=".MainActivity"
        android:label="@string/activity"
        android:launchMode = "singleTask" />

-1
myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      myButton.setOnClickListener(null);
    }
});

Cela ne fonctionnerait probablement pas car vous devrez le déclarer comme définitif.
King

-1

Utilisez une flagvariable pour le définir to true, vérifiez si c'est vrai, mais returneffectuez un appel d'activité.

Vous pouvez également utiliser setClickable (false) one pour exécuter l'appel d'activité

flg=false
 public void onClick(View view) { 
       if(flg==true)
         return;
       else
       { flg=true;
        // perform click}
    } 

perform click; wait; flg = false;à notre retour
Xeno Lupus

-1

Vous pouvez simplement remplacer startActivityForResult et utiliser la variable d'instance:

boolean couldStartActivity = false;

@Override
protected void onResume() {
    super.onResume();

    couldStartActivity = true;
}

@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if (couldStartActivity) {
        couldStartActivity = false;
        intent.putExtra(RequestCodeKey, requestCode);
        super.startActivityForResult(intent, requestCode, options);
    }
}

-4

Vous pouvez essayer ceci aussi

Button game = (Button) findViewById(R.id.games);
        game.setOnClickListener(new View.OnClickListener() 
        {
            public void onClick(View view) 
            {
                Intent myIntent = new Intent(view.getContext(), Games.class);
                startActivityForResult(myIntent, 0);
            }

        });
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.