在使用setContentView加载布局的时候显示进度对话框

12

这是我的情境:我有一个登录屏幕,它会打开另一个活动。在该活动中,我只有以下代码:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_details);
}
布局有点沉重,因为它由一些片段组成,需要约1.5秒来加载。现在,我怎样才能在setContentView完成膨胀布局时显示一个ProgressDialog呢?我已经尝试使用AsyncTask通过将setContentView放在doInBackground中,但是这显然行不通,因为只能从UI线程更新UI。所以我需要在UI线程中调用setContentView,但我应该在哪里显示/取消ProgressDialog呢?
感谢您的帮助。
Fra.
编辑:我遵循了@JohnBoker之前的建议,这是我现在拥有的代码:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_empty_layout);
    new ContentSetterTask().execute("");
}

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

    public ProgressDialog prgDlg;

    @Override
    protected void onPreExecute() {
        android.os.Debug.waitForDebugger();
        prgDlg = ProgressDialog.show(MultiPaneActivity.this, "", "Loading...", true);

    }

@Override
protected Void doInBackground(String... args) {
    android.os.Debug.waitForDebugger();
    ViewGroup rootView = (ViewGroup)findViewById(R.id.emptyLayout);
    LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View inflated = inflater.inflate(R.layout.activity_details, rootView);
    return null;
}

@Override
protected void onPostExecute(Void arg) {
    android.os.Debug.waitForDebugger();
    if (prgDlg.isShowing())
        prgDlg.dismiss();
    }
  }
}

这一行

View inflated = inflater.inflate(R.layout.activity_details, rootView);

给了我这个错误:

06-27 16:47:24.010:   
ERROR/AndroidRuntime(8830): Caused by:android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.

1
在doInBackground方法中手动解压它怎么样? - user2443607
嗨,实际上在eeePad Transformer上需要大约1.5秒钟。 我该如何手动膨胀它?布局太长了,无法在此处发布,但它有大约10-12个线性布局和一个真实的片段(目前为止)。 - frapontillo
你在 activity_empty_layout 上有一个布局(相对布局、线性布局等),对吧?那么在填充 activity_details 时传递 null 给根视图,这样之后就可以使用父布局的 addView 方法了。 - user2443607
问题在于你正在后台线程中将视图膨胀到根视图中。你需要先膨胀,然后将其添加到根视图中,你可以这样做:View inflated = inflater.inflate(R.layout.activity_details, null); 然后将该视图返回到你的 onPostExecute 中,在那里你可以将它添加到你的根视图中。 - John Boker
在doInBackground中我有这样的代码:inflated = inflater.inflate(R.layout.activity_details, null);,而在onPostExecute中则是:rootView.addView(inflated);。然而,addView会影响到我的activity_details中的weightSum和layout weights。setContentView的效果与之相同,但没有这些问题。问题在于ProgressDialog被冻结了:它会在加载之前出现,然后自己冻结,一旦布局被填充就消失了。这是因为填充操作是在创建/销毁ProgressDialog的同一个线程上执行的。那么,我该如何使ProgressDialog保持响应呢? - frapontillo
显示剩余6条评论
3个回答

9
我决定在这里为未来的读者提供完整的答案。
经过数小时的研究,我意识到问题出在我试图做两件事情:
1. 膨胀布局,这是一种必须在UI线程上进行的操作设计。
2. 显示对话框(实际上是进度对话框,但这并不改变结果),只能从UI线程中完成,因为服务无法显示任何对话框。
因此,由于两个调用都是从UI线程(onCreate或AsyncTask没有区别,仍然是UI线程)中进行的,第一个调用会阻止第二个调用适当地显示出来。
底线是:目前Android无法解决这个问题。希望我们能获得一些更好的与UI交互的API,因为我们现有的API有点糟糕。
我将通过更改布局并使其更轻(如果可能的话!)来解决这个问题。
谢谢大家!

1
如果您使用HandlerThread,可以在单独的线程上手动填充视图。基本上,您需要在填充视图的线程上使用一个Looper。 - techi.services

8

在创建一个复杂的视图时,我遇到了同样的问题。我的解决办法是只在xml文件中放置一个linearlayout,并在其中调用setContentView,然后在异步任务中创建真正的视图,并将该视图动态添加到linearlayout中。

这种方法似乎有效,我能够在过程中显示进度对话框。


是的,那似乎是一个完美的选择! - user2443607
您可以看一下http://developer.android.com/resources/articles/painless-threading.html中的AsyncTask部分,将进度对话框放在onPreExecute中显示,在onPostExecute中关闭。在http://stackoverflow.com/questions/5292328/once-a-view-is-inflated-dynamically-as-a-list-with-a-button-attached-to-each-row中有一个动态填充视图的示例,每行都附带一个按钮。 - John Boker
我已经在pre和post中完成了显示和解除。现在我在doInBackground中添加了膨胀,这就是我得到的(显然)错误:06-27 16:12:35.630: ERROR/AndroidRuntime(8077): Caused by: android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. - frapontillo
我已经在第一篇帖子中添加了一些细节,包括完整的代码和上面的错误。看起来我做了与您建议的不同的事情。 :( - frapontillo

0

这不是一个完美的解决方案,但到目前为止是最接近我所需的:我们需要在设置进度对话框(或消息视图或其他内容)可见并调用setContentView()之间留出一些时间,以便实际显示对话框/消息。

为了实现这一点,我启动一个线程执行短暂的sleep(),然后在UI线程中调用setContentView()。

以下是一个带有进度对话框的示例:

private ProgressDialog  m_Hourglass = null ;
private Thread          m_LoadingThread = null ;

...

public void onCreate( Bundle savedInstanceState )
{
    super.onCreate( savedInstanceState );

    ...

    m_Hourglass = new ProgressDialog( this );
    m_Hourglass.setMessage( "Loading" );
    m_Hourglass.setIndeterminate( true );
    m_Hourglass.setCancelable( false );
}

public void SetMyView( View vvv )                   // Can be called from buttons.
{
    if( m_LoadingThread != null ) return;           // Avoid multiple press

    m_Hourglass.show();

    ////////////////////////////////////////////////////
    // Load in a thread, just to show the above message
    // before changing layout (because setContentView()
    // freezes the GUI.)
    m_LoadingThread = new Thread()
    {
            @Override
            public void run()
            {
                super.run();
                try                 {   sleep( 20 ); }
                catch (Exception e) {   System.out.println( e ); }
                finally             {   runOnUiThread( new Runnable()
                                        {
                                            public void run() 
                                            {
                                                SetMyViewThreaded();
                                            }
                                        });
                                    }
                m_LoadingThread = null ; // Now we can load again
            }
    };
    m_LoadingThread.start();
}

public void SetMyViewThreaded()
{
    setContentView( R.layout.some_gorgeous_layout );

    /////////////////////////////////////////////////////
    // Catch when to finally hide the progress dialog:
    ImageView iv            = (ImageView) findViewById( R.id.some_view_in_the_layout );
    ViewTreeObserver obs    = iv.getViewTreeObserver();      
    obs.addOnGlobalLayoutListener( new OnGlobalLayoutListener()
    {          
         @Override          
         public void onGlobalLayout()    // The view is now loaded
         {
             m_Hourglass.hide();
         }      
    });

    //////////////////////////////////////////////////////
    // Here we can work on the layout
    ...
}

我对此的主要问题是 sleep(20) ,这是一个近似值,在所有机器上可能不足够。它对我有效,但如果您没有看到进度对话框,则可能需要增加睡眠时间。


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