AndroidActivity.kt 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package com.hzliuzhi.applet.container
  2. import android.graphics.Rect
  3. import android.os.Bundle
  4. import android.view.View
  5. import android.view.ViewGroup
  6. import android.view.ViewTreeObserver
  7. import android.widget.FrameLayout
  8. import androidx.activity.ComponentActivity
  9. import androidx.activity.enableEdgeToEdge
  10. open class AndroidActivity : ComponentActivity() {
  11. /**
  12. * 软键盘适配工具
  13. */
  14. private var workaround: AndroidBug5497Workaround? = null
  15. override fun onCreate(savedInstanceState: Bundle?) {
  16. super.onCreate(savedInstanceState)
  17. setContent() // 设置内容视图,可被子类重写
  18. // 初始化软键盘适配工具
  19. val content = findViewById<ViewGroup?>(android.R.id.content)
  20. val child = content?.getChildAt(0)
  21. workaround = if (child != null) AndroidBug5497Workaround(child) else null
  22. }
  23. /**
  24. * 生命周期回调,注册软键盘适配监听。
  25. */
  26. override fun onResume() {
  27. super.onResume()
  28. workaround?.register()
  29. }
  30. /**
  31. * 生命周期回调,移除软键盘适配监听。
  32. */
  33. override fun onPause() {
  34. super.onPause()
  35. workaround?.unregister()
  36. }
  37. /**
  38. * 设置内容视图,默认启用EdgeToEdge,可被子类重写。
  39. */
  40. open fun setContent() {
  41. enableEdgeToEdge()
  42. }
  43. }
  44. /**
  45. * 软键盘适配工具类,处理软键盘弹出时的布局自适应。
  46. * 通过监听全局布局变化,动态调整根视图高度,防止内容被软键盘遮挡。
  47. * 参考:https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible
  48. * @property rootView 根视图
  49. */
  50. private class AndroidBug5497Workaround(private val rootView: View) {
  51. /** 根视图的布局参数 */
  52. private val rootViewLayout: FrameLayout.LayoutParams = rootView.layoutParams as FrameLayout.LayoutParams
  53. /** 用于监听布局变化的 ViewTreeObserver */
  54. private var viewTreeObserver: ViewTreeObserver = rootView.viewTreeObserver
  55. /** 用于记录窗口内容区域的 Rect */
  56. private val contentAreaOfWindowBounds = Rect()
  57. /** 上一次可用高度 */
  58. private var usableHeightPrevious = 0
  59. /**
  60. * 全局布局监听器,触发内容区域高度调整。
  61. */
  62. private val listener = ViewTreeObserver.OnGlobalLayoutListener { possiblyResizeChildOfContent() }
  63. /**
  64. * 注册全局布局监听器。
  65. */
  66. fun register() {
  67. if (!viewTreeObserver.isAlive) viewTreeObserver = rootView.viewTreeObserver
  68. viewTreeObserver.addOnGlobalLayoutListener(listener)
  69. }
  70. /**
  71. * 移除全局布局监听器,避免内存泄漏。
  72. */
  73. fun unregister() {
  74. if (!viewTreeObserver.isAlive) viewTreeObserver = rootView.viewTreeObserver
  75. viewTreeObserver.removeOnGlobalLayoutListener(listener)
  76. }
  77. /**
  78. * 判断并调整内容区域高度,适配软键盘弹出等场景。
  79. * 若可用高度发生变化,则重新设置根视图高度并请求布局。
  80. */
  81. private fun possiblyResizeChildOfContent() {
  82. (rootView.parent as? ViewGroup)?.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds)
  83. val usableHeightNow = contentAreaOfWindowBounds.height() + contentAreaOfWindowBounds.top
  84. if (usableHeightNow != usableHeightPrevious) {
  85. rootViewLayout.height = usableHeightNow
  86. rootView.layout(
  87. contentAreaOfWindowBounds.left,
  88. contentAreaOfWindowBounds.top,
  89. contentAreaOfWindowBounds.right,
  90. contentAreaOfWindowBounds.bottom
  91. )
  92. rootView.requestLayout()
  93. usableHeightPrevious = usableHeightNow
  94. }
  95. }
  96. }