flutter底部弹窗组件过大导致卡顿问题
思想
采用Visibility + Stack来显示、隐藏组件,而非每次都构建组件。
键盘弹出会导致组件移动,不再尝试实时获取键盘高度,而是选择直接通过通道获取键盘的固定高度。
实现:
1. Kotlin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.WindowManager
import android.view.Display
import android.graphics.Rect
import android.view.View
import android.view.Window
import android.os.Build
import android.os.Bundle
import android.view.WindowInsets
import android.view.ViewConfiguration;
import android.content.Context;
import android.util.DisplayMetrics
import android.view.KeyCharacterMap
import android.view.KeyEvent
//class MainActivity: FlutterActivity() {}
class MainActivity: FlutterActivity(), EventChannel.StreamHandler {
private val channelName = "keyboard_event_channel"
private var eventChannel: EventChannel? = null
private var eventSink: EventChannel.EventSink? = null
override fun onStart() {
super.onStart()
window.decorView.visibility = View.VISIBLE;
//flutterEngine!!.lifecycleChannel.appIsResumed()
}
override fun onRestart() {
super.onRestart()
window.decorView.visibility = View.VISIBLE;
//flutterEngine!!.lifecycleChannel.appIsResumed()
}
override fun onResume() {
super.onResume()
window.decorView.visibility = View.VISIBLE;
}
override fun onStop() {
super.onStop()
window.decorView.visibility = View.GONE;
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
eventChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
eventChannel?.setStreamHandler(this)
}
// eventChannel
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
// dosomething
val rootView = window?.decorView?.rootView
rootView?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
// 屏幕高度
val screenHeight = rootView.height
// 可视化区域
val r = Rect()
rootView.getWindowVisibleDisplayFrame(r)
// 屏幕高度 - 可视化区域的bottom == 键盘高度 + 导航栏
var keypadHeight = screenHeight - r.bottom
// 判断是否开启导航栏
val isNavBarVisible = isNavigationBarVisible()
//println("isNavBarVisible $isNavBarVisible")
if(isNavBarVisible) {
keypadHeight = keypadHeight - getNavigationBarHeight()
}
val displayMetrics = getResources().displayMetrics
val logicalKeypadHeight = keypadHeight / (displayMetrics?.density ?: 1f)
if (keypadHeight > screenHeight * 0.15) {
events?.success(logicalKeypadHeight.toDouble())
} else {
events?.success(0.0)
}
}
})
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
private fun getNavigationBarHeight(): Int {
val resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId != null && resourceId > 0) {
getResources().getDimensionPixelSize(resourceId) ?: 0
} else {
0
}
}
private fun isNavigationBarVisible(): Boolean {
//println("Build.VERSION.SDK_INT ${Build.VERSION.SDK_INT}")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowInsets = window.decorView.rootWindowInsets
return windowInsets?.isVisible(WindowInsets.Type.navigationBars())?: false
} else {
//val decorView = window.decorView
//val b1 = decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION == 0
//val b2 = ViewConfiguration.get(context).hasPermanentMenuKey();
// return b2 && b1 && b3;
val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)
val hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME)
val hasPermanentMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
//println("hasBackKey $hasBackKey")
//println("hasHomeKey $hasHomeKey")
//println("hasPermanentMenuKey $hasPermanentMenuKey")
val display = window.windowManager.defaultDisplay
val realDisplayMetrics = DisplayMetrics()
display.getRealMetrics(realDisplayMetrics)
val realHeight = realDisplayMetrics.heightPixels
val realWidth = realDisplayMetrics.widthPixels
val displayMetrics = DisplayMetrics()
display.getMetrics(displayMetrics)
val displayHeight = displayMetrics.heightPixels
val displayWidth = displayMetrics.widthPixels
//println("(realWidth - displayWidth) > 0 ${(realWidth - displayWidth) > 0}")
//println("(realHeight - displayHeight) > 0 ${(realHeight - displayHeight) > 0}")
//println("realHeight $realHeight")
//println("displayHeight $displayHeight")
val temporaryHidden = activity.window.decorView.visibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION != 0
//println("temporaryHidden $temporaryHidden")
if (temporaryHidden) return false
val decorView = activity.window.decorView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
decorView.rootWindowInsets?.let{
return it.stableInsetBottom != 0
}
}
return true
//return hasPermanentMenuKey || hasBackKey || hasHomeKey || (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
}
}
}2.dart
import 'dart:async';
import 'package:flutter/services.dart';
typedef KeyboardHeightCallback = void Function(double height);
class KeyBoardHeight {
static const keboardEventChannel = EventChannel('keyboard_event_channel');
StreamSubscription? streamSubscription;
void onKeyboardHeightChanged(KeyboardHeightCallback callback) {
if (streamSubscription != null) {
streamSubscription!.cancel();
}
streamSubscription =
keboardEventChannel.receiveBroadcastStream().listen((dynamic height) {
callback(height as double);
});
}
void dispose() {
if (streamSubscription != null) {
streamSubscription!.cancel();
}
}
}
3. 组件监听键盘高度变化
import 'package:flutter/material.dart';
import '../channel/keyboard_height.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: TestWidget(),
);
}
}
class TestWidget extends StatefulWidget {
const TestWidget({Key? key}) : super(key: key);
@override
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
final KeyBoardHeight _keyBoardHeight = KeyBoardHeight();
@override
void initState() {
super.initState();
_keyBoardHeight.onKeyboardHeightChanged((double height) {});
}
@override
Widget build(BuildContext context) {
return const SizedBox();
}
}