65 lines
3.0 KiB
Dart
65 lines
3.0 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
/// 204 (사용자 지적 10/11): Android 브라우저에서 화면 아래를 위로 밀면 아래에서 검은 화면이 올라왔다.
|
|
/// 제스처 내비 · 삼성 태블릿의 작업 표시줄은 아래에서 밀면 잠깐 나타나고, 그동안 아래 시스템 막대 높이 (viewPadding) 가 커진다.
|
|
/// 앱 맨 위의 SafeArea 가 그만큼 앱 전체를 줄여 그 자리에 어두운 바탕이 보이고, 텍스처로 그리는 웹 뷰는
|
|
/// 새 크기로 다시 그릴 때까지 늦어 더 검게 보였다.
|
|
///
|
|
/// 이 위젯 안의 [child] (웹 뷰) 는 그렇게 잠깐 커진 아래 막대 때문에는 크기를 바꾸지 않는다: 원래 크기 그대로 두고
|
|
/// 줄어든 만큼 아래로 넘쳐 (자르지 않고) 그 띠 위에 그린다. 막대가 들어가면 그대로 원래 자리.
|
|
/// - 키보드가 열리면 (viewInsets) 지금처럼 줄인다 - 입력 칸이 키보드에 가리지 않게.
|
|
/// - 커진 막대가 [settle] 넘게 그대로면 (작업 표시줄을 고정하는 등) 그 크기를 새 기준으로 받아들인다.
|
|
/// - 폭이 바뀌면 (회전 · 창 크기) 기준을 다시 잡는다.
|
|
class BottomInsetHold extends StatefulWidget {
|
|
final Widget child;
|
|
final Duration settle;
|
|
const BottomInsetHold({super.key, required this.child, this.settle = const Duration(seconds: 3)});
|
|
|
|
@override
|
|
State<BottomInsetHold> createState() => _BottomInsetHoldState();
|
|
}
|
|
|
|
class _BottomInsetHoldState extends State<BottomInsetHold> {
|
|
/// 이 폭에서 기준으로 삼는 아래 시스템 막대 높이 (논리 픽셀)
|
|
double? _base;
|
|
double _width = -1;
|
|
Timer? _accept;
|
|
|
|
@override
|
|
void dispose() {
|
|
_accept?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => LayoutBuilder(builder: (context, box) {
|
|
// 앱 위 SafeArea 가 MediaQuery 의 여백을 지우므로 창 값을 직접 본다. 막대가 바뀌면 SafeArea 가 크기를 바꿔
|
|
// 이 builder 가 다시 불리므로 여기서 읽는다 (밖에서 읽으면 예전 값)
|
|
final view = View.of(context);
|
|
final dpr = view.devicePixelRatio;
|
|
final pad = view.viewPadding.bottom / dpr;
|
|
final keyboard = view.viewInsets.bottom / dpr;
|
|
if (box.maxWidth != _width) {
|
|
_width = box.maxWidth;
|
|
_base = pad;
|
|
}
|
|
if (pad <= _base!) {
|
|
_base = pad;
|
|
_accept?.cancel();
|
|
_accept = null;
|
|
}
|
|
final extra = keyboard > 0 ? 0.0 : pad - _base!;
|
|
if (extra <= 0 || !box.hasBoundedHeight) return widget.child;
|
|
// 오래 그대로면 새 기준으로 (한 번 다시 배치)
|
|
_accept ??= Timer(widget.settle, () {
|
|
if (!mounted) return;
|
|
setState(() => _base = View.of(context).viewPadding.bottom / View.of(context).devicePixelRatio);
|
|
_accept = null;
|
|
});
|
|
final h = box.maxHeight + extra;
|
|
return OverflowBox(alignment: Alignment.topCenter, minHeight: h, maxHeight: h, child: widget.child);
|
|
});
|
|
}
|