Проблемы с созданием всплывающего окна в Android-активности
Я пытаюсь создать всплывающее окно, которое появляется только при первом запуске приложения. Я хочу, чтобы он отображал текст и имел кнопку, чтобы закрыть всплывающее окно. Однако у меня возникают проблемы с тем, чтобы PopupWindow работал даже. Я пробовал два разных способа сделать это:
Сначала у меня есть XML файл, который объявляет макет всплывающего окна popup.xml(текстовое представление внутри linearlayout), и я добавил это в 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 и говорит: "Невозможно добавить нулевой токен окна".
Что в мире я делаю неправильно? Я очень новичок, поэтому, пожалуйста, несите меня.
Ответы
Ответ 1
Чтобы избежать 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);
}
});
Ответ 2
Решение, предоставляемое Kordzik, не будет работать, если вы запускаете 2 действия последовательно:
startActivity(ActivityWithPopup.class);
startActivity(ActivityThatShouldBeAboveTheActivivtyWithPopup.class);
Если вы добавите всплывающее окно таким образом в таком случае, вы получите тот же самый сбой, потому что ActivityWithPopup в этом случае не будет прикреплен к окну.
Больше универсального содействия onAttachedToWindow и onDetachedFromWindow.
А также нет необходимости в 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();
}
Ответ 3
Принятый ответ не работал для меня. Я все еще получил BadTokenException. Поэтому я просто вызвал Runnable из обработчика с задержкой как таковой:
new Handler().postDelayed(new Runnable() {
public void run() {
showPopup();
}
}, 100);
Ответ 4
используйте класс Context, например. MainActivity.this вместо getApplicationContext()
Ответ 5
Есть два сценария, когда это исключение может произойти. Один упоминается кордзиком. Здесь упоминается другой сценарий: http://blackriver.to/2012/08/android-annoying-exception-unable-to-add-window-is-your-activity-running/
Убедитесь, что вы обрабатываете оба из них
Ответ 6
решение состоит в том, чтобы установить режим развертки в диалоговом режиме, как показано ниже:
android:spinnerMode="dialog"
или
Spinner(Context context, int mode)
tnxs RamallahDroid
См. это.
Ответ 7
В зависимости от варианта использования для типов всплывающих окон для отображения сообщения установка всплывающего типа в TYPE_TOAST с помощью setWindowLayoutType()
устраняет проблему, так как всплывающее окно этого типа не зависит от основного действия.
Изменить: один из побочных эффектов: никакого взаимодействия во всплывающем окне для API <= 18, поскольку сенсорные/фокусируемые события будут удалены системой. (http://www.jianshu.com/p/634cd056b90c)
В конечном итоге я использую TYPE_PHONE (так как приложение имеет разрешение SYSTEM_ALERT_WINDOW, иначе это тоже не сработает).
Ответ 8
Вы можете проверить rootview, если он имеет токен. Вы можете получить родительский макет, определенный из вашей деятельности xml, mRootView
if (mRootView != null && mRootView.getWindowToken() != null) {
popupWindow.showAtLocation();
}
Ответ 9
Убедитесь, что findViewById
возвращает что-то - вы можете называть его слишком рано, прежде чем компоновка будет построена
Также вы можете отправить вывод logcat для исключений, которые вы получаете
Ответ 10
Вы также можете попробовать эту проверку:
public void showPopupProgress (){
new Handler().post(new Runnable() {
@Override
public void run() {
if (getWindow().getDecorView().getWindowVisibility() == View.GONE) {
showPopupProgress();
return;
}
popup.showAtLocation(.....);
}
});
}
Ответ 11
Если вы показываете PopupWindow в другом PopupWindow, не используйте представление в первом POP, используйте родительский вид источника.
pop.showAtLocation(parentView, ... );
Ответ 12
У меня была такая же проблема (BadTokenException) с AlertDialog на dialog.show()
. Я делал AlertDialog, следуя некоторому примеру. В моем случае причиной этой проблемы была строка
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST)
Все стало работать после того, как я его убрал.
Ответ 13
Может быть, пришло время для более нового решения. Этот метод проверяет 5 раз каждые 50 мс, имеет ли маркер родительское представление для PopupWindow. Я использую его внутри своего настроенного PopupWindow.
private fun tryToShowTooltip(tooltipLayout: View) {
Flowable.fromCallable { parentView.windowToken != null }
.map { hasWindowToken ->
if (hasWindowToken) {
[email protected] 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()
Ответ 14
Вы можете указать y-offset для учетной записи строки состояния из метода pw.showAtLocation...