n8crwlr
n8crwlr

Reputation: 313

How to get child widget size for animation values in parent

While flutter is growing, there are much recommendations and "ways how to". I am not sure what is correct as i am really new to the framework.

Say, i have the following code (hardly reduced):

@override
Widget build(BuildContext context) {
  return GestureDetector(
    // heights are needed here for animation values
      child: Stack(
    children: <Widget>[
      Positioned(
        left: 0,
        right: 0,
        bottom: value,
        child: Column( // overall height
          children: <Widget>[
            Container(height: 60),  // 1
            Container(height: 150), // 2
            Container(height: 200),
          ],
        ),
      ),
    ],
  ));
}

In the GestureDetector i am animating some widget actions, depending on different heights of it`s children.

The height of children //1 and //2 (and third) will be set hardcoded while developing, but, because there are several such elements and custom widgets, i want to get them automatically. Later on, this is better maintenable all in all, because mostly every widget anywhere has some animations.

So, i can use GlobalKey, or maybe BoxConstraints, but - all in all - what is the cheapest way to do that if building as a custom widget?

To close the question: in GestureDetector i am able to rebuild after the initial layout is done and after every rebuild.

I am just not sure about which way is safe and has minimum costs (having those widgets several times in the code).

About //1 and //2: //1 is the initial animation begin: value, //2 is a value in the middle to stop, // overall height is animation end: value. Just to explain.

At least: i am using states_rebuilder inside all of that.

Upvotes: 5

Views: 4102

Answers (2)

DevLife of Brian
DevLife of Brian

Reputation: 1014

Inspired by the accepted answer of @Thang Mai I made a similar stateless solution:

class ChildSizeNotifier extends StatefulWidget {
  final Widget Function(BuildContext context, Size size, Widget child) builder;
  final Widget child;
  ChildSizeNotifier({
    Key? key,
    required this.builder,
    this.child,
  }) : super(key: key) {}

  @override
  State<ChildSizeNotifier> createState() => _ChildSizeNotifierState();
}

class _ChildSizeNotifierState extends State<ChildSizeNotifier> {
 final ValueNotifier<Size> _notifier = ValueNotifier(const Size(0, 0));

  @override
  void dispose() {
    _notifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback(
      (_) {
        notifier.value = (context.findRenderObject() as RenderBox).size;
      },
    );
    return ValueListenableBuilder(
      valueListenable: notifier,
      builder: builder,
      child: child,
    );
  }
}

Use it like this

ChildSizeNotifier(
  builder: (context, size, child) {
    // size is the size of the text
    return Text(size.height > 50 ? 'big' : 'small');
  },
)

Upvotes: 2

Thắng Mai
Thắng Mai

Reputation: 1061

We just can get the size of a widget after it's rendered unless it is a constant. We can use ValueNotifier to send the size of the child after it rendered.

Here is the completed example for it.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ValueNotifier<Size> _size = ValueNotifier<Size>(Size(0, 0));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("example")
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ValueListenableBuilder(
              builder: (BuildContext context, Size value, Widget child) {
                return Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    Text('Height of the child: ${value.height}'),
                    Text('Width of the child: ${value.width}'),
                    SizeTrackingWidget(
                      child: Container(
                        height: MediaQuery.of(context).size.width*0.4,
                        width:  MediaQuery.of(context).size.width*0.3,
                        color: Colors.red
                      ),
                      sizeValueNotifier: _size,
                    )
                  ],
                );
              },
              valueListenable: _size,
              child: null,
            )
          ],
        ),
      ),
    );
  }
}


class SizeTrackingWidget extends StatefulWidget {
  Widget child;
  ValueNotifier<Size> sizeValueNotifier;
  SizeTrackingWidget({Key key, @required this.sizeValueNotifier, @required this.child}) : super(key: key);
  @override
  _SizeTrackingWidgetState createState() => _SizeTrackingWidgetState();
}

class _SizeTrackingWidgetState extends State<SizeTrackingWidget> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_){
      _getSize();
    });
  }

  _getSize() {
    RenderBox renderBox = context.findRenderObject();
    widget.sizeValueNotifier.value = renderBox.size;
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

Upvotes: 4

Related Questions