skip to Main Content

I want to make a "Settings" button in the actions area of ​​the AppBar. I seem to have succeeded, but I can click on this empty container. I want to make it so that Settings is either in the Tab widget but next to the buttons (minimize, close) or is in the actions list, but it was possible to switch between the buttons in the AppBar as in Tab.

AppBar

import 'widgets/custom_appbar.dart';
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'constants.dart';
import 'widgets/main_tab/connect_to_server.dart';
import 'widgets/main_tab/create_server.dart';

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

  @override
  State<CoopAndreasLauncher> createState() => _CoopAndreasLauncherState();
}

class _CoopAndreasLauncherState extends State<CoopAndreasLauncher> with TickerProviderStateMixin {
  final TextEditingController nicknameController = TextEditingController();
  final TextEditingController ipPortController = TextEditingController();
  int maxPlayers = 2;
  String selectedLanguage = 'English';

  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(initialIndex: 0, vsync: this, length: 3);
  }

  @override
  void dispose() {
    nicknameController.dispose();
    ipPortController.dispose();
    _tabController.dispose();
    super.dispose();
  }

  late final List<IconButton> _actions = [
    IconButton(
      icon: const Icon(FontAwesomeIcons.gear),
      iconSize: 20.0,
      onPressed: () {
        _tabController.animateTo(2); // this line goto tab with index 2
      },
    ),
    IconButton(
      icon: const Icon(Icons.minimize),
      iconSize: 20.0,
      onPressed: () {
        windowManager.minimize();
      },
    ),
    IconButton(
      icon: const Icon(Icons.close),
      iconSize: 20.0,
      onPressed: () {
        windowManager.close();
      },
    )
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      initialIndex: _tabController.index,
      length: _tabController.length,
      child: Scaffold(
        appBar: CustomAppBar(
          actions: _actions,
          title: TabBar(
            labelColor: Colors.white,
            controller: _tabController,
            indicatorColor: Colors.transparent,
            dividerColor: Colors.transparent,
            splashBorderRadius: const BorderRadius.all(Radius.circular(10.0)),
            tabAlignment: TabAlignment.center,
            tabs: [
              const Tab(
                  icon: Icon(FontAwesomeIcons.plug, size: 21.0),
                  child: Text(
                      "Connect",
                      style: tabTextStyle,
                  )
              ),
              const Tab(
                  icon: Icon(FontAwesomeIcons.server, size: 21.0),
                  child: Text(
                    "Server",
                    style: tabTextStyle,
                  )
              ),
              Container() // tab index 2
            ]
          )
        ),
        body: TabBarView(
          controller: _tabController,
          children: [
            ConnectToServerTab(nicknameController: nicknameController, ipPortController: ipPortController),
            CreateServerTab(nicknameController: nicknameController, ipPortController: ipPortController, maxPlayers: maxPlayers, onMaxPlayersChanged: (value) {
              setState(() {
                maxPlayers = value;
              });
            }),
            // Settings
          ]
        )
      ),
    );
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    In this zone when i clicked Tab didn't work.

    enter image description here


  2. You suggested 2 solutions

    Approach 1: Make settings a tab

    TabBar design limitations made this approach impractical. To achieve this, it would have required something a Spacer but it’s not possible to use it so the other solution would be calculating manual widths to separate Settings from other tab Server. This would be overly complicated and code would become ugly.

    Approach 2: Make settings an action item but behave like a tab

    This was a tricky solution because when you only have one TabController that is shared between TabBar and TabBarView with a different number of items, trying to navigate to a non-existent index in the TabBar will display the content but cause the TabBar to crash.

    To address this, I used two TabControllers: one for the TabBar and another for the TabBarView.

    Next, the tabs in TabBar were wrapped in GestureDetector. Now, when any tab is clicked, we run:

    _tabController2.animateTo(0); // or 1 based on tab 
    _tabController.animateTo(0); // or 1 based on tab 
    if (_selectedColor != Colors.red) {
        _selectedColor = Colors.red;
        setState(() {});
    }
    

    This changes the state of both tab controllers and also updates the color of the selected tab. The reason for changing the color is that when the settings icon button is clicked, _tabController2 changes to index 2, but _tabController remains at index 1, meaning it would still appear selected. Since TabBar must have at least one selected tab, we change the selected color to black to make it look unselected. In this case, we want Settings to be highlighted instead.

    Also to make the tabs not take the whole width to look like in image I needed to set isScrollable: true for TabBar.

    Anyway it’s tricky solution check the code below, just copy paste it to your main.dart. I had to replace the missing widgets with custom ones and comment out some styles and window management plugin

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const CoopAndreasLauncherApp());
    }
    
    class CoopAndreasLauncherApp extends StatelessWidget {
      const CoopAndreasLauncherApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CoopAndreasLauncher(),
        );
      }
    }
    
    class CoopAndreasLauncher extends StatefulWidget {
      const CoopAndreasLauncher({super.key});
    
      @override
      State<CoopAndreasLauncher> createState() => _CoopAndreasLauncherState();
    }
    
    class _CoopAndreasLauncherState extends State<CoopAndreasLauncher>
        with TickerProviderStateMixin {
      final TextEditingController nicknameController = TextEditingController();
      final TextEditingController ipPortController = TextEditingController();
      int maxPlayers = 2;
      String selectedLanguage = 'English';
    
      late TabController _tabController;
      late TabController _tabController2;
    
      Color _selectedColor = Colors.red;
    
      @override
      void initState() {
        super.initState();
        _tabController = TabController(initialIndex: 0, vsync: this, length: 2);
        _tabController2 = TabController(initialIndex: 0, vsync: this, length: 3);
      }
    
      @override
      void dispose() {
        nicknameController.dispose();
        ipPortController.dispose();
        _tabController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return DefaultTabController(
          initialIndex: _tabController.index,
          length: _tabController.length,
          child: Scaffold(
            appBar: AppBar(
              actions: [
                IconButton(
                  icon: Icon(
                    FontAwesomeIcons.gear,
                    color: _tabController2.index == 2 ? Colors.red : Colors.black,
                  ),
                  iconSize: 20.0,
                  onPressed: () {
                    _tabController2
                        .animateTo(2); // this line goes to tab with index 2
    
                    if (_tabController2.index == 2) {
                      _selectedColor = Colors.black;
                    } else {
                      _selectedColor = Colors.red;
                    }
                    setState(() {});
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.minimize),
                  iconSize: 20.0,
                  onPressed: () {
                    // windowManager.minimize();
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.close),
                  iconSize: 20.0,
                  onPressed: () {
                    // windowManager.close();
                  },
                )
              ],
              title: TabBar(
                labelColor: _selectedColor,
                controller: _tabController,
                indicatorColor: Colors.transparent,
                isScrollable: true,
                padding: EdgeInsets.zero,
                labelPadding: EdgeInsets.zero,
                indicatorPadding: EdgeInsets.zero,
                tabAlignment: TabAlignment.start,
                tabs: [
                  GestureDetector(
                    behavior: HitTestBehavior.translucent,
                    onTap: () {
                      _tabController2.animateTo(0);
                      _tabController.animateTo(0);
                      if (_selectedColor != Colors.red) {
                        _selectedColor = Colors.red;
                        setState(() {});
                      }
                    },
                    child: const Padding(
                      padding: EdgeInsets.fromLTRB(8, 16, 8, 16),
                      child: Tab(
                        icon: Icon(FontAwesomeIcons.plug, size: 21.0),
                        child: Text(
                          "Connect",
                          // style: tabTextStyle,
                        ),
                      ),
                    ),
                  ),
                  GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    onTap: () {
                      _tabController2.animateTo(1);
                      _tabController.animateTo(1);
                      if (_selectedColor != Colors.red) {
                        _selectedColor = Colors.red;
                        setState(() {});
                      }
                    },
                    child: const Padding(
                      padding: EdgeInsets.fromLTRB(8, 16, 8, 16),
                      child: Tab(
                        icon: Icon(FontAwesomeIcons.server, size: 21.0),
                        child: Text(
                          "Server",
                          // style: tabTextStyle,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            body: TabBarView(
              controller: _tabController2,
              children: [
                ConnectToServerTab(
                    nicknameController: nicknameController,
                    ipPortController: ipPortController),
                CreateServerTab(
                  nicknameController: nicknameController,
                  ipPortController: ipPortController,
                  maxPlayers: maxPlayers,
                  onMaxPlayersChanged: (value) {
                    setState(() {
                      maxPlayers = value;
                    });
                  },
                ),
                // Placeholder for Settings
                const Center(
                  child: Text(
                    'Settings Page',
                    style: TextStyle(fontSize: 24),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class ConnectToServerTab extends StatelessWidget {
      final TextEditingController nicknameController;
      final TextEditingController ipPortController;
    
      const ConnectToServerTab({
        super.key,
        required this.nicknameController,
        required this.ipPortController,
      });
    
      @override
      Widget build(BuildContext context) {
        return const Center(
          child: Text(
            'Connect to Server Page',
            style: TextStyle(fontSize: 24),
          ),
        );
      }
    }
    
    class CreateServerTab extends StatelessWidget {
      final TextEditingController nicknameController;
      final TextEditingController ipPortController;
      final int maxPlayers;
      final ValueChanged<int> onMaxPlayersChanged;
    
      const CreateServerTab({
        super.key,
        required this.nicknameController,
        required this.ipPortController,
        required this.maxPlayers,
        required this.onMaxPlayersChanged,
      });
    
      @override
      Widget build(BuildContext context) {
        return const Center(
          child: Text(
            'Create Server Page',
            style: TextStyle(fontSize: 24),
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search