瀏覽代碼

处理软键盘问题

cc12458 1 月之前
父節點
當前提交
0ba4029174

+ 110 - 0
app/src/main/java/com/hzliuzhi/applet/container/AndroidActivity.kt

@@ -0,0 +1,110 @@
+package com.hzliuzhi.applet.container
+
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.widget.FrameLayout
+import androidx.activity.ComponentActivity
+import androidx.activity.enableEdgeToEdge
+
+open class AndroidActivity : ComponentActivity() {
+  /**
+   * 软键盘适配工具
+   */
+  private var workaround: AndroidBug5497Workaround? = null
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    setContent() // 设置内容视图,可被子类重写
+    // 初始化软键盘适配工具
+    val content = findViewById<ViewGroup?>(android.R.id.content)
+    val child = content?.getChildAt(0)
+    workaround = if (child != null) AndroidBug5497Workaround(child) else null
+  }
+
+  /**
+   * 生命周期回调,注册软键盘适配监听。
+   */
+  override fun onResume() {
+    super.onResume()
+    workaround?.register()
+  }
+
+  /**
+   * 生命周期回调,移除软键盘适配监听。
+   */
+  override fun onPause() {
+    super.onPause()
+    workaround?.unregister()
+  }
+
+  /**
+   * 设置内容视图,默认启用EdgeToEdge,可被子类重写。
+   */
+  open fun setContent() {
+    enableEdgeToEdge()
+  }
+}
+
+/**
+ * 软键盘适配工具类,处理软键盘弹出时的布局自适应。
+ * 通过监听全局布局变化,动态调整根视图高度,防止内容被软键盘遮挡。
+ * 参考:https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible
+ * @property rootView 根视图
+ */
+private class AndroidBug5497Workaround(private val rootView: View) {
+  /** 根视图的布局参数 */
+  private val rootViewLayout: FrameLayout.LayoutParams = rootView.layoutParams as FrameLayout.LayoutParams
+
+  /** 用于监听布局变化的 ViewTreeObserver */
+  private var viewTreeObserver: ViewTreeObserver = rootView.viewTreeObserver
+
+  /** 用于记录窗口内容区域的 Rect */
+  private val contentAreaOfWindowBounds = Rect()
+
+  /** 上一次可用高度 */
+  private var usableHeightPrevious = 0
+
+  /**
+   * 全局布局监听器,触发内容区域高度调整。
+   */
+  private val listener = ViewTreeObserver.OnGlobalLayoutListener { possiblyResizeChildOfContent() }
+
+  /**
+   * 注册全局布局监听器。
+   */
+  fun register() {
+    if (!viewTreeObserver.isAlive) viewTreeObserver = rootView.viewTreeObserver
+    viewTreeObserver.addOnGlobalLayoutListener(listener)
+  }
+
+  /**
+   * 移除全局布局监听器,避免内存泄漏。
+   */
+  fun unregister() {
+    if (!viewTreeObserver.isAlive) viewTreeObserver = rootView.viewTreeObserver
+    viewTreeObserver.removeOnGlobalLayoutListener(listener)
+  }
+
+  /**
+   * 判断并调整内容区域高度,适配软键盘弹出等场景。
+   * 若可用高度发生变化,则重新设置根视图高度并请求布局。
+   */
+  private fun possiblyResizeChildOfContent() {
+    (rootView.parent as? ViewGroup)?.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds)
+    val usableHeightNow = contentAreaOfWindowBounds.height() + contentAreaOfWindowBounds.top
+    if (usableHeightNow != usableHeightPrevious) {
+      rootViewLayout.height = usableHeightNow
+      rootView.layout(
+        contentAreaOfWindowBounds.left,
+        contentAreaOfWindowBounds.top,
+        contentAreaOfWindowBounds.right,
+        contentAreaOfWindowBounds.bottom
+      )
+      rootView.requestLayout()
+      usableHeightPrevious = usableHeightNow
+    }
+  }
+}

+ 9 - 13
app/src/main/java/com/hzliuzhi/applet/container/MainActivity.kt

@@ -1,13 +1,10 @@
 package com.hzliuzhi.applet.container
 
 import android.annotation.SuppressLint
-import android.os.Bundle
 import android.view.KeyEvent
 import android.widget.Toast
-import androidx.activity.ComponentActivity
 import androidx.activity.compose.LocalActivity
 import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Scaffold
 import androidx.compose.runtime.rememberCoroutineScope
@@ -23,12 +20,17 @@ import com.hzliuzhi.applet.core.shared.SharedFlowHub
 import com.hzliuzhi.applet.core.theme.SixTheme
 import com.hzliuzhi.applet.scanner.Scanner
 
-class MainActivity : ComponentActivity() {
+class MainActivity : AndroidActivity() {
   private var navController: NavHostController? = null
 
-  override fun onCreate(savedInstanceState: Bundle?) {
-    super.onCreate(savedInstanceState)
-    enableEdgeToEdge()
+  @SuppressLint("RestrictedApi")
+  override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+    val context = applicationContext
+    return Scanner.getInstance(context).dispatchKeyEvent(event).takeIf { it } ?: super.dispatchKeyEvent(event)
+  }
+
+  override fun setContent() {
+    super.setContent()
     setContent {
       navController = rememberNavController().also {
         SixTheme {
@@ -69,10 +71,4 @@ class MainActivity : ComponentActivity() {
       }
     }
   }
-
-  @SuppressLint("RestrictedApi")
-  override fun dispatchKeyEvent(event: KeyEvent): Boolean {
-    val context = applicationContext
-    return Scanner.getInstance(context).dispatchKeyEvent(event).takeIf { it } ?: super.dispatchKeyEvent(event)
-  }
 }