Flutter

[Flutter] Sidebar, Side Navigation 구현하기 (sidebarx)

izongg 2024. 2. 5. 19:48
반응형

 

flutter에서 좌측에 고정된 사이드바를 구현하였다.

일반 모바일폰이 아닌 태블릿이나 웹에서 사용하기 용이하다.

모바일폰에서는 sidebar 보다는 drawer을 많이 사용하는 것 같다.

 


패키지 설치

 flutter pub add sidebarx

 

 

전체 예제 코드

공식 문서에 있는 example이다. 

주석에도 간단하게 설명되어있지만 아래에서 주요 파라미터만 뽑아서 다시 정리하였다.

import 'package:flutter/material.dart';
import 'package:sidebarx/sidebarx.dart';

void main() {
  runApp(NavHome());
}

class NavHome extends StatelessWidget {
  NavHome({Key? key}) : super(key: key);

  // 내가 어느 메뉴를 클릭했고, 현재 사이드바가 펼쳐져있는지 여부를 관리하는 컨트롤러
  final _controller = SidebarXController(selectedIndex: 0, extended: true);
  final _key = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SidebarX Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        // 뭐가바뀌는지 모르겠음
        primaryColor: primaryColor,
        // 사이드바 배경 컬러
        canvasColor: canvasColor,
        // 우측 화면 배경컬러
        scaffoldBackgroundColor: scaffoldBackgroundColor,

        // 우측 화면에 있는 텍스트 스타일
        // 타이틀에 대한 텍스트 스타일인거같음.
        textTheme: const TextTheme(
          headlineSmall: TextStyle(
            color: Colors.white,
            fontSize: 46,
            fontWeight: FontWeight.w800,
          ),
        ),
      ),
      home: Builder(
        builder: (context) {
          // 기기가 일반 스마트폰인지 태블릿인지 여부
          final isSmallScreen = MediaQuery.of(context).size.width < 600;
          return Scaffold(
            key: _key,
            // 기기가 일반 스마트폰인지 태블릿인지 여부에 따라 앱바 다르게 보이게 함
            appBar: isSmallScreen
                ? AppBar(
                    backgroundColor: canvasColor,
                    title: Text(_getTitleByIndex(_controller.selectedIndex)),
                    leading: IconButton(
                      onPressed: () {
                        // if (!Platform.isAndroid && !Platform.isIOS) {
                        //   _controller.setExtended(true);
                        // }
                        _key.currentState?.openDrawer();
                      },
                      icon: const Icon(Icons.menu),
                    ),
                  )
                // 태블릿일 경우 아래 null 부분 실행
                : null,
            drawer: ExampleSidebarX(controller: _controller),
            body: Row(
              children: [
                // 일반 스마트폰의 경우 사이드바가 아님. 
                if (!isSmallScreen) ExampleSidebarX(controller: _controller),
                // 우측의 화면
                Expanded(
                  child: Center(
                    child: _ScreensExample(
                      controller: _controller,
                    ),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

class ExampleSidebarX extends StatelessWidget {
  const ExampleSidebarX({
    Key? key,
    required SidebarXController controller,
  })  : _controller = controller,
        super(key: key);

  final SidebarXController _controller;

  @override
  Widget build(BuildContext context) {
    return SidebarX(
      controller: _controller,
      theme: SidebarXTheme(
        margin: const EdgeInsets.all(10),
        decoration: BoxDecoration(
          color: canvasColor,
          borderRadius: BorderRadius.circular(20),
        ),
        hoverColor: scaffoldBackgroundColor,
        textStyle: TextStyle(color: Colors.white.withOpacity(0.7)),
        selectedTextStyle: const TextStyle(color: Colors.white),
        itemTextPadding: const EdgeInsets.only(left: 30),
        selectedItemTextPadding: const EdgeInsets.only(left: 30),
        itemDecoration: BoxDecoration(
          borderRadius: BorderRadius.circular(10),
          border: Border.all(color: canvasColor),
        ),
        selectedItemDecoration: BoxDecoration(
          borderRadius: BorderRadius.circular(10),
          border: Border.all(
            color: actionColor.withOpacity(0.37),
          ),
          gradient: const LinearGradient(
            colors: [accentCanvasColor, canvasColor],
          ),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.28),
              blurRadius: 30,
            )
          ],
        ),
        iconTheme: IconThemeData(
          color: Colors.white.withOpacity(0.7),
          size: 20,
        ),
        selectedIconTheme: const IconThemeData(
          color: Colors.white,
          size: 20,
        ),
      ),

      extendedTheme: const SidebarXTheme(
        width: 200,
        decoration: BoxDecoration(
          color: canvasColor,
        ),
      ),
      // 사이드바 하단에 divider, 아래에 열고닫는 버튼이 있음
      footerDivider: divider,
      // 사이드바의 젤 상단에 나오는 텍스트
      headerBuilder: (context, extended) {
        return SizedBox(
          height: 100,
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Text("ㅁㅁㅁ"),
          ),
        );
      },

      //사이드바의 메뉴 설정.
      // label이 메뉴의 이름을 나타냄
      items: [
        SidebarXItem(
          icon: Icons.home,
          label: 'Home',
          onTap: () {
            debugPrint('Home');
          },
        ),
        const SidebarXItem(
          icon: Icons.search,
          label: 'Search',
        ),
        const SidebarXItem(
          icon: Icons.people,
          label: 'People',
        ),
        const SidebarXItem(
          icon: Icons.favorite,
          label: 'Favorites',
        ),
        const SidebarXItem(
          iconWidget: FlutterLogo(size: 20),
          label: 'Flutter',
        ),
      ],
    );
  }
}

class _ScreensExample extends StatelessWidget {
  const _ScreensExample({
    Key? key,
    required this.controller,
  }) : super(key: key);

  final SidebarXController controller;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        final pageTitle = _getTitleByIndex(controller.selectedIndex);
        switch (controller.selectedIndex) {
          // 여기서 사이드바에서 메뉴를 클릭했을 때 달라지는 우측의 화면 지정해주는 곳.
          // ex) case 1일땐 어느화면 보여주고, case 2 일땐 어느 화면 보여주고..
          case 0:
            return ListView.builder(
              padding: const EdgeInsets.only(top: 10),
              itemBuilder: (context, index) => Container(
                height: 100,
                width: double.infinity,
                margin: const EdgeInsets.only(bottom: 10, right: 10, left: 10),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(20),
                  color: Theme.of(context).canvasColor,
                  boxShadow: const [BoxShadow()],
                ),
              ),
            );
          // case 2:
          // return Text("화면 분기 테스트");

          default:
            return Text(
              pageTitle,
              style: theme.textTheme.headlineSmall,
            );
        }
      },
    );
  }
}

// 새로운 화면을 추가하거나 제거할 때 아래 index를 통해 설정하고 사용
String _getTitleByIndex(int index) {
  switch (index) {
    case 0:
      return 'Home';
    case 1:
      return 'Search';
    case 2:
      return 'People';
    case 3:
      return 'Favorites';
    case 4:
      return 'Custom iconWidget';
    case 5:
      return 'Profile';
    case 6:
      return 'Settings';
    default:
      return 'Not found page';
  }
}

const primaryColor = Color(0xFF685BFF);
const canvasColor = Color(0xFF2E2E48);
const scaffoldBackgroundColor = Color(0xFF464667);
const accentCanvasColor = Color(0xFF3E3E61);
const white = Colors.white;
final actionColor = const Color(0xFF5F5FA7).withOpacity(0.6);
final divider = Divider(color: white.withOpacity(0.3), height: 1);

 

주요 파라미터

Sidearx Class 

showToggleButton : 사이드 바를 축소하거나 확장하는 버튼을 보이게 할지 여부 (bool)

items : 메뉴 아이템을 위에서부터 정렬하여 배치. -> 제일 일반적으로 사용함. (List<SidebarXItem>)

footerItems : 메뉴 아이템을 하단에 배치. (List<SidebarXItem>) 

 

SidebarXTheme Class

textStyle : 선택되지 않은 메뉴 텍스트 스타일

selectedTextstyle : 선택된 메뉴 텍스트 스타일

extendedTheme : 사이드바를 펼쳤을 때의 사이드바 너비, 배경 관련 boxdecoration 설정

itemMargin, itemPadding : 선택되지않은 아이템 margin과 padding

selectedItemMargin, selectedItemPadding  : 선택된 아이템에 대한 margin 과 padding

selectedItemTextPadding : 선택되었을 때 메뉴 텍스트에 대한 padding

 

 

return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        final pageTitle = _getTitleByIndex(controller.selectedIndex);
        switch (controller.selectedIndex) {
          case 0:
          	return Text("두번째 화면");
          case 1:
         	 return Text("두번째 화면");
          case 2:
          	return Text("세번째 화면");
          case 3:
          	return Text("네번째 화면");
          default:
            return Text(
              pageTitle,
              style: theme.textTheme.headlineSmall,
            );
        }
      },
    );

 

items와 footerItems 리스트에 들어가있는 index 순서대로, 메뉴 버튼을 눌렀을 때 우측에 return 위젯을 불러옴.

 

참고자료

https://pub.dev/packages/sidebarx

반응형