Comments 8
А в чем проблема использовать Stack
? Текущее решение мне кажется оверинжирингом, во всяком случае, для конкретной указанной проблемы.
Во-первых, Stack
все равно используется внутри buildOverlay
. Во-вторых, надо оборачивать каждый TextField
.
Я бы сделал что-нибудь типа такого и обернул им SingleChildScrollView
:
class Wrapper extends StatelessWidget {
const Wrapper({Key key, this.child, this.isVisible}) : super(key: key);
final Widget child;
final bool isVisible;
@override
Widget build(BuildContext context) {
const double height = 50;
return Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: isVisible ? height : 0),
child: child,
),
if (isVisible)
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.red,
height: height,
),
),
],
);
}
}
+1
Можно оставить чисто стек, но Overlay (неважно что внутри) более независимо.
Придется тогда весь экран класть в стек, это может породить дополнительные проблемы.
Текущий кейс просто пример. После написания статьи, скроллил к активному фильтру в AppBar, у фильтров динамический размер. Обернул каждый в RenderMetricsObject, сложил размеры и проскроллил при заходе на экран.
А как доскралливать на разницу в вашем решении, плашка же перекроет поле. Особенно, если поле многострочное. Или вы только о инструментах позиционирования?
Придется тогда весь экран класть в стек, это может породить дополнительные проблемы.
Текущий кейс просто пример. После написания статьи, скроллил к активному фильтру в AppBar, у фильтров динамический размер. Обернул каждый в RenderMetricsObject, сложил размеры и проскроллил при заходе на экран.
А как доскралливать на разницу в вашем решении, плашка же перекроет поле. Особенно, если поле многострочное. Или вы только о инструментах позиционирования?
0
— В OverlayEntry можно прокидывать такие виджеты как Positioned и другие, которые работают со Stack.
То есть не надо строить Stack в OverlayEntry если у вас 1 ребёнок
Тут и тут
— Не уверен, что скролл вниз удобен.
Скролл вверх, чтобы поле стало видимым — это понятно, но наоборот — скорее не привычно
То есть не надо строить Stack в OverlayEntry если у вас 1 ребёнок
Тут и тут
— Не уверен, что скролл вниз удобен.
Скролл вверх, чтобы поле стало видимым — это понятно, но наоборот — скорее не привычно
Данный случай можно решить так
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:keyboard_visibility/keyboard_visibility.dart';
void main() => runApp(
MaterialApp(
home: App(),
),
);
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
class NextBlock extends StatelessWidget {
const NextBlock({
Key key,
this.isShow,
}) : super(key: key);
final bool isShow;
@override
Widget build(BuildContext context) {
if (!isShow) return const SizedBox();
return ColoredBox(
color: const Color.fromRGBO(0, 0, 0, 0.3),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FlatButton(
color: Colors.white,
onPressed: () {},
child: Text('Next'),
),
),
);
}
}
class _AppState extends State<App> {
final list = List.generate(20, (index) => index.toString());
bool _isShow = false;
OverlayEntry _overlayEntry;
KeyboardVisibilityNotification _keyboardListener;
final _key = GlobalKey();
double _height = 0;
@override
void initState() {
super.initState();
_overlayEntry = OverlayEntry(builder: _buildOverlay);
_keyboardListener = KeyboardVisibilityNotification()
..addNewListener(onChange: _keyboardHandle);
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
Overlay.of(context).insert(_overlayEntry);
});
}
@override
void dispose() {
_keyboardListener.dispose();
_overlayEntry.remove();
super.dispose();
}
Widget _buildOverlay(BuildContext context) => Positioned(
bottom: MediaQuery.of(context).viewInsets.bottom,
left: 0,
right: 0,
child: AnimatedOpacity(
key: _key,
duration: const Duration(milliseconds: 200),
opacity: _isShow ? 1.0 : 0.0,
child: NextBlock(
isShow: _isShow,
),
),
);
void _keyboardHandle(bool isVisible) {
_isShow = isVisible;
_overlayEntry?.markNeedsBuild();
if (isVisible) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
final height = _key.currentContext.size.height;
if (height != _height) {
setState(() => _height = height);
}
});
}
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return MediaQuery(
data: _isShow
? mediaQuery.copyWith(
viewInsets: mediaQuery.viewInsets.copyWith(
bottom: mediaQuery.viewInsets.bottom + _height,
),
)
: mediaQuery,
child: Scaffold(
body: SingleChildScrollView(
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
for (String value in list)
TextField(
decoration: InputDecoration(labelText: value),
)
],
),
),
),
),
),
);
}
}
0
1. Круто.
2. По поводу вниз — это было требование заказчика. Данный пример реальный кейс из приложения, просто UI другой. Не стал ничего менять и показал как есть.
2. По поводу вниз — это было требование заказчика. Данный пример реальный кейс из приложения, просто UI другой. Не стал ничего менять и показал как есть.
0
Тогда так
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:keyboard_visibility/keyboard_visibility.dart';
void main() => runApp(
MaterialApp(
home: App(),
),
);
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
class NextBlock extends StatelessWidget {
const NextBlock({
Key key,
this.isShow,
}) : super(key: key);
final bool isShow;
@override
Widget build(BuildContext context) {
if (!isShow) return const SizedBox();
return ColoredBox(
color: const Color.fromRGBO(0, 0, 0, 0.3),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FlatButton(
color: Colors.white,
onPressed: () {},
child: Text('Next'),
),
),
);
}
}
class _AppState extends State<App> {
final list = List.generate(20, (index) => index.toString());
bool _isShow = false;
OverlayEntry _overlayEntry;
KeyboardVisibilityNotification _keyboardListener;
FocusManager _focusScope;
final _key = GlobalKey();
final _scrollKey = GlobalKey();
double _height = 0;
@override
void initState() {
super.initState();
_overlayEntry = OverlayEntry(builder: _buildOverlay);
_keyboardListener = KeyboardVisibilityNotification()
..addNewListener(onChange: _keyboardHandle);
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
_focusScope = context.owner.focusManager..addListener(_handleFocusChange);
Overlay.of(context).insert(_overlayEntry);
});
}
void _handleFocusChange() {
final textFieldContext = FocusScope.of(context).focusedChild.context;
Scrollable.ensureVisible(
textFieldContext,
alignment: 0.9,
duration: const Duration(milliseconds: 400),
);
}
@override
void dispose() {
_focusScope.removeListener(_handleFocusChange);
_keyboardListener.dispose();
_overlayEntry.remove();
super.dispose();
}
Widget _buildOverlay(BuildContext context) => Positioned(
bottom: MediaQuery.of(context).viewInsets.bottom,
left: 0,
right: 0,
child: AnimatedOpacity(
key: _key,
duration: const Duration(milliseconds: 200),
opacity: _isShow ? 1.0 : 0.0,
child: NextBlock(
isShow: _isShow,
),
),
);
void _keyboardHandle(bool isVisible) {
_isShow = isVisible;
_overlayEntry?.markNeedsBuild();
if (isVisible) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
final height = _key.currentContext.size.height;
if (height != _height) {
setState(() => _height = height);
}
});
}
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return MediaQuery(
data: _isShow
? mediaQuery.copyWith(
viewInsets: mediaQuery.viewInsets.copyWith(
bottom: mediaQuery.viewInsets.bottom + _height,
),
)
: mediaQuery,
child: Scaffold(
body: SingleChildScrollView(
key: _scrollKey,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
for (String value in list)
TextField(
decoration: InputDecoration(labelText: value),
)
],
),
),
),
),
),
);
}
}
0
А если у виджета текстового поля будут паддинги, контейнеры и т.д., то как тогда доскроллить в нужное место в вашем решении?
Также если нужно будет скроллить не до текстового поля, а допустим, до кнопки, то ваше решение выше не подойдет.
Только если использовать колбэк для получения контекста или GlobalKey.
Также если нужно будет скроллить не до текстового поля, а допустим, до кнопки, то ваше решение выше не подойдет.
Только если использовать колбэк для получения контекста или GlobalKey.
0
Ваше решение тоже не идеально, так как нижнее поле закрыто оверлеем, хотя речь в статье об этом, а значит основная проблема не решена.
В любом случае надеюсь в библиотеке появится что-то, что бы само пробрасывало менеджер и реализация для сливеров
Скриншот
В любом случае надеюсь в библиотеке появится что-то, что бы само пробрасывало менеджер и реализация для сливеров
0
Sign up to leave a comment.
Flutter. RenderObject — замеряй и властвуй