본문 바로가기
Flutter + Dart/Flutter + Dart 공부

한 파일 내에서 화면 전환하기

by GREEN나무 2025. 5. 12.
728x90

다른 파일의 화면으로 전환하기

📱 Flutter 한 파일 내 화면 전환 방법 (NavigationRail 활용)


✅ 1. 화면 전환이란?

앱에는 여러 화면이 있습니다. 예를 들어:

  • 🏠 Home 화면 (단어 생성기)
  • ❤️ Favorites 화면 (즐겨찾기 목록)

이 두 화면을 버튼이나 메뉴를 눌렀을 때 바뀌게 하고 싶습니다. 이것이 "화면 전환"입니다.


✅ 2. 상태를 관리할 변수 만들기

눌렀을 때 화면전환을 할 수 있는 버튼이나 메뉴를 구현할 class 안에

어느 화면을 보여줄지 기억하기 위해 selectedIndex라는 숫자 변수를 사용합니다.

var selectedIndex = 0; // 기본은 0: Home 화면
  • 0이면 Home 화면
  • 1이면 Favorites 화면
  @override
  Widget build(BuildContext context) {
    /// 추가
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage(); // 기본 화면 구현 함수
        break;
      case 1:
        page = FavoritesList(); // 즐겨찾기 화면 구현 함수
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }
    
    return ...

✅ 3. NavigationRail 만들기 (좌측 메뉴)

class의 return 안에 위치

NavigationRail(
  selectedIndex: selectedIndex, // 현재 선택된 항목
  onDestinationSelected: (value) {
    setState(() {
      selectedIndex = value; // 사용자가 선택한 항목의 인덱스
    });
  },
  destinations: [
    NavigationRailDestination(
      icon: Icon(Icons.home),
      label: Text('Home'),
    ),
    NavigationRailDestination(
      icon: Icon(Icons.favorite),
      label: Text('Favorites'),
    ),
  ],
),
  • 메뉴를 클릭하면 onDestinationSelected가 실행됨
  • 그때 몇 번째 항목을 눌렀는지 value로 전달됨 ( 배열 destinations의 index 값이 value가 됩니다.)
  • selectedIndex = value로 값이 바뀌면서 화면도 바뀜

✅ 4. selectedIndex에 따라 화면 변경하기

이 page 위젯을 Scaffold의 body에 넣습니다:

Expanded(
  child: Container(
    color: Theme.of(context).colorScheme.primaryContainer,
    child: page, // 선택된 화면 보여주기
  ),
)

✅ 5. 전체 구조 요약

@override
Widget build(BuildContext context) {
  Widget page;
  switch (selectedIndex) {
    case 0:
      page = GeneratorPage();
      break;
    case 1:
      page = FavoritesList();
      break;
  }

  return Scaffold(
    body: Row(
      children: [
        NavigationRail(
          selectedIndex: selectedIndex,
          onDestinationSelected: (value) {
            setState(() {
              selectedIndex = value;
            });
          },
          destinations: [
            NavigationRailDestination(
              icon: Icon(Icons.home),
              label: Text('Home'),
            ),
            NavigationRailDestination(
              icon: Icon(Icons.favorite),
              label: Text('Favorites'),
            ),
          ],
        ),
        Expanded(child: page),
      ],
    ),
  );
}

✅ 정리

항목  설명
selectedIndex 현재 선택된 화면을 나타냄 (0=Home, 1=Favorites)
NavigationRail 왼쪽 메뉴 (아이콘/라벨)
onDestinationSelected 메뉴 클릭 시 호출되어 selectedIndex 변경
switch(selectedIndex) 현재 index에 맞는 화면을 보여줌

🧠 추가 팁

  • 모바일에서 좌측 메뉴(NavigationRail)가 불편하면 BottomNavigationBar로 바꾸는 것도 고려할 수 있습니다.
  • 화면 전환용 상태(selectedIndex)를 Provider로 관리하면 다른 위젯에서도 쉽게 바꿀 수 있습니다.

main.dart 전문

더보기
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(
              // 앱 기본색상
              seedColor: const Color.fromARGB(160, 119, 214, 238)),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
  // ↓ 버튼을 상태에 연결하기. getNext 메서드를 추가.
  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }

  /// ↓ favorites 속성 추가
  final List<WordPair> _favorites = [];
  // var favorites = <WordPair>[];

  void toggleFavorite() {
    if (_favorites.contains(current)) {
      _favorites.remove(current);
    } else {
      _favorites.add(current); // 즐겨찾기 단어 추가
    }
    notifyListeners();
  }

  ///
  void removeFavorite(WordPair pair) {
    _favorites.remove(pair);
    notifyListeners();
  }

  // ✅ 외부 접근용 getter 추가
  List<WordPair> get favorites => _favorites;
}

///
// MyHomePage 클래스는 앱의 메인 화면을 정의하는 StatelessWidget
class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0; // 선택된 네비게이션 항목 인덱스 초기값(0 = Home)
  @override
  Widget build(BuildContext context) {
    /// 추가
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = FavoritesList(); // Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    ///

    // Scaffold: 앱의 기본 구조를 제공하는 위젯 (앱바, 바디, 네비게이션 등)
    return LayoutBuilder(// 여기 수정
        builder: (context, constraints) {
      // 여기 수정
      return Scaffold(
        body: Row(
          children: [
            // SafeArea: 화면의 노치나 상태바 영역을 피해서 콘텐츠를 표시
            SafeArea(
              child: NavigationRail(
                // extended: false, // 네비게이션 레일이 확장되지 않은 컴팩트 모드
                extended: constraints.maxWidth >= 600, // ← 600px 이상일 때 확장(true)
                // destinations: 네비게이션 항목 리스트
                destinations: [
                  // NavigationRailDestination: 네비게이션 항목 정의
                  NavigationRailDestination(
                    icon: Icon(Icons.home), // 홈 아이콘
                    label: Text('Home'), // 홈 라벨
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite), // 즐겨찾기 아이콘
                    label: Text('Favorites'), // 즐겨찾기 라벨
                  ),
                ],
                selectedIndex: selectedIndex, // ← Change to this.
                onDestinationSelected: (value) {
                  // onDestinationSelected: 네비게이션 항목 선택 시 호출되는 콜백
                  setState(() {
                    selectedIndex = value;
                  });
                },
              ),
            ),
            // Expanded: 남은 공간을 채우는 위젯
            Expanded(
              child: Container(
                // 컨테이너의 배경색을 테마의 primaryContainer 색상으로 설정
                color: Theme.of(context).colorScheme.primaryContainer,
                // GeneratorPage: 메인 콘텐츠를 표시하는 위젯
                child: page, // ← 코드 수정
                // child: GeneratorPage(),
              ),
            ),
          ],
        ),
      );
    });
  }
}

// GeneratorPage 클래스는 메인 콘텐츠를 표시하는 StatelessWidget
class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // context.watch: MyAppState의 상태를 감시하여 변경 시 UI 갱신
    var appState = context.watch<MyAppState>();
    var pair = appState.current; // 현재 표시할 데이터 (예: 단어 쌍)

    // 아이콘 설정: 즐겨찾기 여부에 따라 아이콘 변경
    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite; // 즐겨찾기에 포함된 경우 채워진 하트
    } else {
      icon = Icons.favorite_border; // 포함되지 않은 경우 빈 하트
    }

    // Center: 자식 위젯을 화면 중앙에 배치
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center, // 세로축 중앙 정렬
        children: [
          // BigCard: pair 데이터를 표시하는 사용자 정의 위젯
          BigCard(pair: pair),
          SizedBox(height: 10), // 위젯 간 10px 간격
          // Row: 버튼들을 가로로 배치
          Row(
            mainAxisSize: MainAxisSize.min, // Row 크기를 자식 크기에 맞춤
            children: [
              // ElevatedButton.icon: 아이콘과 텍스트가 포함된 버튼
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite(); // 즐겨찾기 토글 함수 호출
                },
                icon: Icon(icon), // 동적 아이콘 표시
                label: Text('Like'), // 버튼 텍스트
              ),
              SizedBox(width: 10), // 버튼 간 10px 간격
              // ElevatedButton: 다음 항목으로 이동하는 버튼
              ElevatedButton(
                onPressed: () {
                  appState.getNext(); // 다음 데이터로 이동
                },
                child: Text('Next'), // 버튼 텍스트
              ),
            ],
          ),
        ],
      ),
    );
  }
}

///
class BigCard extends StatelessWidget {
  const BigCard({
    super.key,
    required this.pair,
  });

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    // ↓ 코드 추가
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
      fontWeight: FontWeight.bold,
      letterSpacing: 2,
    );

    return Card(
      color: theme.colorScheme.primary,
      elevation: 8, // ← 그림자 깊이
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16), // ← 둥근 모서리
      ),
      child: Padding(
        padding: const EdgeInsets.all(20),
        //child: Text(pair.asLowerCase, style: style),
        // ↓ 코드 수정
        child: Text(
          pair.asLowerCase,
          style: style,
          semanticsLabel: "${pair.first} ${pair.second}",
        ),
      ),
    );
  }
}

class FavoritesList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final favorites = context.watch<MyAppState>().favorites;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.all(10.0),
          child: SafeArea(
            child: Text(
              '즐겨찾기: ${favorites.length}개',
              style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
            ),
          ),
        ),
        //SizedBox(height: 5), // 20px 간격
        Expanded(
          child: favorites.isEmpty
              ? Center(child: Text('즐겨찾기한 단어가 없습니다.'))
              : ListView.builder(
                  itemCount: favorites.length,
                  itemBuilder: (context, index) {
                    final pair = favorites[index];
                    return ListTile(
                      title: Text(pair.asPascalCase),
                      trailing: IconButton(
                        icon: Icon(Icons.favorite, color: Colors.red),
                        onPressed: () {
                          context.read<MyAppState>().removeFavorite(pair);
                        },
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }
}