LoginSignup
0
0

More than 5 years have passed since last update.

PopupWindow $BadTokenException: Unable to add window — token null is not valid について

Posted at

PopUpWindowはちょっとのダイアログや吹き出しなどを実装したいときに作りやすい。

KotlinでPopUpWindow書いている記事があまりないなと思い、勉強も兼ねて実装していたのだが、
ある画面が出て条件を満たす場合に出したい場合、そのままViewの上に描画しようとするとタイトル通りのエラー

原因としてはポップアップさせるタイミングが早すぎるので少しずらしてあげないといけない
そのため、
[1] ストーリーは違うが、ボタンが押された後にPopUpするとか、
[2] RxやAsync, Handler等で非同期処理
する必要がある

[1] については単純にButtonが押された場合にPopUpWindowを呼び出してあげればいい
[2] についてはこのように時間をずらして表示

private fun showPopUpUsingRx(popUp: PopUp) {
        Completable.complete()
                .delay(5, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete { popUp.show(contact_button) }
                .subscribe()
    }

参考コードはこのような感じ

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        showPopUpUsingRx(PopUp(this))

        popup_container.setOnClickListener {
            showPopUpUsingButton(PopUp(this))
        }
    }

    private fun showPopUpUsingButton(popUp: PopUp) {
        if (popUp.isShowing) {
            popUp.dismiss()
            popUp.show(contact_button)
        } else {
            popUp.show(contact_button)
        }
    }

    private fun showPopUpUsingRx(popUp: PopUp) {
        Completable.complete()
                .delay(5, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete { popUp.show(contact_button) }
                .subscribe()
    }
}
PopUp.kt
@SuppressLint("InflateParams")
class PopUp(private val context: Context) : PopupWindow() {

    private val layoutInflater = LayoutInflater.from(context)

    init {
        contentView = layoutInflater.inflate(R.layout.popup_layout, null)
        contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        isOutsideTouchable = true
        isFocusable = true

        contentView.popup.setOnClickListener {
            dismiss()
        }

        setOnDismissListener {
            Toast.makeText(context, "Popup closed", Toast.LENGTH_SHORT).show()
        }
    }

    fun show(anchorView: View) {
        showAsDropDown(anchorView, anchorView.measuredWidth / 4,
                -anchorView.measuredHeight - calcNavHeight(), Gravity.NO_GRAVITY)
    }

    private fun calcNavHeight(): Int {
        val resource = context.resources
        val resourceId = resource.getIdentifier("navigation_bar_height", "dimen", "android")
        return if (resourceId > 0) {
            resource.getDimensionPixelOffset(resourceId)
        } else {
            0
        }
    }
}

結果はこんな感じ

Repository is here

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0