Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Column scrollable when overflowed but use expanded otherwise

I am trying to achieve an effect where there is expandable content on the top end of a sidebar, and other links on the bottom of the sidebar. When the content on the top expands to the point it needs to scroll, the bottom links should scroll in the same view.

Here is an example of what I am trying to do, except that it does not scroll. If I wrap a scrollable view around the column, that won't work with the spacer or expanded that is needed to keep the bottom links on bottom:

import 'package:flutter/material.dart';

const 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: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() {
    return MyWidgetState();
  }
}

class MyWidgetState extends State<MyWidget> {
  List<int> items = [1];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              icon: const Icon(Icons.add),
              onPressed: () {
                setState(() {
                  items.add(items.last + 1);
                });
              },
            ),
            IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () {
                setState(() {
                  if (items.length != 1) items.removeLast();
                });
              },
            ),
          ],
        ),
        for (final item in items)
          MyAnimatedWidget(
            child: SizedBox(
              height: 200,
              child: Center(
                child: Text('Top content item $item'),
              ),
            ),
          ),
        Spacer(),
        Container(
          alignment: Alignment.center,
          decoration: BoxDecoration(border: Border.all()),
          height: 200,
          child: Text('Bottom content'),
        )
      ],
    );
  }
}

class MyAnimatedWidget extends StatefulWidget {
  final Widget? child;

  const MyAnimatedWidget({this.child, Key? key}) : super(key: key);

  @override
  State<MyAnimatedWidget> createState() {
    return MyAnimatedWidgetState();
  }
}

class MyAnimatedWidgetState extends State<MyAnimatedWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;

  @override
  initState() {
    controller = AnimationController(
        value: 0, duration: const Duration(seconds: 1), vsync: this);
    controller.animateTo(1, curve: Curves.linear);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: controller,
        builder: (context, child) {
          return SizedBox(height: 200 * controller.value, child: widget.child);
        });
  }
}

I have tried using a global key to get the size of the spacer and detect after rebuilds whether the spacer has been sized to 0, and if so, re-build the entire widget as a list view (without the spacer) instead of a column. You also need to listen in that case for if the size shrinks and it needs to become a column again, it seemed to make the performance noticeably worse, it was tricky to save the state when switching between column/listview, and it seemed not the best way to solve the problem.

Any ideas?

like image 266
Kris Avatar asked Oct 20 '25 11:10

Kris


1 Answers

Try implementing this solution I've just created without the animation you have. Is a scrollable area at the top and a persistent footer.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(
            title: Text("My AppBar"),
          ),
          body: Column(
            children: [
              Expanded(
                child: SingleChildScrollView(
                  child: Column(
                    children: [

                      // Your scrollable widgets here

                      Container(
                        height: 100,
                        color: Colors.green,
                      ),
                      Container(
                        height: 100,
                        color: Colors.blue,
                      ),
                      Container(
                        height: 100,
                        color: Colors.red,
                      ),
                    ],
                  ),
                ),
              ),
              Container(
                child: Text(
                  'Your footer',
                ),
                color: Colors.blueGrey,
                height: 200,
                width: double.infinity,
              )
            ],
          ),
        ),
      ),
    );
  }
}
like image 142
Thomas Viana Avatar answered Oct 23 '25 02:10

Thomas Viana