skip to Main Content

I’m pretty new to Flutter, and I wanted to create a folder effect with tabs for my portfolio website where you click on the tabs to change what displayed in the center of the folder. Right now the buttons are dynamically generated as children of a row widget. The problem is that at smaller screen widths the text is either too small or gets cut off altogether. I even tried to figure out how to do a sort of multi-line row but gave up.

What I would ideally like is that the buttons wrap such that any buttons that would make the parent too long are placed on a separate row. However, I’m open to any solution that allows all the tabs to fit on screen without the text in the buttons being super shrunk.

My current solutions as you can see is to just scale the text down at smaller screen widths so that its all at least visible. I’m guessing that I’m either going to have redesign the way I implement the effect, or there’s some relatively simple solution that I’m not aware of. I also tried replacing the Row widget that wraps the FolderButtons to a Wrap with no other modifications and that just caused the text to disappear.

Note: The way its currently implemented assumes and depends upon the folder being a perfect square.

Below are Folder, FolderButton, and ScaleSize classes respectively:

class Folder extends StatefulWidget {
  const Folder({super.key});

  static Column? getStaticPages(String page, BuildContext context) {
    Map<String, Column> pages = {
      "Welcome": Column(
        children: [
          Text(
            'Welcome',
            style: TextStyle(color: Colors.deepPurpleAccent.shade100),
          )
        ],
      ),
      "Web Dev's Handbook": Column(
        children: [
          Text(
            "Web Dev's Handbook",
            style: TextStyle(color: Colors.deepPurpleAccent.shade100),
          ),
          TextButton(
              onPressed: () => {
                    Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => const Contents()))
                  },
              child: const Text("Go"))
        ],
      ),
      "Interactive Resume": Column(
        children: [
          Text(
            'Interactive Resume',
            style: TextStyle(color: Colors.deepPurpleAccent.shade100),
          )
        ],
      ),
      "Settings": Column(
        children: [
          Text(
            'Settings',
            style: TextStyle(color: Colors.deepPurpleAccent.shade100),
          )
        ],
      ),
      "Credits": Column(
        children: [
          Text(
            'Credits',
            style: TextStyle(color: Colors.deepPurpleAccent.shade100),
          )
        ],
      ),
    };

    return pages[page];
  }

  static List<Map<String, dynamic>> staticTabs = [
    {"title": "Welcome"},
    {"title": "Web Dev's Handbook"},
    {"title": "Interactive Resume"},
    {"title": "Settings"},
    {"title": "Credits"},
  ];

  static List<FolderButton> generateTabs(int selectedTab, Function setTab) {
    List<FolderButton> newTabs = [];

    for (int x = 0; x < staticTabs.length; x++) {
      bool selected;

      if (selectedTab == x) {
        selected = true;
      } else {
        selected = false;
      }

      newTabs.add(FolderButton(
          title: staticTabs[x]["title"],
          count: x,
          selected: selected,
          setTab: setTab));
    }

    return newTabs;
  }

  @override
  State<Folder> createState() => _FolderState();
}

class _FolderState extends State<Folder> {
  int _selectedTab = 0;

  void _setTab(int count) {
    setState(() {
      _selectedTab = count;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.only(left: 1, right: 1, top: 20),
        child: SizedBox(
          height: 750,
          width: 750,
          child: Column(
            children: [
              Flexible(
                flex: 1,
                child: Padding(
                  padding: const EdgeInsets.only(left: 7.0),
                  child: Row(
                    children: Folder.generateTabs(_selectedTab, _setTab),
                  ),
                ),
              ),
              Flexible(
                flex: 15,
                fit: FlexFit.tight,
                child: Container(
                  decoration: BoxDecoration(
                      color: Colors.deepPurpleAccent,
                      borderRadius: BorderRadius.circular(5)),
                  child: Padding(
                    padding: const EdgeInsets.all(5.0),
                    child: Container(
                        width: 800,
                        decoration:
                            const BoxDecoration(color: Colors.deepPurple),
                        child: Folder.getStaticPages(
                            Folder.staticTabs[_selectedTab]["title"], context)),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
class FolderButton extends StatefulWidget {
  const FolderButton(
      {super.key,
      required this.title,
      required this.count,
      this.selected = false,
      required this.setTab});

  final String title;
  final int count;
  final bool selected;
  final Function setTab;

  static final theme = <String, dynamic>{
    "button": <String, dynamic>{
      "picked": <bool, ButtonStyle>{
        true: TextButton.styleFrom(
            backgroundColor: Colors.deepPurple,
            shape: const RoundedRectangleBorder(
                borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(5), topRight: Radius.circular(5)),
                side: BorderSide(
                    color: Colors.deepPurpleAccent,
                    strokeAlign: StrokeAlign.outside))),
        false: TextButton.styleFrom(
          backgroundColor: Colors.deepPurple,
          shape: const RoundedRectangleBorder(
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(5), topRight: Radius.circular(5)),
              side: BorderSide(
                  color: Colors.deepPurple, strokeAlign: StrokeAlign.outside)),
        ),
      }
    },
    // TODO Make it so I don't need to do it like this
    "padding": <bool, dynamic>{
      true: const EdgeInsets.only(top: 3, left: 3, right: 3),
      false: const EdgeInsets.only(top: 3, left: 3, right: 3)
    }
  };

  static Color? getTabShading(selected) {
    if (selected) {
      return Colors.deepPurpleAccent;
    }
    return Colors.deepPurple;
  }

  static EdgeInsetsGeometry getTabPadding(selected) {
    return theme["padding"][selected];
  }

  @override
  State<FolderButton> createState() => _FolderButtonState();
}

class _FolderButtonState extends State<FolderButton> {
  void changeSelected() {}

  @override
  Widget build(BuildContext context) {
    return Flexible(
      child: Container(
        height: 100,
        // Button Container
        decoration: BoxDecoration(
            // Container Decorations
            color: FolderButton.getTabShading(widget.selected),
            borderRadius: const BorderRadius.only(
              topLeft: Radius.circular(5),
              topRight: Radius.circular(5),
            )),
        // Button Padding
        child: Padding(
          padding: FolderButton.getTabPadding(widget.selected),
          // Button
          child: TextButton(
              onPressed: () {
                widget.setTab(widget.count);
              },
              // Style of button itself
              style: FolderButton.theme["button"]?["picked"][widget.selected],
              child: Text(
                  textAlign: TextAlign.center,
                  textScaleFactor: ScaleSize.textScaleFactor(context,
                      maxTextScaleFactor: 1.5),
                  // Text of the button
                  widget.title,
                  style: TextStyle(
                      color: Colors.deepPurpleAccent.shade100,
                      fontSize: 10,
                      height: 1))),
        ),
      ),
    );
  }
}
class ScaleSize {
  static double textScaleFactor(BuildContext context,
      {double maxTextScaleFactor = 2}) {
    final width = MediaQuery.of(context).size.width;
    double val = (width / 1400) * maxTextScaleFactor;
    return max(1, min(val, maxTextScaleFactor));
  }
}

Any general flutter tips, tricks, conventions, and/or all-around good ideas are appreciated.

3

Answers


  1. Chosen as BEST ANSWER

    The answer is simply to rebuild the component with Wrap as the parent to the Buttons as it wrap overflowing children below the rest. I will also be sure to maintain a bit more forethought in terms of responsive layout design while making use of the responsive framework package.


  2. Perhaps Wrap widget is what you need. It works just like a Row until you reach the limit of the horizontal size, and then places the next widget in a new "Row" below the previous.

    Login or Signup to reply.
  3. Try using Expanded as a child for Row/Column or whatever you need there. I’m giving you a brief description from the flutter page. I think that for text that does not have a specific width it will be good.

    https://api.flutter.dev/flutter/widgets/Expanded-class.html

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search