Como evitar que a atividade carregue duas vezes ao pressionar o botão


93

Estou tentando evitar que a atividade carregue duas vezes se pressiono o botão duas vezes imediatamente após o primeiro clique.

Tenho uma atividade que carrega com o clique de um botão, digamos

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

Agora, como a atividade a ser carregada possui chamadas de rede, leva um pouco de tempo para carregar (MVC). Eu mostro uma visualização de carregamento para isso, mas se eu pressionar o botão duas vezes antes disso, posso ver a atividade sendo carregada duas vezes.

Alguém sabe como evitar isso?


Você pode desabilitar o botão depois de abrir a atividade ... e quando a atividade terminar ,, reabilitá-lo ... você pode detectar o fim da segunda atividade chamando a função onActivityResult
Maneesh

Desative o botão quando for clicado pela primeira vez e reative-o mais tarde apenas quando desejar que o botão seja clicado novamente.
JimmyB de

desabilitar não funciona de uma maneira simples se a próxima instrução for para um longo processo ou início de atividade ... Para desabilitar o botão você deve criar um tópico separado ...
Awais Tariq


Respostas:


69

No ouvinte de evento do botão, desative o botão e mostre outra atividade.

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

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

Substitua onResume()para reativar o botão.

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

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

1
Esta é a abordagem correta. Ele até lidará com os Estados selecionados do botão para você (se você os fornecer) e todos os “goodies” do Material Design que você esperaria de um widget padrão simples. Não acredito que as pessoas usam temporizadores para isso. Aí você começa a ver bibliotecas estranhas para lidar com coisas como essas ...
Martin Marconcini

157

Adicione isso à sua Activitydefinição em AndroidManifest.xml...

android:launchMode = "singleTop"

Por exemplo:

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

ok, acho que você está demorando muito para processar depois de iniciar uma nova atividade. É por isso que a tela fica preta. Agora, se você quiser evitar essa tela preta, deve mostrar uma caixa de diálogo de progresso no início da atividade e fazer o longo processamento em um thread separado (ou seja, UI Thread ou Simply use async class). Assim que o processamento estiver concluído, oculte esse diálogo. É a melhor solução que eu
conheço

Tenho o diálogo para mostrar. Mas sim eu tenho um método apontando web no onCreate. Mas esta é a única solução? Porque, neste ponto, eu quero lidar sem mudar de segmento e tudo. Então você conhece outras maneiras possíveis. E estou tendo o botão no meu adaptador de lista e declarei o método para ele no xml, não programaticamente
tejas

2
o que mais é possível ?? De uma forma ou de outra, você deve implementar o threading para obter um aplicativo de aparência suave ... Experimente cara ..;) Basta colocar todo o código atual em um método e chamar esse método de um thread separado no mesmo lugar onde você escreveu antes ... dificilmente aumentará cinco a seis linhas de código ..
Awais Tariq

19
Isso evita que duas instâncias da atividade existam, mas não impede que o código seja executado duas vezes, incorretamente. A resposta aceita é melhor, apesar de ter menos votos positivos.
lilbyrdie

18
Isso está errado, faz com que a atividade nunca exista duas vezes, mesmo em tarefas diferentes. A maneira correta seria android:launchMode = "singleTop", que atinge o efeito sem quebrar a multitarefa do Android. A documentação afirma que a maioria dos aplicativos não deve usar a singleInstanceopção.
Nohus

37

Você pode usar os sinalizadores de intent assim.

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

Isso fará com que apenas uma atividade seja aberta no topo da pilha de histórico.


4
Esta resposta, combinada com a resposta mais votada, parece funcionar melhor. Use este sinalizador no manifesto da Atividade:, android:launchMode = "singleTop"desta forma é resolvido sem ter que adicionar o sinalizador a todos os Intent.
Nohus

1
isso não é útil quando você precisa de atividades aninhadas, porque você não pode ter duas atividades do mesmo tipo.
Behnam Heydari

4
Isso não está funcionando no caso de startActivityForResult
raj

27

Já que o SO não me permite comentar sobre outras respostas, tenho que poluir este tópico com uma nova resposta.

Respostas comuns para o problema "atividade abre duas vezes" e minhas experiências com essas soluções (Android 7.1.1):

  1. Botão Desativar que inicia a atividade: Funciona, mas parece um pouco desajeitado. Se você tiver várias maneiras de iniciar a atividade em seu aplicativo (por exemplo, um botão na barra de ação E clicando em um item em uma exibição de lista), você deve controlar o estado ativado / desativado de vários elementos da GUI. Além disso, não é muito conveniente desabilitar itens clicados em uma exibição de lista, por exemplo. Portanto, não é uma abordagem muito universal.
  2. launchMode = "singleInstance": Não funciona com startActivityForResult (), interrompe a navegação com startActivity (), não recomendado para aplicativos regulares pela documentação de manifesto do Android.
  3. launchMode = "singleTask": Não funciona com startActivityForResult (), não recomendado para aplicativos regulares pela documentação de manifesto do Android.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: Botão para retroceder.
  5. FLAG_ACTIVITY_SINGLE_TOP: Não funciona, a atividade ainda está aberta duas vezes.
  6. FLAG_ACTIVITY_CLEAR_TOP: Este é o único que trabalha para mim.

EDIT: Isso era para iniciar atividades com startActivity (). Ao usar startActivityForResult (), preciso definir FLAG_ACTIVITY_SINGLE_TOP e FLAG_ACTIVITY_CLEAR_TOP.


FLAG_ACTIVITY_CLEAR_TOP: Este é o único funcionando para mim no Android 7.1.1
Mingjiang Shi,

1
Estou usando "FLAG_ACTIVITY_REORDER_TO_FRONT" e ele está funcionando perfeitamente e o botão Voltar também funciona normalmente. O que exatamente você quis dizer com "Botão quebras de volta"? Você poderia esclarecer isso?
Mirmuhsin Sodiqov

Descobri que o sinalizador "REORDER" tinha um bug ... e não estava sendo reordenado no KitKat. No entanto, verifiquei no Lollipop and Pie, está funcionando bem.
Mirmuhsin Sodiqov

7

Só estava funcionando para mim quando startActivity(intent)

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

1
@raj, você tentou adicionar isso android:launchMode = "singleInstance"ao arquivo Manifest da sua tag de atividade?
Shylendra Madda

5

Use singleInstance para evitar que a atividade seja chamada duas vezes.

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

4

Digamos que @wannik esteja certo, mas se tivermos mais de 1 botão chamando o mesmo ouvinte de ação e eu clicar em dois botões quase ao mesmo tempo antes de iniciar a próxima atividade ...

Portanto, é bom se você tiver campo private boolean mIsClicked = false;e no ouvinte:

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

E onResume()precisamos retornar o estado:

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

    mIsClicked = false;
}

Qual é a diferença entre a minha resposta e a de @wannik?

Se você definir enabled como false no ouvinte de sua exibição de chamada, outro botão usando o mesmo ouvinte ainda estará ativado. Portanto, para ter certeza de que a ação do ouvinte não será chamada duas vezes, você precisa ter algo global que desative todas as chamadas do ouvinte (esqueça se é uma nova instância ou não)

Qual é a diferença entre minha resposta e outras?

Eles estão pensando da maneira certa, mas não estão pensando em um futuro retorno à mesma instância da atividade de chamada :)


servoper, obrigado por sua pesquisa. Essa questão já foi resolvida, porém sua resposta também parece promissora para a situação que você relatou. Deixe-me tentar chegar ao resultado :)
tejas

1
Eu tenho esse problema em um dos meus jogos. Eu tenho balões de "selecionar nível" que têm o mesmo ouvinte e as visualizações são diferentes apenas por tags. Portanto, se eu escolher dois balões rapidamente, ele iniciará duas atividades. Eu sei disso porque a nova atividade começa com o som .. e neste caso o som é reproduzido duas vezes ... mas você pode verificar clicando em voltar, o que o levará à atividade anterior
Sir NIkolay Cesar The First

1
Isso não é suficiente. Você também precisa usar um synchronized(mIsClicked) {...}para estar 100% seguro.
Monstieur de

@Monstieur você não precisa de um bloco sincronizado porque este é todo o Tópico Principal ...
Martin Marconcini

@MartinMarconcini Só porque acontece de ser seguro em uma atividade do Android não significa que seja um bom código. Se fosse uma classe independente, teria que ser documentada como não segura para thread.
Monstieur,

4

Para esta situação, irei para um dos dois abordados, singleTaskem manifest.xml OU um sinalizador no onResume()&onDestroy() métodos respectivamente.

Para a primeira solução: prefiro usar singleTaskpara a atividade no manifesto em vez de singleInstance, conforme o uso singleInstance, descobri que, em algumas ocasiões, a atividade cria uma nova instância separada para si mesma, o que resulta em ter uma janela de dois aplicativos separados nos aplicativos em execução no bcakground e além de alocações de memória extra que resultariam em uma experiência do usuário muito ruim quando o usuário abre a visualização de aplicativos para escolher algum aplicativo para retomar. Portanto, a melhor maneira é ter a atividade definida no manifest.xml da seguinte forma:

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

você pode verificar os modos de inicialização da atividade aqui .


Pelo segundo solução, você deve apenas definir uma variável estática ou uma variável de preferência, por exemplo:

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;
    }

}

e do outro lado quando você quiser iniciar esta atividade, basta verificar:

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

3

Acho que você está resolvendo o problema da maneira errada. Geralmente é uma má idéia para uma atividade a ser tomada de longa execução solicitações da web em qualquer um dos seus métodos de ciclo de vida de inicialização ( onCreate(), onResume(), etc). Na verdade, esses métodos devem ser usados ​​simplesmente para instanciar e inicializar objetos que sua atividade usará e, portanto, devem ser relativamente rápidos.

Se você precisar realizar uma solicitação da web, faça isso em um thread de segundo plano de sua atividade recém-iniciada (e mostre a caixa de diálogo de carregamento na nova atividade). Depois que o thread de solicitação em segundo plano é concluído, ele pode atualizar a atividade e ocultar a caixa de diálogo.

Isso significa que sua nova atividade deve ser iniciada imediatamente e evitar que o clique duplo seja possível.


3

Espero que isto ajude:

 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

Outra solução muito simples caso não queira usar onActivityResult()é desabilitar o botão por 2 segundos (ou o tempo que quiser), não é o ideal, mas pode resolver parcialmente o problema em alguns casos e o código é simples:

   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

// variável para rastrear o tempo do evento

private long mLastClickTime = 0;

2. No onClick, verifique se o horário atual e a diferença de horário do último clique são inferiores a um segundo, então não faça nada (retorne), vá para o evento de clique

 @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

Basta manter um sinalizador no método onClick do botão como:

public boolean oneTimeLoadActivity = false;

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

0

Se você estiver usando onActivityResult, poderá usar uma variável para salvar o estado.

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;
  }
}

Funciona com o botão Voltar pressionado também porque o código de solicitação é retornado no evento.


0

adicione o modo de inicialização como tarefa única no manifesto para evitar que a atividade abra duas vezes ao clicar

<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);
    }
});

Isso provavelmente não funcionaria, pois você teria que declará-lo como final.
Rei

-1

Use uma flagvariável para configurá-la to true, verifique se ela é verdadeira apenas returnexecute uma chamada de atividade.

Você também pode usar setClickable (false) executando a chamada de atividade

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

perform click; wait; flg = false;para quando voltarmos
Xeno Lupus

-1

Você pode simplesmente substituir startActivityForResult e usar a variável de instância:

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

Você pode tentar isso também

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);
            }

        });
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.