如何在异步任务运行时禁用按钮?(Android)

5
我正在尝试在下载任务执行时禁用一个按钮。我已经尝试使用setEnabled、setVisibility和setClickable方法,以及它们的各种组合。所有这些方法都可以在任务执行时禁用按钮的点击事件,但是事件仍然会以某种方式被注册,并且当我重新激活按钮时,如果我在按钮被禁用时点击它,处理程序将被调用...即使它是不可见或"消失"的!(我不确定是否称之为处理程序,我想引用onClick方法)。
我还插入了一个计数器和一个Log来验证上面所说的情况。下面的代码段if(counter>1) return;用于停止崩溃,但我想删除它,因为我想重新启用按钮,而不是永久禁用它。 onClick:
public void downloadOnClick(View v) {
    counter++;
    Log.d(this.getLocalClassName(), "Button was clicked " + counter + " times.");
    if(counter>1) return;

    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        //mButton.setVisibility(View.GONE);
        mButton.setEnabled(false);
        //mButton.setClickable(false);
        mTextView.setText("Getting html file...");
        // if we use simple http, we will need to handle redirect status code
        new DownloadWebpageTask().execute("https://www.google.com/");
    } else {
        mTextView.setText("No network connection available.");
    }
}

异步任务:

private class DownloadWebpageTask extends AsyncTask<String, Void, String> {

    private HttpURLConnection mConnection;

    @Override
    protected String doInBackground(String... urls) {
        try {
            URL url = new URL(urls[0]);
            mConnection = (HttpURLConnection) url.openConnection();
            mConnection.setReadTimeout(10000 /* milliseconds */);
            mConnection.setConnectTimeout(15000 /* milliseconds */);
            mConnection.setRequestMethod("GET");
            mConnection.setDoInput(true);

            mConnection.connect();
            int statusCode = mConnection.getResponseCode();
            if (statusCode != HttpURLConnection.HTTP_OK) {
                return "Error: Failed getting update notes";
            }

            return readTextFromServer(mConnection);

        } catch (IOException e) {
            return "Error: " + e.getMessage();
        }
    }

    private String readTextFromServer(HttpURLConnection connection) throws IOException {
        InputStreamReader stream = null;
        try {
            stream = new InputStreamReader(connection.getInputStream());
            BufferedReader br = new BufferedReader(stream);
            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            while (line != null) {
                sb.append(line + "\n");
                line = br.readLine();
            }
            return sb.toString();
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    @Override
    protected void onPostExecute(String result) {
        mTextView.setText(result);
        // Can not reactivate button / cancel (pending?) events....
        //mButton.setVisibility(View.VISIBLE);
        mButton.setEnabled(true);
        //mButton.setClickable(true);
    }
}

完整的项目(非常简单,只是一个培训示例)可在我刚创建的存储库中测试。
总之,从我所读到的来看,确实存在关于禁用按钮的问题。通常情况下,这是通过使用标志来调用onClick方法仅当标志为true时才解决的。虽然,这并不能解决重新启用按钮的问题。我也尝试过mButton.cancelPendingInputEvents();但它不起作用(而且我不知道为什么。点击事件还没有注册吗?或者它们没有挂起?
有没有简单的解决方案?任何想法?我错过了一些基本细节吗?如果不是,我考虑尝试通过编程方式创建一个新的按钮来解决这个问题。如果我不保留旧按钮的引用,它们会通过垃圾回收被删除吗?
提前致谢!
[编辑]澄清:
由于标题可能在这一点上具有误导性,我想澄清一下,即我能够禁用和重新启用按钮,所有功能都很好,除了当按钮被禁用时。请注意,我添加了if(counter>1) return;这行只是为了测试,但它停止了按钮按我所需的方式工作(这就是为什么我不使用标志。我不希望解决问题时出现这行!)日志足以告诉我,当我单击它被禁用的按钮时,方法正在被调用!

3
我不知道你看到了什么,但我6年的Android工作经验告诉我setEnabled的运作绝对不是这样——如果按钮被禁用,它不会排队事件。这让我认为你的代码有一些奇怪的地方或者你很迷惑。 - Gabe Sechan
1
我正在运行你的代码,看到完全相同的行为。关于这个问题在SO上有很多帖子,所以似乎这是一个已知的问题。 - Daniel Nugent
2
感谢您的回复@DanielNugent。是的,我认为我在我的问题中已经说明了这一点。但我没有找到任何重新启用按钮的方法。我发现的所有问题都涉及到禁用按钮,而这正是我已经实现的。 - João Miranda
2个回答

2
我发现在你的示例中,AsyncTask 完成得非常快,导致 Button 由于被禁用而无法点击的时间非常短暂。所以,这基本上是一个时间问题。
我发现通过延迟 4 秒重新启用 Button,可以按预期工作。
请注意,随着这个改变,视觉上,ButtonTextView 填充后的一瞬间重新启用。
以下是在 onPostExecute() 中的代码更改:
        @Override
        protected void onPostExecute(String result) {
            mTextView.setText(result);

            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {

                @Override
                public void run() {
                    //re-enable the button
                    mButton.setEnabled(true);

                }
            }, 4000);
        }

请注意,您可以删除计数器逻辑,现在应该按预期工作:
public void downloadOnClick(View v) {

    Log.d(this.getLocalClassName(), "Button was clicked");

    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {

        mButton.setEnabled(false);

        mTextView.setText("Getting html file...");
        // if we use simple http, we will need to handle redirect status code
        new DownloadWebpageTask().execute("https://www.google.com/");
    } else {
        mTextView.setText("No network connection available.");
    }
}

1
所以如果我理解正确的话,问题在于UI中的更改是以并行方式进行的,因此按钮在TextView填充之前就已启用(即使它似乎被禁用)。我假设这是因为即使我改变了图形更改的顺序,当文本完全加载时,按钮似乎只能启用。添加断点并进行调试,我可以看到在下载之前,单击事件没有被监听,但在textView被填充时会被监听。您的解决方案解决了问题,我将尝试从中制作一个更通用的解决方案。 - João Miranda
1
@JoãoMiranda 是的,每个View重新绘制都需要时间,因此在UI中看到Button重新启用之前,它已经被重新启用并接受点击事件了。 - Daniel Nugent
1
感谢您的时间!如果没有您的帮助,我不认为我能理解正在发生的事情。 - João Miranda

0

错误在这里:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="DOWNLOAD TEXT"
    android:id="@+id/button"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:onClick="downloadOnClick" />

你没有理解OnClickListener的概念!首先,你必须按照以下方式修改上面的xml,删除onClick属性:
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="DOWNLOAD TEXT"
    android:id="@+id/button"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true" />

然后您需要修改Activity的onCreate方法,以便在按钮上设置OnClickListener:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTextView = (TextView) findViewById(R.id.text);
    mButton = (Button) findViewById(R.id.button);
    mButton.setOnClickListener(new View.OnClickListener() {
         public void onClick(View v) {
            counter++;
            Log.d(this.getLocalClassName(), "Button was clicked " + counter + " times.");
            if(counter>1) return;

            ConnectivityManager connMgr = (ConnectivityManager)
                    getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
            if (networkInfo != null && networkInfo.isConnected()) {
                mButton.setEnabled(false);
                mTextView.setText("Getting html file...");
                // if we use simple http, we will need to handle redirect status code
                new DownloadWebpageTask().execute("https://www.google.com/");
            } else {
                mTextView.setText("No network connection available.");
            }
         }
     });
}

这是处理点击的正确方法。

查看更多:

此外:

从我个人角度来看,最好的方法是在您的Activity上实现OnClickListener():

public class MyActivity extends Activity implements View.OnClickListener {
}

通过这种方式,您可以为每个按钮编写代码,以设置OnClickListener:

buttonX.setOnClickListener(this);
buttonY.setOnClickListener(this);
buttonZ.setOnClickListener(this);

在你的Activity onClick() 方法中,你必须重写OnClickListener方法,所以:
@Override
public void onClick(View v) {
    if(v.getId() == R.id.ButtonX)){
        //do here what u wanna do.
    } else if(v.getId() == R.id.ButtonY){
        //do here what u wanna do.
    } else if(v.getId() == R.id.ButtonZ){
        //do here what u wanna do.
    }
}

在onClick中,您可以使用view.getId()获取资源ID,然后在switch/case块中使用它来识别每个按钮并执行相应的操作。


1
谢谢你的帮助! 我认为在这种情况下(我不想动态更改我正在调用的内容),使用xml指向一个方法是一样的。无论如何,我尝试了你的解决方案,但它仍然在我点击禁用按钮时调用该方法。请注意,该按钮仍处于“禁用”状态(除了记录日志之外不执行任何操作),因为我在方法开头添加了一个条件返回!当你点击禁用按钮时,你没有收到日志信息吗?也许在我的设备上测试时发生了一些奇怪的事情... - João Miranda
1
在执行AsyncTask期间禁用按钮时,我从未遇到过这个问题。该按钮在所有时间内都被禁用,并且如果我单击它,不会触发任何其他事件!我不知道您是否还缺少什么!我希望它能正常工作。 - antoniodvr
1
它现在不起作用,但我会在模拟器中尝试而不是在手机上。如果还是不行,明天我会问更多的人并在这里发布错误原因。再次感谢您的时间! - João Miranda
1
@VoodooCoder 在 XML 中的 onClick 会在编译时动态地为您创建 onClickListener,并通过反射调用活动中的命名函数。除了在性能上稍微牺牲一点以换取清晰度之外,这两种方法应该没有区别。 - Gabe Sechan
1
你能否用最新的更改更新代码库? - antoniodvr
1
完成。虽然它应该像以前一样运行。 - João Miranda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接