jazzbpn
jazzbpn

Reputation: 7318

Is it possible to get the size of the child widget without rendering in flutter?

I am trying to create a resizable widget that needs a size parameter. I want to make the height and width field dynamic according to it's child sizes, so that we do not have to give the static size of widget. Please help me out!

Note: Please go through ResizebleWidget class that takes the height and width parameter. I want to make theese params dynamic according to it's children widget size

import 'package:flutter/material.dart';

class ResizeWidget extends StatefulWidget {
@override
_ResizeWidgetState createState() => _ResizeWidgetState();
}

class _ResizeWidgetState extends State<ResizeWidget> {
@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      backgroundColor: Colors.black,
      body: Container(
        // padding: EdgeInsets.only(top: 50),
        child: ResizebleWidget(
          child: Container(
            padding: EdgeInsets.all(10),
            child: Text(
              'Waao!! you can really dance.',
              style: TextStyle(
                  color: Colors.white,
                  fontStyle: FontStyle.italic,
                  fontSize: 18),
            ),
          ),
        ),
      ),
    ),
  );
}
}

**HOW to create this ResizebleWidget size dynamic according to its children**
class ResizebleWidget extends StatefulWidget {
ResizebleWidget({this.child});

final Widget child;
@override
_ResizebleWidgetState createState() => _ResizebleWidgetState();
}

const ballDiameter = 10.0;

class _ResizebleWidgetState extends State<ResizebleWidget> {
double height = 100;
double width = 200;
bool isCorner = false;

double top = 0;
double left = 0;

@override
Widget build(BuildContext context) {
  return Stack(
    children: <Widget>[
      Positioned(
        top: top,
        left: left,
        child: Container(
          height: height,
          width: width,

          decoration: BoxDecoration(
            color: Colors.blueGrey,
            border: Border.all(
              width: 2,
              color: Colors.white70,
            ),
            borderRadius: BorderRadius.circular(0.0),
          ),

          // need tp check if draggable is done from corner or sides
          child: isCorner
              ? FittedBox(
                  child: widget.child,
                )
              : Center(
                  child: widget.child,
                ),
        ),
      ),
      // top left
      Positioned(
        top: top - ballDiameter / 2,
        left: left - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var mid = (dx + dy) / 2;
            var newHeight = height - 2 * mid;
            var newWidth = width - 2 * mid;

            setState(() {
              isCorner = true;
              height = newHeight > 0 ? newHeight : 0;
              width = newWidth > 0 ? newWidth : 0;
              top = top + mid;
              left = left + mid;
            });
          },
          handlerWidget: HandlerWidget.VERTICAL,
        ),
      ),
      // top middle
      Positioned(
        top: top - ballDiameter / 2,
        left: left + width / 2 - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var newHeight = height - dy;

            setState(() {
              isCorner = false;

              height = newHeight > 0 ? newHeight : 0;
              top = top + dy;
            });
          },
          handlerWidget: HandlerWidget.HORIZONTAL,
        ),
      ),
      // top right
      Positioned(
        top: top - ballDiameter / 2,
        left: left + width - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var mid = (dx + (dy * -1)) / 2;

            var newHeight = height + 2 * mid;
            var newWidth = width + 2 * mid;

            setState(() {
              isCorner = true;
              height = newHeight > 0 ? newHeight : 0;
              width = newWidth > 0 ? newWidth : 0;
              top = top - mid;
              left = left - mid;
            });
          },
          handlerWidget: HandlerWidget.VERTICAL,
        ),
      ),
      // center right
      Positioned(
        top: top + height / 2 - ballDiameter / 2,
        left: left + width - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var newWidth = width + dx;

            setState(() {
              isCorner = false;

              width = newWidth > 0 ? newWidth : 0;
            });
          },
          handlerWidget: HandlerWidget.HORIZONTAL,
        ),
      ),
      // bottom right
      Positioned(
        top: top + height - ballDiameter / 2,
        left: left + width - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var mid = (dx + dy) / 2;

            var newHeight = height + 2 * mid;
            var newWidth = width + 2 * mid;

            setState(() {
              isCorner = true;

              height = newHeight > 0 ? newHeight : 0;
              width = newWidth > 0 ? newWidth : 0;
              top = top - mid;
              left = left - mid;
            });
          },
          handlerWidget: HandlerWidget.VERTICAL,
        ),
      ),
      // bottom center
      Positioned(
        top: top + height - ballDiameter / 2,
        left: left + width / 2 - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var newHeight = height + dy;

            setState(() {
              isCorner = false;

              height = newHeight > 0 ? newHeight : 0;
            });
          },
          handlerWidget: HandlerWidget.HORIZONTAL,
        ),
      ),
      // bottom left
      Positioned(
        top: top + height - ballDiameter / 2,
        left: left - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var mid = ((dx * -1) + dy) / 2;

            var newHeight = height + 2 * mid;
            var newWidth = width + 2 * mid;

            setState(() {
              isCorner = true;

              height = newHeight > 0 ? newHeight : 0;
              width = newWidth > 0 ? newWidth : 0;
              top = top - mid;
              left = left - mid;
            });
          },
          handlerWidget: HandlerWidget.VERTICAL,
        ),
      ),
      //left center
      Positioned(
        top: top + height / 2 - ballDiameter / 2,
        left: left - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            var newWidth = width - dx;

            setState(() {
              isCorner = false;

              width = newWidth > 0 ? newWidth : 0;
              left = left + dx;
            });
          },
          handlerWidget: HandlerWidget.HORIZONTAL,
        ),
      ),
      // center center
      Positioned(
        top: top + height / 2 - ballDiameter / 2,
        left: left + width / 2 - ballDiameter / 2,
        child: ManipulatingBall(
          onDrag: (dx, dy) {
            setState(() {
              isCorner = false;

              top = top + dy;
              left = left + dx;
            });
          },
          handlerWidget: HandlerWidget.VERTICAL,
        ),
      ),
    ],
  );
}
}

class ManipulatingBall extends StatefulWidget {
ManipulatingBall({Key key, this.onDrag, this.handlerWidget});

final Function onDrag;
final HandlerWidget handlerWidget;

@override
_ManipulatingBallState createState() => _ManipulatingBallState();
}

enum HandlerWidget { HORIZONTAL, VERTICAL }

class _ManipulatingBallState extends State<ManipulatingBall> {
double initX;
double initY;

_handleDrag(details) {
  setState(() {
    initX = details.globalPosition.dx;
    initY = details.globalPosition.dy;
  });
}

_handleUpdate(details) {
  var dx = details.globalPosition.dx - initX;
  var dy = details.globalPosition.dy - initY;
  initX = details.globalPosition.dx;
  initY = details.globalPosition.dy;
  widget.onDrag(dx, dy);
}

@override
Widget build(BuildContext context) {
  return GestureDetector(
    onPanStart: _handleDrag,
    onPanUpdate: _handleUpdate,
    child: Container(
      width: ballDiameter,
      height: ballDiameter,
      decoration: BoxDecoration(
        color: Colors.white,
        shape: this.widget.handlerWidget == HandlerWidget.VERTICAL
            ? BoxShape.circle
            : BoxShape.rectangle,
      ),
    ),
  );
}
}

Upvotes: 1

Views: 1579

Answers (2)

Sachin Walunjakar
Sachin Walunjakar

Reputation: 1

It is not possible to get the height of the widget without rendering it, but we can use an overlay widget trick to get the height.

Use these steps:

  1. used an overlay to render the widget. (with opacity zero so it can't be visible)
  2. as the widget is rendered, so we can calculate the height of the widget

Here is the actual implementation:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

Future<double> getHeightOfWidget(Widget widget, BuildContext context) {
    Completer<double> completer = new Completer<double>();
    late OverlayEntry entry; 
  entry = OverlayEntry(
    builder: (_) => Center(
      child: WidgetSize(
                onChange: (Size s) {
                    entry.remove();
                    completer.complete(s.height);
                },
        child: Opacity(opacity: 0, child: widget),
      ),
    ),
  );
    Overlay.of(context)?.insert(entry);
    return completer.future;
}

class WidgetSize extends StatefulWidget {
  final Widget child;
  final Function onChange;

  const WidgetSize({
    Key? key,
    required this.onChange,
    required this.child,
  }) : super(key: key);

  @override
  _WidgetSizeState createState() => _WidgetSizeState();
}

class _WidgetSizeState extends State<WidgetSize> {
  @override
  Widget build(BuildContext context) {
    SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
    return Container(
      key: widgetKey,
      child: widget.child,
    );
  }

  var widgetKey = GlobalKey();
  var oldSize;

  void postFrameCallback(_) {
    var context = widgetKey.currentContext;
    if (context == null) return;

    var newSize = context.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    widget.onChange(newSize);
  }
}

Upvotes: 0

Gustavo Guzman
Gustavo Guzman

Reputation: 146

The size of widget cannot be determined before it has been rendered. However I can see you want to know the size of a text so you can create you own widget. You can do this by using TextPainter. In this example I'm using it to create some sort of a text container that you can style in any possible way thanks to CustomPainter.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.white,
        body: Center(child: TextContainer()),
      ),
    );
  }
}

class TextContainer extends StatelessWidget {
  static String text = "TextPainter is amazing!!!!!!!!!!!!!!!!!";
  static double padding =
      20.0; // in case you want you text container to have padding.

  @override
  Widget build(BuildContext context) {
    // to calculate the size of the text you need to create a TextPainter based on the text and
    // its style
    final TextPainter textPainter = TextPainter(
        textDirection: TextDirection.ltr,
        text: TextSpan(
            text: text,
            style: TextStyle(
              color: Colors.white,
              fontStyle: FontStyle.italic,
              fontSize: 50,
            )))
      ..layout(maxWidth: 300);
    // in the layout of the TexPainter you set the maxWidth, layout is required.
    return Container(
        width: textPainter.width + padding,
        height: textPainter.height + padding,
        child: CustomPaint(
          painter:
              TextContainerPainter(textPainter: textPainter, padding: padding),
        ));
  }
}

class TextContainerPainter extends CustomPainter {
  TextContainerPainter({required this.textPainter, required this.padding});

  final TextPainter textPainter;
  final double padding;

  @override
  void paint(Canvas canvas, Size size) {

    // ============================================== Container

    final paint = Paint()..color = Colors.greenAccent;
    final rect = Offset.zero &
        Size(textPainter.width + padding, textPainter.height + padding);

    canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(15)), paint);

    // ========================================== TextPainter

    textPainter.paint(canvas, Offset(padding / 2, padding / 2));
    // this offset is necessary so that the text is at the center of its container
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

As you can see in the image bellow, the widget has the size of the text and the padding we specified.

enter image description here

Upvotes: 1

Related Questions