Как предотвратить загрузку активности дважды при нажатии на кнопку
Я пытаюсь предотвратить загрузку активности дважды, если дважды нажать кнопку сразу после первого щелчка.
У меня есть активность, которая загружается по нажатию кнопки, скажем
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
//Load another activity
}
});
Теперь, поскольку загружаемое действие имеет сетевые вызовы, загрузка занимает немного времени (MVC). Я показываю вид загрузки для этого, но если я дважды нажму кнопку до этого, я вижу, что действие загружается дважды.
Кто-нибудь знает, как это предотвратить?
23 ответа
В прослушивателе событий кнопки отключите кнопку и покажите другое действие.
Button b = (Button) view;
b.setEnabled(false);
Intent i = new Intent(this, AnotherActitivty.class);
startActivity(i);
Override onResume()
чтобы снова включить кнопку.
@Override
protected void onResume() {
super.onResume();
Button button1 = (Button) findViewById(R.id.button1);
button1.setEnabled(true);
}
Добавьте это к вашему Activity
определение в Androidmanifest.xml
...
android:launchMode = "singleInstance"
Вы можете использовать флаги намерений, как это.
Intent intent = new Intent(Class.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(intent);
В верхней части стека истории будет открыто только одно действие.
Так как SO не позволяет мне комментировать другие ответы, я должен загрязнить эту ветку новым ответом.
Общие ответы на проблему "активность открывается дважды" и мой опыт использования этих решений (Android 7.1.1):
- Кнопка "Отключить", которая запускает действие: работает, но чувствует себя немного неуклюже. Если у вас есть несколько способов запустить действие в вашем приложении (например, кнопка на панели действий И, нажав на элемент в виде списка), вы должны отслеживать состояние включения / выключения нескольких элементов графического интерфейса. Плюс, например, не очень удобно отключать нажатые элементы в виде списка. Итак, не очень универсальный подход.
- launchMode = "singleInstance": не работает с startActivityForResult(), прерывает навигацию с помощью startActivity(), не рекомендуется для обычных приложений в документации манифеста Android.
- launchMode = "singleTask": не работает с startActivityForResult(), не рекомендуется для обычных приложений в документации манифеста Android.
- FLAG_ACTIVITY_REORDER_TO_FRONT: кнопка возврата назад.
- FLAG_ACTIVITY_SINGLE_TOP: не работает, действие все еще открывается дважды.
- FLAG_ACTIVITY_CLEAR_TOP: это единственный, который работает на меня.
РЕДАКТИРОВАТЬ: Это было для начала деятельности с startActivity(). При использовании startActivityForResult () мне нужно установить FLAG_ACTIVITY_SINGLE_TOP и FLAG_ACTIVITY_CLEAR_TOP.
Это работало только для меня, когда startActivity(intent)
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Используйте singleInstance, чтобы избежать активности, чтобы вызвать дважды.
<activity
android:name=".MainActivity"
android:label="@string/activity"
android:launchMode = "singleInstance" />
Допустим, @wannik прав, но если у нас более одной кнопки, вызывающей один и тот же слушатель действия, и я нажимаю две кнопки один раз почти одновременно, прежде чем приступить к следующему действию...
Так что хорошо, если у вас есть поле private boolean mIsClicked = false;
и в слушателе:
if(!mIsClicked)
{
mIsClicked = true;
Intent i = new Intent(this, AnotherActitivty.class);
startActivity(i);
}
А также onResume()
нам нужно вернуть состояние:
@Override
protected void onResume() {
super.onResume();
mIsClicked = false;
}
В чем разница между ответом my и @ wannik?
Если вы установили значение "false" в прослушивателе этого вызова, другая кнопка, использующая того же прослушивателя, все равно будет включена. Таким образом, чтобы быть уверенным, что действие слушателя не вызывается дважды, вам нужно иметь что-то глобальное, что отключает все вызовы слушателя (не важно, новый это экземпляр или нет)
В чем разница между моим ответом и другими?
Они думают правильно, но они не думают о будущем возвращении к тому же случаю вызывающей деятельности:)
Для этой ситуации я пойду на один из двух подходов, singleTask
в manifest.xml ИЛИ флаг в деятельности onResume()
& onDestroy()
методы соответственно.
Для первого решения: я предпочитаю использовать singleTask
за активность в манифесте, а не singleInstance
согласно использованию singleInstance
Я выяснил, что в некоторых случаях действие создает новый отдельный экземпляр для себя, в результате чего в запущенных приложениях в bcakground появляется окно с двумя отдельными приложениями, и помимо дополнительного выделения памяти, что может привести к очень плохому пользовательскому интерфейсу, когда пользователь открывает приложения посмотреть, чтобы выбрать приложение, чтобы возобновить. Таким образом, лучший способ - определить действие в файле manifest.xml следующим образом:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"</activity>
Вы можете проверить режимы запуска активности здесь.
Для второго решения вам нужно просто определить статическую переменную или переменную предпочтения, например:
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;
}
}
а с другой стороны, когда вы хотите запустить это действие, просто проверьте:
private void launchMainActivity(){
if(MainActivity.isRunning)
return;
Intent intent = new Intent(ThisActivity.this, MainActivity.class);
startActivity(intent);
}
// переменная для отслеживания времени события
private long mLastClickTime = 0;
2. В onClick проверьте, что если текущее время и разница во времени последнего клика меньше, чем я секунда, то ничего не предпринимайте (возврат), иначе перейдите к событию клика
@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.
}
}
}
Я думаю, что вы собираетесь решить проблему неправильно. Как правило, плохой идеей является выполнение длительных веб-запросов в любом из методов запуска жизненного цикла (onCreate()
, onResume()
, так далее). Действительно, эти методы должны просто использоваться для создания и инициализации объектов, которые будет использовать ваша деятельность, и поэтому должны быть относительно быстрыми.
Если вам нужно выполнить веб-запрос, сделайте это в фоновом потоке из недавно запущенного действия (и покажите диалоговое окно загрузки в новом действии). После завершения потока фонового запроса он может обновить действие и скрыть диалоговое окно.
Это означает, что ваша новая активность должна быть запущена немедленно и предотвратить двойной щелчок.
Надеюсь это поможет:
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()
}`
Добавить режим запуска как отдельную задачу в манифест, чтобы активность не открывалась дважды при нажатии
<activity
android:name=".MainActivity"
android:label="@string/activity"
android:launchMode = "singleTask" />
Другое очень очень простое решение, если вы не хотите использовать onActivityResult()
Это отключить кнопку на 2 секунды (или нужное время), не идеально, но может частично решить проблему в некоторых случаях, и код прост:
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...
}
});
Просто сохраните один флаг в методе onClick кнопки как:
public boolean oneTimeLoadActivity = false;
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if(!oneTimeLoadActivity){
//start your new activity.
oneTimeLoadActivity = true;
}
}
});
Если вы используете onActivityResult, вы можете использовать переменную для сохранения состояния.
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;
}
}
Работает и при нажатой кнопке назад, потому что код запроса возвращается по событию.
Добавление:
android:launchMode="singleTop"
Внутри тега активности в AndroidManifest.xml проблема решена.
В kotlin вы можете использовать эту функцию расширения, чтобы отключить щелчок представления, это включит представление по истечении времени
fun View.disableClick(time: Long = 1000) {
isEnabled = false
postDelayed({
isEnabled = true
}, time)
}
вы можете вызвать функцию, как показано ниже
binding.btGetStarted.setOnClickListener {
//disabling button click
binding.btGetStarted.disableClick()
//activity redirection
ActivityUtils.startActivity(this,RegisterActivity::class.java, isFinish = false)
}
Мы также можем использовать функцию устранения дребезга кнопок.
public abstract class DebounceOnClickListener implements View.OnClickListener {
private final long minimumInterval;
private final Map<View, Long> lastClickMap;
public abstract void debounceOnClick(View view);
public DebounceOnClickListener(long minIntervalMilliSec) {
this.minimumInterval = minIntervalMilliSec;
this.lastClickMap = new WeakHashMap<View, Long>();
}
@Override
public void onClick(View clickedView) {
Long previousClickTimestamp = lastClickMap.get(clickedView);
long currentTimestamp = SystemClock.uptimeMillis();
lastClickMap.put(clickedView, currentTimestamp);
if (previousClickTimestamp == null ||
(currentTimestamp - previousClickTimestamp.longValue() > minimumInterval)) {
debounceOnClick(clickedView);
}
}
}
Более общее решение, которое будет применено только один раз для всех приложений, состоит в добавлении следующих двух классов, как показано ниже
public class SingleClickButton extends AppCompatButton {
public SingleClickButton(Context context) {
super(context);
}
public SingleClickButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SingleClickButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
if(l!=null){
super.setOnClickListener(new DebouncedClickListener() {
@Override
public void onClicked(View v) {
l.onClick(v);
}
});
}else{
super.setOnClickListener(l);
}
}
}
public abstract class DebouncedClickListener implements View.OnClickListener {
private static final long MAX_TIME_INTERVAL = 750L;
private long lastClickedTime;
public abstract void onClicked(View v);
@Override public void onClick(View v) {
Logger.d("Click Test trying to click");
long now = SystemClock.elapsedRealtime();
long diff = now - lastClickedTime;
if (diff > MAX_TIME_INTERVAL) {
Logger.d("Click Test allowed to click");
onClicked(v);
}else{
Logger.d("Click Test not allowed to click time ");
}
lastClickedTime = now;
}
}
поэтому всякий раз, когда вы хотите кнопку одним щелчком мыши, просто добавьте пользовательский вид в XML, как показано ниже
<yourpackagge.SingleClickButton .... />
Вы можете попробовать это также
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);
}
});
Вы можете просто переопределить startActivityForResult и использовать переменную экземпляра:
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);
}
}
Использовать flag
переменная установить его to true
Проверьте, правда ли это просто return
еще выполнить Activity Call.
Вы также можете использовать setClickable(false) для выполнения Activity Call
flg=false
public void onClick(View view) {
if(flg==true)
return;
else
{ flg=true;
// perform click}
}
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
myButton.setOnClickListener(null);
}
});