首页 > 解决方案 > 如何在全角按钮中居中容器,同时它们中的文本应该从左侧的相同位置开始?

问题描述

换句话说,问题的更长版本:如何在全宽 [Y] 小部件中居中 [X] 小部件,同时 [X] 中具有随机宽度的 [Z] 小部件应从左侧开始与其他 [Y] 小部件中的其他 [Z] 小部件的位置相同?

进行翻译后,我意识到在这种情况下我不能使用硬编码值。需要动态子容器宽度支持。

我试图玩 IntrinsicWidth 但没有运气。我不认为这个问题可以用它来解决。也许它有点复杂,也许不是。希望有人可以提供一个不错的解决方案。


在此处输入图像描述

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 && 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 && _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);
  }
}

更新 - 我创建了两个可能的解决方案并将它们包含在上面的代码中!

在此处输入图像描述

标签: flutterflutter-layout

解决方案


这是可能的,但您必须在构建小部件后设置值。这是我在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)
        ],
      ),
    );
  }
}

推荐阅读