#flutter #flutter-layout
#flutter #flutter-макет
Вопрос:
Более длинная версия вопроса другими словами: как центрировать виджет [X] в полноразмерном виджете [Y], в то же время виджет [Z] со случайной шириной в [X] должен начинаться слева в том же положении, что и другие виджеты [Z] в других виджетах.[Y] виджеты?
После выполнения переводов я понял, что в этом случае я не могу использовать жестко заданные значения. Необходима поддержка динамической ширины субконтейнера.
Я пытался поиграть с внутренней шириной, но безуспешно. Я не думаю, что проблема может быть решена с помощью этого. Может быть, это немного сложнее, а может и нет. Надеюсь, кто-нибудь может помочь с хорошим решением.
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter/scheduler.dart';
void main() {
runApp(MaterialApp(
home: TestScreen(),
));
}
class TestScreen extends StatefulWidget {
@override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
final List<String> _randomStringsForSolutions = Iterable<int>.generate(5).map((e) => _randomStringInRandomLength).toList();
final List<GlobalKey> _globalKeysForSolution2 = Iterable<int>.generate(5).map((e) => GlobalKey()).toList();
double _biggestWidthForSolution1;
double _biggestWidthForSolution2;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(_setBiggestWidthForSolution2);
}
_setBiggestWidthForSolution2(_) {
double biggestWidth;
for (int i = 0; i < _globalKeysForSolution2.length; i ) {
GlobalKey globalKey = _globalKeysForSolution2[i];
RenderBox renderBox = globalKey.currentContext?.findRenderObject();
if (renderBox == null) {
continue;
}
double width = renderBox.size.width;
if (biggestWidth != null amp;amp; biggestWidth >= width) {
continue;
}
biggestWidth = width;
}
if (_biggestWidthForSolution2 == biggestWidth) {
return;
}
setState(() {
_biggestWidthForSolution2 = biggestWidth;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.brown,
body: Stack(
children: [
Center(child: Container(color: Colors.black, width: 2.0)), // a vertical line in the center for easier understanding
SingleChildScrollView(
child: SafeArea(
child: Column(
children: [
Text('expected result: the icons are in line and everything is centered as possible - only works with hardcoded content width', style: TextStyle(color: Colors.white)),
...expectedResult,
SizedBox(height: 20.0),
Text('actual result: the icons are in line, but the content is not really centered', style: TextStyle(color: Colors.white)),
...actualResult,
Divider(height: 60.0, thickness: 10.0, color: Colors.green),
Text('solution1: the icons are in line and everything is centered as possible - dynamic content width supported', style: TextStyle(color: Colors.lightGreenAccent)),
Text(
'solution1 disadvantages: performance problems are possible because of re-renders (use-case dependent); a new SizeMeasurer widget class has to be implemented',
style: TextStyle(color: Colors.white),
),
...solution1,
Divider(height: 60.0, thickness: 10.0, color: Colors.green),
Text(
'solution2: the icons are in line and everything is centered as possible - dynamic content width supported with only one re-render',
style: TextStyle(color: Colors.lightGreenAccent),
),
Text(
'solution2 disadvantages: we need to keep records of global keys',
style: TextStyle(color: Colors.white),
),
...solution2,
],
),
),
),
],
),
);
}
/// Expected result with hardcoded content and width settings
List<Widget> get expectedResult {
return [
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Container(
width: 200,
child: Center(
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10),
child: Icon(Icons.mood),
),
Text('Continue with Facebook'),
],
),
),
),
),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Container(
width: 200,
child: Center(
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10),
child: Icon(Icons.mood),
),
Text('Continue with Twitter'),
],
),
),
),
),
),
];
}
/// Actual result when the lengths of the strings are unknown
List<Widget> get actualResult {
return List.generate(2, (_) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Container(
width: 200, // THIS CAN NO LONGER BE USED!
child: Center(
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10),
child: Icon(Icons.mood_bad),
),
Text(_randomStringInRandomLength),
],
),
),
),
),
);
});
}
List<Widget> get solution1 {
return List.generate(_randomStringsForSolutions.length, (index) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Container(
width: _biggestWidthForSolution1 ?? null,
child: SizeMeasurer(
onChange: (sizeOfChild) {
if (_biggestWidthForSolution1 != null amp;amp; _biggestWidthForSolution1 >= sizeOfChild.width) {
return;
}
setState(() {
_biggestWidthForSolution1 = sizeOfChild.width;
});
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10),
child: Icon(Icons.mood),
),
Text(_randomStringsForSolutions[index]),
],
),
),
),
),
);
});
}
List<Widget> get solution2 {
return List.generate(_randomStringsForSolutions.length, (index) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Container(
width: _biggestWidthForSolution2 ?? null,
child: Row(
key: _globalKeysForSolution2[index],
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10),
child: Icon(Icons.mood),
),
Text(_randomStringsForSolutions[index]),
],
),
),
),
);
});
}
}
String get _randomStringInRandomLength {
Random random = Random();
int randomInt = random.nextInt(20) 1;
return String.fromCharCodes(List.generate(randomInt, (index) => random.nextInt(33) 89));
}
typedef void OnWidgetSizeChange(Size size);
class SizeMeasurer extends StatefulWidget {
final Widget child;
final OnWidgetSizeChange onChange;
const SizeMeasurer({
Key key,
@required this.onChange,
@required this.child,
}) : super(key: key);
@override
_SizeMeasurerState createState() => _SizeMeasurerState();
}
class _SizeMeasurerState extends State<SizeMeasurer> {
GlobalKey widgetKey = GlobalKey();
Size oldSize;
@override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
return Container(
key: widgetKey,
child: widget.child,
);
}
void postFrameCallback(_) {
BuildContext context = widgetKey.currentContext;
if (context == null) {
return;
}
Size newSize = context.size;
if (oldSize == newSize) {
return;
}
oldSize = newSize;
widget.onChange(newSize);
}
}
ОБНОВЛЕНИЕ — я создал два возможных решения и включил их в приведенный выше код!
Ответ №1:
Это возможно, но вам нужно будет установить значения после сборки виджета. Вот пример кода, который я написал для вас в Dartpad ( https://dartpad.dev/fea023e2c9191faa21d01af92d808ca7 ). Приведенный ниже код выполняется только один раз после сборки виджета, вы можете изменить его, чтобы он запускался при каждом изменении состояния, но это можно легко сделать :
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: MyHome(),
);
}
}
class MyHome extends StatefulWidget {
final String title;
const MyHome({Key key, this.title}) : super(key: key);
@override
_MyHomeState createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
GlobalKey _keyRed = GlobalKey();
double padding = 0;
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _findPosition());
}
void _findPosition() {
final RenderBox renderBoxRed = _keyRed.currentContext.findRenderObject();
final positionRed = renderBoxRed.localToGlobal(Offset.zero);
print(positionRed);
setState(() {
padding = positionRed.dx;
});
}
@override
Widget build(BuildContext context) {
List<String> titles = ["one", "two", "three", "f"];
final String longest = titles.reduce((a, b) {
return a.length > b.length ? a : b;
});
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
for (var title in titles)
MyRow(
title: title,
alignment: longest == title
? MainAxisAlignment.center
: MainAxisAlignment.start,
padding: longest == title ? 0 : padding,
rowKey: longest == title ? _keyRed : null),
],
),
);
}
}
class MyRow extends StatelessWidget {
const MyRow({this.title, this.alignment, this.padding, this.rowKey});
final String title;
final MainAxisAlignment alignment;
final double padding;
final GlobalKey rowKey;
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisAlignment: alignment,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: padding),
child: Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
key: rowKey,
),
),
SizedBox(width: 10),
Text(title)
],
),
);
}
}
Ответ №2:
Вы можете использовать поля строки, чтобы получить то, что вы ищете, в частности размер, который может быть установлен в min.
Это ожидаемый результат приведенного ниже кода.
return SizedBox(
width: double.maxFinite,
child: ElevatedButton(
onPressed: () {},
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.mood),
SizedBox(width: 10),
Text('Continue with Twitter'),
],
),
),
);
Комментарии:
1. Неправильный ответ. В вашем «решении» строки центрируются, но значки не совпадают со значками на других кнопках.
2. Возможно, я неправильно понял ваш вопрос, рад, что вы разобрались. Счастливого кодирования!