how do you make a container(whose child is a textformfield) expand based on the number of lines of text? (flutter)

1

I am attempting to make a container expand based on the number of lines in a textformfield as you see in most modern messaging apps. I've searched high and low with no avail and am part of the way there with a possible solution. So i have the container height change based on the number of lines part but the container will not respect the changes. I tried putting a set state after I made the changes but it overrode the textformfield to one line and it reverted the changes. My code can be seen below if you have any suggestions on how to edit my current code or even change my code to make it work as intended I'm open to all suggestions! If you need any more info tag me in a comment or email me isis@hanglyde.com Referenced https://github.com/flutter/flutter/issues/21943

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:photosgroup2/recurringwidgets/ombrebackground.dart';
import 'recurringwidgets/size_config.dart';

//import 'package:flutter_chat_bar/flutter_chat_bar.dart';
import 'chat/chatbar.dart';


///MAX BOX SIZE W 291 H 294
///MIN BOX SIZE W 150 H 52
import 'expandedImage.dart';

class ChatDemo extends StatefulWidget {
  //final User user;

  //ChatDemo({this.user});

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

class _ChatDemoState extends State<ChatDemo> with RouteAware {
  double intialHeight = ChatBubbleState.chatBarHeight;
  
  @override

  Widget build(BuildContext context) {
    print('Chat Bar Height $ChatBubbleState.chatBarHeight ');
  if(ChatBubbleState.updated){
    print('Chat Bar Height $ChatBubbleState.chatBarHeight ');
  }  
  
    return Scaffold(
      resizeToAvoidBottomInset: false,
      //resizeToAvoidBottomPadding: true,

      body: SafeArea(
        bottom: false,
        top: false,
        child: //Column(
            //mainAxisAlignment: MainAxisAlignment.spaceAround,
            //children: <Widget>[

            Stack(
          children: <Widget>[
            ombreBackground(),
            

            Positioned(
              bottom: MediaQuery.of(context).viewInsets.bottom,
              child: Padding(
                padding: EdgeInsets.only(top: 750 * SizeConfig.heightRatio),
                child: Stack(
                  children: [
                    Positioned(
                      top: 22.5 * SizeConfig.heightRatio,
                      bottom: 0,
                      child: ClipRRect(
                        borderRadius: BorderRadius.only(
                          topLeft: Radius.circular(32.0),
                          topRight: Radius.circular(32.0),
                        ),
                        child: ClipRect(
                          //clipBehavior: Clip.antiAlias,
                          child: BackdropFilter(
                            filter: ui.ImageFilter.blur(
                              sigmaX: 20,
                              sigmaY: 20,
                            ),
                            child: Transform.translate(
                              offset: Offset(0, 20 * SizeConfig.heightRatio),
                              child: Container(
                                height: 42,
                                width: SizeConfig.screenWidth,
                                decoration: BoxDecoration(
                                  borderRadius: BorderRadius.only(
                                    topLeft: Radius.circular(32.0),
                                    topRight: Radius.circular(32.0),
                                  ),
                                  color:
                                      const Color(0x00000000).withOpacity(0.00),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                    Transform.translate(
                      offset: Offset(0, -5 * SizeConfig.heightRatio),
                      child: FlutterChatBar(
                        //addIconSize: 20,
                        avatarRadius: 22,
                        avatarColor: Color(0xffffffff),
                        height: ChatBubbleState.chatBarHeight,//62.5, // OG 60
                        width: SizeConfig.screenWidth, // OG 370
                        color: Color(0x00000000),
                        firstChild: ChatBubble(),
                        secondChild: SecondChild(),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class ChatBubble extends StatefulWidget {
  const ChatBubble({
    Key key,
  }) : super(key: key);

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

class ChatBubbleState extends State<ChatBubble> {
  static double chatBarHeight;
  static bool updated = true;

  @override
  Widget build(BuildContext context) {
    final TextEditingController myController = TextEditingController();
    int numLines = 1;
    int charLength = 0;
    chatBarHeight = 62.5;
    //= 52.5;//52.5;
    int lastIndexNewLine = 0;

    //print('before $numLines');
    //builds the bar
    return Padding(
      padding: const EdgeInsets.only(bottom: 5),
      child: Container(
        //margin: EdgeInsets.only(top:13,bottom: 5),
        //color: Colors.blue,
        /*constraints: BoxConstraints(
            maxHeight: 176,
          ),*/
        //height: chatBarHeight,
        height: chatBarHeight,
        width: 310.0 * SizeConfig.widthRatio,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(27.5),
          //color: Colors.blue,
          color: const Color(0xffffffff),
          boxShadow: [
            BoxShadow(
              color: const Color(0x29000000),
              offset: Offset(3, 3),
              blurRadius: 6,
            ),
          ],
        ),
        child:
            /*Padding(
                padding: EdgeInsets.only(
            left: 12 * SizeConfig.widthRatio,
            right: 12 * SizeConfig.widthRatio,
            top: 13 * SizeConfig.heightRatio,
            bottom: 5 * SizeConfig.heightRatio),
                child:*/
            TextFormField(
          expands: true,
          textAlign: TextAlign.start,
          style: TextStyle(
              fontSize: 17 * SizeConfig.textMultiplier,
              color: const Color(0xd9343f4b),
              fontFamily: 'Lato'),
          //textAlign: TextAlign.left,
          maxLines: null,
          minLines: null,
          textCapitalization: TextCapitalization.sentences,
          onChanged: (String e) {
            //String partial =e;
            charLength = e.length;
            numLines = '\n'.allMatches(e).length + 1;
            if (e.contains('\n')) {
              lastIndexNewLine = e.indexOf('\n');
              e = e.substring(lastIndexNewLine);
              charLength = e.length - 1;
            }

            if (charLength > 35) {
              numLines++;
            }
            if (numLines == 1) {
              // chatBarHeight = 52.5;
            }
            if (numLines >= 2) {
              print('I have 2 lines.');
              chatBarHeight = 110;

              updated = true;
              print(chatBarHeight);
              print('uPDATED $updated');
            }
            print('after $numLines');
          },
          controller: myController,
          decoration: InputDecoration.collapsed(
              hintStyle: TextStyle(
                color: Color(0x80343f4b),
                fontFamily: 'Lato',
                fontSize: 15 * SizeConfig.textMultiplier,
                //color: const Color(0xd9343f4b),
              ),
              // hintStyle: ,
              hintText: 'Enter Comment'),
        ),
      ),
      // ), //Padding
      //),
    );
  }
}


class SecondChild extends StatelessWidget {
  const SecondChild({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    return Transform.translate(
      offset:
          Offset(12.5 * SizeConfig.widthRatio, -2.5 * SizeConfig.heightRatio),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          
        ],
      ),
    );
  }
}

 
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';




class FlutterChatBar extends StatelessWidget {

  //Height of Chat Bar widget.
  final double height;

  //Width of chat bar widget.
  final double width;

  //Color of chat bar widget.
  final Color color;

  //Initial child which is to be displayed inside chat bar widget.
  //Ex- Message Container which is displayed (Can be TextField also)
  final Widget firstChild;

  //Child which comes after animation (when we tap on add Icon button)
  //Ex - 3 Icons which are displayed
  final Widget secondChild;

  //Color of add Icon by default it is white.
  final Color addIconColor;

  //Color of Circle avatar which has a child of Add Icon , by default color is white30
  final Color avatarColor;

  //Radius of circle avatar , by default it is 30.0
  final double avatarRadius;

  //Size of add Icon , by default it is 30.0
  final double addIconSize;

  FlutterChatBar(
    {
      @required this.height,
      @required this.width,
      @required this.color,
      @required this.firstChild,
      @required this.secondChild,
      this.addIconColor = Colors.white,
      this.avatarColor = Colors.white30,
      this.avatarRadius = 43.0,
      this.addIconSize = 43.0
    }
  );
  @override
  Widget build(BuildContext context) {
    return Container(
      height: height,
      width: width,
      decoration: BoxDecoration(
          //borderRadius: BorderRadius.circular(27.5), 
          color: color),
      child: ContentWidget(
        firstChild: firstChild,
        secondChild: secondChild,
        color: color,
        addIconColor: addIconColor,
        addIconSize: addIconSize,
        avatarRadius: avatarRadius,
        avatarColor: avatarColor,
      ),
    );
  }
}

class ContentWidget extends StatefulWidget {


  final Widget firstChild;
  final Widget secondChild;
  final Color color;
  final Color addIconColor;
  final Color avatarColor;
  final double addIconSize;
  final double avatarRadius;

  ContentWidget(
      {this.firstChild,
      this.secondChild,
      this.color,
      this.addIconColor,
      this.avatarColor,
      this.addIconSize,
      this.avatarRadius});
  @override
  _ContentWidgetState createState() => _ContentWidgetState();
}

class _ContentWidgetState extends State<ContentWidget>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  //Animation for firstChild (Ex- Message container)
  Animation<double> _firstChildAnimation;

  //Animation for secondChild (Ex- 3 Icons in a row)
  Animation<double> _secondChildAnimation;

  //Animation for add Icon 
  Animation<double> _iconAnimation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(duration: Duration(milliseconds: 500), vsync: this)
          ..addListener(() {
            setState(() {});
          });

    _firstChildAnimation = Tween(begin: pi / 4, end: 0.0).animate(CurvedAnimation(
        parent: _controller,
        curve: Curves.easeOut,
        reverseCurve: Curves.easeIn));

    _secondChildAnimation = Tween(begin: 0.0, end: pi / 4).animate(CurvedAnimation(
        parent: _controller,
        curve: Curves.easeIn,
        reverseCurve: Curves.easeOut));

    _iconAnimation = Tween(begin: 0.0, end: pi / 4).animate(CurvedAnimation(
        parent: _controller,
        curve: Curves.easeOut,
        reverseCurve: Curves.easeIn));
  }

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

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        /*Padding(
          //padding: const EdgeInsets.all(4.0),
          child:*/ 
          Flexible(
            fit: FlexFit.loose,
            flex: 1,
                      child: GestureDetector(
                onTap: () {
                  if (_controller.status == AnimationStatus.completed) {
                    _controller.reverse();
                  } else {
                    _controller.forward();
                  }
                },
                child: Transform.translate(
                  offset: Offset(20,-3), //15
                                  child: Container(
                                    decoration: BoxDecoration(
                                      borderRadius: BorderRadius.circular(27.5),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0x29000000),
                    offset: Offset(3, 3),
                    blurRadius: 6,
                  ),
                ],
              ),
                                    child: CircleAvatar( ///radius 22
                    backgroundColor: widget.avatarColor,
                    radius: widget.avatarRadius,
                    child: Transform.rotate(
                        angle: _iconAnimation.value,
                        child: SvgPicture.string(
              _svg_nznkw8,
              allowDrawingOutsideViewBox: true,
              fit: BoxFit.fill,
            ),),
                  ),
                                  ),
                ),
              ),
          ),
        //),
        Flexible(
flex:7,
                  child: Stack(
            children: <Widget>[
              Transform.scale(
                scale: _firstChildAnimation.value,
                child: widget.firstChild,
              ),
              Transform.scale(
                scale: _secondChildAnimation.value,
                child: widget.secondChild,
              )
            ],
          ),
        ),
        Flexible
        (
          flex: 1,
          fit: FlexFit.loose,
                  child: Transform.translate(
                    offset: Offset(-20,-3),
                                      child: Container(
                                        decoration: BoxDecoration(
                                          borderRadius: BorderRadius.circular(27.5),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0x29000000),
                    offset: Offset(3, 3),
                    blurRadius: 6,
                  ),
                ],
              ),
                                        child: CircleAvatar( ///radius 22
                    backgroundColor: widget.avatarColor,
                    radius: widget.avatarRadius,
                    child: SvgPicture.string(
                      _svg_52blhh,
                      allowDrawingOutsideViewBox: true,
                      fit: BoxFit.fill,
                    ),),
                                      ),
                ),
        ),
      ],
    );
  }
    }
const String _svg_52blhh = //send
    '<svg viewBox="332.0 762.0 20.0 20.0" ><path transform="translate(332.0, 762.0)" d="M 18.59535026550293 0.1261749565601349 L 0.4879408478736877 10.56938934326172 C -0.2191661894321442 10.97555732727051 -0.1293128132820129 11.95973491668701 0.5738875865936279 12.25654983520508 L 4.726676464080811 13.99838733673096 L 15.95053577423096 4.109749794006348 C 16.16540145874023 3.918381690979004 16.47012329101562 4.211291790008545 16.28650856018066 4.433903217315674 L 6.875344276428223 15.89644432067871 L 6.875344276428223 19.04034423828125 C 6.875344276428223 19.96203422546387 7.988744735717773 20.32524108886719 8.535677909851074 19.65740776062012 L 11.01641273498535 16.63848304748535 L 15.88412189483643 18.6771354675293 C 16.43886947631836 18.91146278381348 17.07174873352051 18.56387710571289 17.1733226776123 17.96634101867676 L 19.98612403869629 1.094730377197266 C 20.11895179748535 0.3058263659477234 19.27120399475098 -0.2643715739250183 18.59535026550293 0.1261749565601349 Z" fill="#343f4b" stroke="none" stroke-width="1" stroke-miterlimit="10" stroke-linecap="butt" /></svg>';
const String _svg_nznkw8 =
    '<svg viewBox="24.0 762.0 22.0 22.0" ><path transform="translate(24.0, 762.0)" d="M 20.4285717010498 8.642857551574707 L 13.35714244842529 8.642857551574707 L 13.35714244842529 1.571428537368774 C 13.35714244842529 0.703705370426178 12.65343761444092 0 11.7857141494751 0 L 10.2142858505249 0 C 9.346562385559082 0 8.642857551574707 0.703705370426178 8.642857551574707 1.571428537368774 L 8.642857551574707 8.642857551574707 L 1.571428537368774 8.642857551574707 C 0.703705370426178 8.642857551574707 0 9.346562385559082 0 10.2142858505249 L 0 11.7857141494751 C 0 12.65343761444092 0.703705370426178 13.35714244842529 1.571428537368774 13.35714244842529 L 8.642857551574707 13.35714244842529 L 8.642857551574707 20.4285717010498 C 8.642857551574707 21.29629516601562 9.346562385559082 22 10.2142858505249 22 L 11.7857141494751 22 C 12.65343761444092 22 13.35714244842529 21.29629516601562 13.35714244842529 20.4285717010498 L 13.35714244842529 13.35714244842529 L 20.4285717010498 13.35714244842529 C 21.29629516601562 13.35714244842529 22 12.65343761444092 22 11.7857141494751 L 22 10.2142858505249 C 22 9.346562385559082 21.29629516601562 8.642857551574707 20.4285717010498 8.642857551574707 Z" fill="#343f4b" stroke="none" stroke-width="1" stroke-miterlimit="10" stroke-linecap="butt" /></svg>';

flutter
dart
containers
chat
textformfield
asked on Stack Overflow Oct 30, 2020 by Isis Curiel

1 Answer

1

Mainly the container wasn't expanding is, because you have inserted

final TextEditingController myController = TextEditingController();
int numLines = 1;
int charLength = 0;
chatBarHeight = 62.5;
//= 52.5;//52.5;
int lastIndexNewLine = 0;

inside the build method of ChatBubbleState; this will cause the chatBarHeight to be 62.5 even if you change it inside onChanged property of the textFormField.

Also if you wanted the container to expand, First remove the expands property from the textFormField, this property will expand the textformField to fit it's container not expand while typing and second you don't need the calculation inside the onChanged property.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: ExpandingText(),
  ));
}

class ExpandingText extends StatefulWidget {
  _ExpandingText createState() => _ExpandingText();
}

class _ExpandingText extends State<ExpandingText> {
  TextEditingController myController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          width: 310.0,
          decoration: BoxDecoration(
            color: const Color(0xffffffff),
            boxShadow: [
              BoxShadow(
                color: const Color(0x29000000),
                offset: Offset(3, 3),
                blurRadius: 6,
              ),
            ],
          ),
          child: TextFormField(
            textAlign: TextAlign.start,
            style: TextStyle(
                fontSize: 17,
                color: const Color(0xd9343f4b),
                fontFamily: 'Lato'),
            maxLines: null,
            textCapitalization: TextCapitalization.sentences,
            controller: myController,
            decoration: InputDecoration(
                hintStyle: TextStyle(
                  color: Color(0x80343f4b),
                  fontFamily: 'Lato',
                  fontSize: 15,
                ),
                // hintStyle: ,
                hintText: 'Enter Comment'),
          ),
        ),
      ),
    );
  }
}
answered on Stack Overflow Oct 30, 2020 by Benyam S

User contributions licensed under CC BY-SA 3.0