简体   繁体   中英

Align Container to top and bottom of Text

I'm trying to align a colored rectangle to the left of two Text widgets. Specifically, I want the bottom of the colored rectangle to align with the baseline of the lower Text and the top of the rectangle to align with the cap height of the upper Text. Here is a mock of what I'm trying to achieve: 所需布局的屏幕截图 My code so far:

final TextStyle helloTextStyle = const TextStyle(
  fontWeight: FontWeight.w600,
  fontSize: 28,
  letterSpacing: 0,
  wordSpacing: 0,
  fontFamily: "DejaVuSansCondensed",
  color: Color(0XFF232444),
  decoration: TextDecoration.none,
);
final TextStyle everyoneTextStyle = const TextStyle(
  fontWeight: FontWeight.w700,
  fontSize: 38,
  letterSpacing: 0,
  fontFamily: "DejaVuSansCondensed",
  color: Color(0XFF232444),
  decoration: TextDecoration.none,
);

return Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Container(
      decoration: BoxDecoration(
        border: Border(
          left: BorderSide(
              width: 16.0,
              color: Colors.red),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("HELLO", style: helloTextStyle),
          Text("EVERYONE", style: everyoneTextStyle),
        ],
      ),
    ),
  ],
)

How would I align the bottom of the colored rectangle with the baseline of the lower Text and align the the top of the rectangle to the cap height of the upper Text?

Edit: One solution would be to determine the distance between the baseline and the bottom of the Text widget as well as the distance between the cap height and the top of the Text widget. Text widget doesn't appear to offer those values though.

wrap you Row in a Container with its height property defined :

 Container(
  height: MediaQuery.of(context).size.height/4 ,
  child: Row(
    children: <Widget>[
      Column(
       children: <Widget>[
          Container(
           width: 16.0,
           color: Colors.red,
          ),
        ]
      ),
      Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Text("HELLO", style: helloTextStyle),
          Text("EVERYONE", style: everyoneTextStyle),
        ]
      ),
    ]
  ),
 ),

You Don't need to put Column as child of Container as you did. That way it won't Baseline with Text.

One way doing it:

double fontSize1 = 38.0;
double fontSize2 = 28.0;

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: IntrinsicHeight(
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(top:(fontSize2 / 4.5),bottom: (fontSize1 / 4.2)),
                child: Container(
                  decoration: BoxDecoration(
                    border: Border(
                      left: BorderSide(
                          width: 16.0,
                          color: Colors.red),
                    ),
                  ),
                ),
              ),
              Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text("HELLO", style: helloTextStyle),
                  Text("EVERYONE", style: everyoneTextStyle),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

Output:

在此处输入图片说明

with:

  double fontSize1 = 68.0;
  double fontSize2 = 18.0;

output: 在此处输入图片说明

so Padding is calculated auto now.

There is no Flutter API for getting the exact bounds of the text. Flutter: finding the exact bounds of text covers this. That said, I have a solution based on the same discussion.

The approach is to paint a character (a capital 'I' in my case) to a canvas and then scan the image pixels looking for the edge of the character. I count the rows of pixels between the character and image edge and use that to set the padding on the colored block. My solution is a little more involved because I have two Text widgets within a Column and each Text is a different size.

Note: I wouldn't advise this solution unless you really care about a precise alignment with the edge of the character.

The layout code:

IntrinsicHeight(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: <Widget>[
      FutureBuilder<TopBottomPadding>(
          future: _calcPadding(
              TextSpan(
                  text: "I", style: helloTextStyle),
              TextSpan(
                  text: "I", style: everyoneTextStyle),
              mediaQueryData.textScaleFactor),
          builder: (BuildContext context, tuple) {
            return Padding(
              padding: EdgeInsets.only(
                top: tuple.data.top,
                bottom: tuple.data.bottom,
              ),
              child: Container(
                decoration: BoxDecoration(
                  border: Border(
                    left: BorderSide(
                        width: 16.0, color: Colors.red),
                  ),
                ),
              ),
            );
          }),
      Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text("HELLO", style: helloTextStyle),
          Text("EVERYONE", style: everyoneTextStyle),
        ],
      ),
    ],
  ),
)

Generating the image involves an async call and so I've enlisted the FutureBuilder widget.

Future<TopBottomPadding> _calcPadding(final TextSpan topSpan,
    final TextSpan bottomSpan, final double textScaleFactor) async {
  final topPadding = await _calcTopPadding(topSpan, textScaleFactor);
  final bottomPadding = await _calcBottomPadding(bottomSpan, textScaleFactor);
  return TopBottomPadding(topPadding, bottomPadding);
}

Future<double> _calcTopPadding(TextSpan span, double textScaleFactor) async {
  final int bytesPerPixel = 4;
  final imageData =
      await _getImageByteData(span, ImageByteFormat.rawRgba, textScaleFactor);
  final Size imageSize = imageData.size;
  final ByteData byteData = imageData.byteData;
  final numRows =
      (byteData.lengthInBytes / (bytesPerPixel * imageSize.width)).round();
  int foundRow;
  /// Scan each pixel from top to bottom keeping track of the row
  for (int row = 0; row < numRows && foundRow == null; row++) {
    final int rowLength = bytesPerPixel * imageSize.width.round();
    final int startRowByteIndex = row * rowLength;
    /// Only looking at first byte of each pixel is good enough
    for (int byteArrayIndex = startRowByteIndex;
        byteArrayIndex < row * rowLength + rowLength;
        byteArrayIndex += bytesPerPixel) { 
      final int byteValue = byteData.getUint8(byteArrayIndex);
      /// The background is white so look for a non-white pixel.
      if (foundRow == null && byteValue != 0xff) {
        foundRow = row;
        break;
      }
    }
  }
  final double result = foundRow == null ? 0 : foundRow.toDouble();
  return result;
}

Future<double> _calcBottomPadding(
    final TextSpan span, final textScaleFactor) async {
  final int bytesPerPixel = 4;
  final imageData =
      await _getImageByteData(span, ImageByteFormat.rawRgba, textScaleFactor);
  final Size imageSize = imageData.size;
  final ByteData byteData = imageData.byteData;
  final numRows =
      (byteData.lengthInBytes / (bytesPerPixel * imageSize.width)).round();
  int foundRow;
  /// Scan each pixel from bottom to top keeping track of the row
  for (int row = numRows - 1; row >= 0 && foundRow == null; row--) {
    final int rowLength = bytesPerPixel * imageSize.width.round();
    final int startRowByteIndex = row * rowLength;
    /// Only looking at first byte of each pixel is good enough
    for (int byteArrayIndex = startRowByteIndex;
        byteArrayIndex < row * rowLength + rowLength;
        byteArrayIndex += bytesPerPixel) {
      final int byteValue = byteData.getUint8(byteArrayIndex);
      /// The background is white so look for a non-white pixel.
      if (foundRow == null && byteValue != 0xff) {
        foundRow = row;
        break;
      }
    }
  }
  final double foundRowIndex = foundRow == null ? 0 : foundRow.toDouble();
  final int heightAsZeroBasedIndex = imageSize.height.round() - 1;
  final double paddingValue = heightAsZeroBasedIndex - foundRowIndex;
  return paddingValue;
}

Future<ImageData> _getImageByteData(final TextSpan span,
    final ImageByteFormat byteFormat, final double textScaleFactor) async {
  final painter = TextPainter(
      text: span,
      textDirection: TextDirection.ltr,
      textScaleFactor: textScaleFactor);
  painter.layout();
  final imageData = ImageData();
  imageData.size = Size(painter.width, painter.height);
  final recorder = PictureRecorder();
  final screen = Offset.zero & imageData.size;
  final canvas = Canvas(recorder);
  drawBackground(canvas, screen);
  painter.paint(canvas, Offset.zero);
  final picture = recorder.endRecording();
  final image =
      await picture.toImage(screen.width.round(), screen.height.round());
  final ByteData byteData = await image.toByteData(format: byteFormat);
  imageData.byteData = byteData;
  return imageData;
}

void drawBackground(final Canvas canvas, final Rect screen) {
  canvas.drawRect(
      screen,
      Paint()
        ..color = Colors.white
        ..style = PaintingStyle.fill);
}

class TopBottomPadding {
  double top;
  double bottom;

  TopBottomPadding(this.top, this.bottom);
}

class ImageData {
  ByteData byteData;
  Size size;
}

This solution works for any screen density, font size, or text scale factor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM