55

我正在尝试创建一个仅在应用程序第一次启动时出现的弹出窗口。我希望它显示一些文本并有一个按钮来关闭弹出窗口。但是,我无法让 PopupWindow 正常工作。我尝试了两种不同的方法:

首先,我有一个 XML 文件,它声明了名为 popup.xml 的弹出窗口的布局(一个线性布局内的文本视图),并且我在我的主 Activity 的 OnCreate() 中添加了它:

PopupWindow pw = new PopupWindow(findViewById(R.id.popup), 100, 100, true);
    pw.showAtLocation(findViewById(R.id.main), Gravity.CENTER, 0, 0);

其次,我对这段代码做了完全相同的事情:

final LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    PopupWindow pw = new PopupWindow(inflater.inflate(R.layout.popup, (ViewGroup) findViewById(R.layout.main) ), 100, 100, true);
    pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);

第一个抛出 NullPointerException,第二个抛出 BadTokenException 并说“无法添加窗口——令牌 null 无效”

我到底做错了什么?我非常新手,所以请多多包涵。

4

14 回答 14

187

为避免 BadTokenException,您需要推迟显示弹出窗口,直到调用所有生命周期方法(-> 显示活动窗口):

 findViewById(R.id.main_page_layout).post(new Runnable() {
   public void run() {
     pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
   }
});
于 2011-01-17T13:09:17.110 回答
34

如果您连续启动 2 个活动,Kordzik 提供的解决方案将不起作用:

startActivity(ActivityWithPopup.class);
startActivity(ActivityThatShouldBeAboveTheActivivtyWithPopup.class);

如果在这种情况下以这种方式添加弹出窗口,您将遇到同样的崩溃,因为在这种情况下 ActivityWithPopup 不会附加到 Window 上。

更通用的解决方案是onAttachedToWindowonDetachedFromWindow

而且也不需要 postDelayed(Runnable, 100)。因为这 100 毫不保证任何东西

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    Log.d(TAG, "onAttachedToWindow");

    showPopup();
}

@Override
public void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    Log.d(TAG, "onDetachedFromWindow");

    popup.dismiss();
}
于 2015-11-19T16:53:46.797 回答
18

接受的答案对我不起作用。我仍然收到 BadTokenException。所以我只是从一个处理程序中调用了 Runnable,但延迟如下:

new Handler().postDelayed(new Runnable() {
    public void run() {
        showPopup();
    }
}, 100);
于 2013-07-03T17:30:30.520 回答
10

使用类上下文例如。MainActivity.this 而不是 getApplicationContext()

于 2018-01-08T12:15:09.417 回答
4

有两种情况可能会发生此异常。kordzik提到了一个。这里提到了其他场景:http: //blackriver.to/2012/08/android-annoying-exception-unable-to-add-window-is-your-activity-running/

确保你同时处理它们

于 2013-06-26T07:08:06.133 回答
4

解决方案是将微调器模式设置为对话框,如下所示:

android:spinnerMode="dialog"

或者

Spinner(Context context, int mode)
tnxs RamallahDroid

见这个。

于 2013-07-21T07:41:17.807 回答
1

根据用例,对于显示消息的弹出窗口类型,将弹出窗口类型设置为 TYPE_TOAST 使用setWindowLayoutType()可以避免问题,因为这种类型的弹出窗口不依赖于底层活动。

编辑:副作用之一:API <= 18 的弹出窗口中没有交互,因为系统将删除可触摸/可聚焦事件。( http://www.jianshu.com/p/634cd056b90c )

我最终使用了 TYPE_PHONE(因为该应用程序恰好具有 SYSTEM_ALERT_WINDOW 权限,否则这也不起作用)。

于 2015-12-14T11:27:10.157 回答
1

您可以检查根视图是否具有令牌。您可以获取从您的活动 xml 定义的父布局,mRootView

if (mRootView != null && mRootView.getWindowToken() != null) {
    popupWindow.showAtLocation();
}
于 2016-08-17T19:04:29.137 回答
0

检查findViewById返回的东西——你可能在布局构建之前调用它太早了

此外,您可能希望为您遇到的异常发布 logcat 输出

于 2010-11-15T20:30:29.663 回答
0

您也可以尝试使用此检查:

  public void showPopupProgress (){
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            if (getWindow().getDecorView().getWindowVisibility() == View.GONE) {
                showPopupProgress();
                return;
            }
            popup.showAtLocation(.....);
        }
    });
}
于 2014-12-29T14:00:51.223 回答
0

如果在另一个 PopupWindow 中显示 PopupWindow,请不要使用第一个 POP 中的视图,使用原始父视图。

pop.showAtLocation(parentView, ... );
于 2016-09-01T08:22:00.680 回答
0

我在 AlertDialog 上遇到了同样的问题(BadTokenException)dialog.show()。我按照一些例子制作了一个 AlertDialog 。在我的情况下,这个问题的原因是一个字符串 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST)

在我删除它之后,一切都变得正常了。

于 2018-06-08T14:19:59.920 回答
0

也许是时候采用更新的解决方案了。如果 PopupWindow 的父视图有令牌,此方法每 50 毫秒检查 5 次。我在自定义的 PopupWindow 中使用它。

private fun tryToShowTooltip(tooltipLayout: View) {
    Flowable.fromCallable { parentView.windowToken != null }
            .map { hasWindowToken ->
                if (hasWindowToken) {
                    return@map hasWindowToken
                }
                throw RetryException()
            }
            .retryWhen { errors: Flowable<Throwable> ->
                errors.zipWith(
                        Flowable.range(1, RETRY_COUNT),
                        BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
                            if (retryCount >= RETRY_COUNT) {
                                throw error
                            } else {
                                retryCount
                            }
                        })
                        .flatMap { retryCount: Int ->
                            Flowable.timer(retryCount * MIN_TIME_OUT_MS, TimeUnit.MILLISECONDS)
                        }
            }
            .onErrorReturn {
                false
            }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({ hasWindowToken ->
                if (hasWindowToken && !isShowing) {
                    showAtLocation(tooltipLayout, Gravity.NO_GRAVITY, 100, 100)
                }
            }, { t: Throwable? ->
                //error logging
            })
}

companion object {

    private const val RETRY_COUNT = 5
    private const val MIN_TIME_OUT_MS = 50L
}

class RetryException : Throwable()
于 2019-08-28T13:25:36.577 回答
-1

您可以从pw.showAtLocation方法指定 y 偏移量以说明状态栏...

于 2011-02-28T16:16:21.360 回答