Dandy Now!
  • [Flutter] "Do it! 플러터 앱 프로그래밍" - iOS 스타일로 플러터 앱 만들기 | 쿠퍼티노 위젯
    2022년 02월 14일 01시 04분 38초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    "조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 6장을 실습하였다. iOS 스타일을 적용할 수 있는 쿠퍼티노 위젯을 다루었다. 5장에서 작성한 코드를 활용하여 실습하였기 때문에 5장과 6장의 코드가 섞여있다. 주석을 볼 때 이를 감안해야 한다. "동물 추가하기 화면 완성하기" 실습의 158쪽 2단계 CuperthinoSegmentedControl 이후 코드에서 에러가 발생해 애를 많이 먹었다. 본문의 코드와 여러 차례 대조해봤지만 별 문제가 없어 답답했다. 결국 저자의 깃허브를 참조했다.

     

    Do it! 플러터 앱 프로그래밍

    플러터 기본 & 고급 위젯은 물론오픈 API와 파이어베이스를 이용한 앱 개발부터 배포까지!플러터 SDK 2.x 버전을 반영한 개정판!이 책은 플러터의 기초부터 고급 활용법까지 다루어 다양한 영역에

    book.naver.com

     

    dependencies 설정

    pubspec.yaml의 dependencies 설정은 패키지를 가져와서 사용할 수 있도록 하는 방법이다. 쿠퍼티노 아이콘 라이브러리 패키지는 pubspec.yaml에 기본으로 세팅되어 있다.

    // pubspec.yaml
    dependencies:
      flutter:
        sdk: flutter
      cupertino_icons: ^1.0.2 // 쿠퍼티노 아이콘 라이브러리

     


     

    쿠퍼티노 탭바

    // cupertinoMain.dart
    import 'package:flutter/cupertino.dart';
    
    // iOS 스타일 화면을 구현하기 위한 쿠퍼티노 준비
    class CupertinoMain extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _CupertinoMain();
      }
    }
    
    class _CupertinoMain extends State<CupertinoMain> {
      CupertinoTabBar? tabBar;
    
      @override
      void initState() {
        super.initState();
        tabBar = CupertinoTabBar(items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.home)),
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.add)),
        ]);
      }
    
      @override
      Widget build(BuildContext context) {
        // iOS 스타일은 MaterialApp 대신 CupertinoApp 사용
        return CupertinoApp(
          // 쿠퍼티노 위젯의 이름에는 항상 'Cupertino' 접두어가 붙는다.
          home: CupertinoTabScaffold(
              tabBar: tabBar!,
              tabBuilder: (context, value) {
                if (value == 0) {
                  return Container(
                    child: Center(
                      child: Text('cupertino tab 1 '),
                    ),
                  );
                } else {
                  return Container(
                    child: Center(
                      child: Text('cupertino tab 2'),
                    ),
                  );
                }
              }),
        );
      }
    }

     

    // main.dart
    import 'package:flutter/material.dart';
    import './animalItem.dart';
    import 'sub/firstPage.dart';
    import 'sub/secondPage.dart';
    import './cupertinoMain.dart'; // iOS 디자인 쿠퍼티노 적용
    
    // void main() => runApp(MyApp());
    void main() => runApp(CupertinoMain()); // iOS 디자인 쿠퍼티노 적용
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Listview Example',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _MyHomePageState();
      }
    }
    
    // SingleTickerProviderStateMixin는 _MyHomePageState 클래스에서 애니메이션 동작을 처리할 수 있게 한다. vsync: this 코드의 오류가 사라진다.
    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      TabController? controller;
      List<Animal> animalList = new List.empty(
          growable: true); // growable 이 리스트가 가변적으로 증가할 수 있다는 것을 의미한다.
    
      // animalList를 추가한 initState() 함수
      @override
      void initState() {
        super.initState();
        // length는 탭수, vsync는 탭이 이동했을 때 호출되는 콜백 함수를 어디서 처리할지 지정한다.
        controller = TabController(length: 2, vsync: this);
    
        animalList.add(Animal(
            animalName: "고양이", kind: "포유류", imagePath: "repo/images/cat.png"));
        animalList.add(Animal(
            animalName: "병아리", kind: "조류", imagePath: "repo/images/chicken.png"));
        animalList.add(
            Animal(animalName: "소", kind: "포유류", imagePath: "repo/images/cow.png"));
        animalList.add(
            Animal(animalName: "개", kind: "포유류", imagePath: "repo/images/dog.png"));
        animalList.add(Animal(
            animalName: "오리", kind: "조류", imagePath: "repo/images/duck.png"));
        animalList.add(Animal(
            animalName: "개구리", kind: "양서류", imagePath: "repo/images/frog.png"));
        animalList.add(Animal(
            animalName: "팬더", kind: "포유류", imagePath: "repo/images/panda.png"));
        animalList.add(Animal(
            animalName: "돼지", kind: "포유류", imagePath: "repo/images/pig.png"));
        animalList.add(Animal(
            animalName: "토끼", kind: "포유류", imagePath: "repo/images/rabbit.png"));
      }
    
      @override
      Widget build(BuildContext context) {
        // Scaffold로 appBar, body, bottomNavigation를 선언한다.
        return Scaffold(
          appBar: AppBar(
            title: Text('Listview Example'),
          ),
          body: TabBarView(
            children: <Widget>[
              FirstApp(list: animalList),
              SecondApp(),
            ],
            controller: controller,
          ),
          bottomNavigationBar: TabBar(
            tabs: <Tab>[
              Tab(
                icon: Icon(Icons.looks_one, color: Colors.blue),
              ),
              Tab(
                icon: Icon(Icons.looks_two, color: Colors.blue),
              )
            ],
            controller: controller,
          ),
        );
      }
    
      @override
      // 스테이이트풀 생명주기에서 위젯의 상태를 완전히 끝내도록 하여 메모리 누수를 막는다.
      void dispose() {
        controller!.dispose();
        super.dispose();
      }
    }

     

    [그림 1] 쿠퍼티노 탭바

     


     

    쿠퍼티노 리스트뷰

    // cupertinoMain.dart
    import 'package:flutter/cupertino.dart';
    import 'animalItem.dart';
    import 'iosSub/cupertinoFirstPage.dart';
    
    // iOS 스타일 화면을 구현하기 위한 쿠퍼티노 준비
    class CupertinoMain extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _CupertinoMain();
      }
    }
    
    class _CupertinoMain extends State<CupertinoMain> {
      CupertinoTabBar? tabBar;
      List<Animal> animalList = List.empty(growable: true);
    
      @override
      void initState() {
        super.initState();
        tabBar = CupertinoTabBar(items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.home)),
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.add)),
        ]);
        animalList.add(Animal(
            animalName: "고양이", kind: "포유류", imagePath: "repo/images/cat.png"));
        animalList.add(Animal(
            animalName: "병아리", kind: "조류", imagePath: "repo/images/chicken.png"));
        animalList.add(
            Animal(animalName: "소", kind: "포유류", imagePath: "repo/images/cow.png"));
        animalList.add(
            Animal(animalName: "개", kind: "포유류", imagePath: "repo/images/dog.png"));
        animalList.add(Animal(
            animalName: "오리", kind: "조류", imagePath: "repo/images/duck.png"));
        animalList.add(Animal(
            animalName: "개구리", kind: "양서류", imagePath: "repo/images/frog.png"));
        animalList.add(Animal(
            animalName: "팬더", kind: "포유류", imagePath: "repo/images/panda.png"));
        animalList.add(Animal(
            animalName: "돼지", kind: "포유류", imagePath: "repo/images/pig.png"));
        animalList.add(Animal(
            animalName: "토끼", kind: "포유류", imagePath: "repo/images/rabbit.png"));
      }
    
      @override
      Widget build(BuildContext context) {
        // iOS 스타일은 MaterialApp 대신 CupertinoApp 사용
        return CupertinoApp(
          // 쿠퍼티노 위젯의 이름에는 항상 'Cupertino' 접두어가 붙는다.
          home: CupertinoTabScaffold(
              tabBar: tabBar!,
              tabBuilder: (context, value) {
                if (value == 0) {
                  // return Container(
                  //   child: Center(
                  //     child: Text('cupertino tab 1 '),
                  //   ),
                  // );
                  return CupertinoFirstPage(
                    animalList: animalList,
                  );
                } else {
                  return Container(
                    child: Center(
                      child: Text('cupertino tab 2'),
                    ),
                  );
                }
              }),
        );
      }
    }

     

    // iosSub/cupertinoFirstPage.dart
    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    import '../animalItem.dart';
    
    class CupertinoFirstPage extends StatelessWidget {
      final List<Animal> animalList;
      const CupertinoFirstPage({Key? key, required this.animalList})
          : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return CupertinoPageScaffold(
            navigationBar: CupertinoNavigationBar(
              middle: Text('동물 리스트'),
            ),
            child: ListView.builder(
              itemBuilder: (context, index) {
                // 쿠퍼티노에는 카드 위젯이 없다. 따라서 Container를 이용한다.
                return Container(
                  padding: EdgeInsets.all(5),
                  height: 100,
                  child: Column(
                    children: <Widget>[
                      Row(
                        children: <Widget>[
                          Image.asset(
                            animalList[index].imagePath!,
                            height: 80,
                            width: 80,
                            fit: BoxFit.contain,
                          ),
                          Text(animalList[index].animalName!)
                        ],
                      ),
                      Container(
                        height: 2,
                        color: CupertinoColors.black,
                      ),
                    ],
                  ),
                );
              },
              itemCount: animalList.length,
            ));
      }
    }

     

    [그림 2] 쿠퍼티노 리스트뷰 이용한 동물 리스트

     


     

    세그먼트 위젯

    세그먼트 위젯은 머티리얼의 라디오 버튼처럼 여럿 중 하나를 선택할 수 있다.

    // cupertinoMain.dart
    import 'package:flutter/cupertino.dart';
    import 'package:flutter_listview_example/iosSub/cupertinoSecondPage.dart';
    import 'animalItem.dart';
    import 'iosSub/cupertinoFirstPage.dart';
    
    // iOS 스타일 화면을 구현하기 위한 쿠퍼티노 준비
    class CupertinoMain extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _CupertinoMain();
      }
    }
    
    class _CupertinoMain extends State<CupertinoMain> {
      CupertinoTabBar? tabBar;
      List<Animal> animalList = List.empty(growable: true);
    
      @override
      void initState() {
        super.initState();
        tabBar = CupertinoTabBar(items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.home)),
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.add)),
        ]);
        animalList.add(Animal(
            animalName: "고양이", kind: "포유류", imagePath: "repo/images/cat.png"));
        animalList.add(Animal(
            animalName: "병아리", kind: "조류", imagePath: "repo/images/chicken.png"));
        animalList.add(
            Animal(animalName: "소", kind: "포유류", imagePath: "repo/images/cow.png"));
        animalList.add(
            Animal(animalName: "개", kind: "포유류", imagePath: "repo/images/dog.png"));
        animalList.add(Animal(
            animalName: "오리", kind: "조류", imagePath: "repo/images/duck.png"));
        animalList.add(Animal(
            animalName: "개구리", kind: "양서류", imagePath: "repo/images/frog.png"));
        animalList.add(Animal(
            animalName: "팬더", kind: "포유류", imagePath: "repo/images/panda.png"));
        animalList.add(Animal(
            animalName: "돼지", kind: "포유류", imagePath: "repo/images/pig.png"));
        animalList.add(Animal(
            animalName: "토끼", kind: "포유류", imagePath: "repo/images/rabbit.png"));
      }
    
      @override
      Widget build(BuildContext context) {
        // iOS 스타일은 MaterialApp 대신 CupertinoApp 사용
        return CupertinoApp(
          // 쿠퍼티노 위젯의 이름에는 항상 'Cupertino' 접두어가 붙는다.
          home: CupertinoTabScaffold(
              tabBar: tabBar!,
              tabBuilder: (context, value) {
                if (value == 0) {
                  // return Container(
                  //   child: Center(
                  //     child: Text('cupertino tab 1 '),
                  //   ),
                  // );
                  return CupertinoFirstPage(
                    animalList: animalList,
                  );
                } else {
                  // return Container(
                  //   child: Center(
                  //     child: Text('cupertino tab 2'),
                  //   ),
                  // );
                  return CupertinoSecondPage(
                    animalList: animalList,
                  );
                }
              }),
        );
      }
    }

     

    // iosSub/cupertinoSecondPage.dart
    import 'package:flutter/cupertino.dart';
    import '../animalItem.dart';
    
    class CupertinoSecondPage extends StatefulWidget {
      final List<Animal>? animalList;
    
      const CupertinoSecondPage({Key? key, required this.animalList})
          : super(key: key);
    
      @override
      State<StatefulWidget> createState() {
        return _CupertinoSecondPage();
      }
    }
    
    class _CupertinoSecondPage extends State<CupertinoSecondPage> {
      TextEditingController? _textController; // 동물 이름 입력할 텍스트 필드
      int _kindChoice = 0; // 동물 종류
      bool _flyExist = false; // 날개 유무
      String? _imagePath; // 동물 이미지
      // Map으로 세그먼트 내용 구성
      Map<int, Widget> segmentWidgets = {
        // SizedBox는 영역을 만들어 주는 위젯
        0: SizedBox(
          child: Text('양서류', textAlign: TextAlign.center),
          width: 80, // 텍스트만 넣으면 세그먼트가 너무 좁기 때문에 width를 주었다.
        ),
        1: SizedBox(
          child: Text('조류', textAlign: TextAlign.center),
          width: 80,
        ),
        2: SizedBox(
          child: Text('포유류', textAlign: TextAlign.center),
          width: 80,
        ),
      };
    
      @override
      void initState() {
        super.initState();
        _textController = TextEditingController();
      }
    
      @override
      Widget build(BuildContext context) {
        return CupertinoPageScaffold(
          navigationBar: CupertinoNavigationBar(
            middle: Text('동물 추가'),
          ),
          child: Container(
            child: Center(
              child: Column(
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.all(10),
                    child: CupertinoTextField(
                      // iOS 스타일의 입력 창
                      controller:
                          _textController, // _textController는 iniState() 함수에 선언하였다.
                      keyboardType: TextInputType.text,
                      maxLines: 1, // 최대로 입력할 수 있는 줄 수
                    ),
                  ),
                  CupertinoSegmentedControl(
                      padding: EdgeInsets.only(bottom: 20, top: 20),
                      groupValue: _kindChoice,
                      children: segmentWidgets,
                      onValueChanged: (int? value) {
                        setState(() {
                          _kindChoice = value!;
                        });
                      }),
                  Row(
                    children: <Widget>[
                      Text('날개가 존재합니까?'),
                      CupertinoSwitch(
                          value: _flyExist,
                          onChanged: (value) {
                            setState(() {
                              _flyExist = value;
                            });
                          })
                    ],
                    mainAxisAlignment: MainAxisAlignment.center,
                  ),
                  Container(
                    height: 100,
                    child: ListView(
                      scrollDirection: Axis.horizontal,
                      children: <Widget>[
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/cat.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/cat.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/cow.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/cow.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/dog.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/dog.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/duck.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/duck.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/frog.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/frog.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/panda.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/panda.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/pig.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/pig.png';
                          },
                        ),
                        GestureDetector(
                          child: Image.asset(
                            'repo/images/rabbit.png',
                            width: 80,
                          ),
                          onTap: () {
                            _imagePath = 'repo/images/rabbit.png';
                          },
                        ),
                      ],
                    ),
                  ),
                  CupertinoButton(
                      child: Text('동물 추가하기'),
                      onPressed: () {
                        widget.animalList!.add(Animal(
                            animalName: _textController!.value.text,
                            kind: getKind(_kindChoice),
                            imagePath: _imagePath!,
                            flyExist: _flyExist));
                      })
                ],
                mainAxisAlignment: MainAxisAlignment.center,
              ),
            ),
          ),
        );
      }
    
      getKind(int kindChoice) {
        switch (kindChoice) {
          case 0:
            return "양서류";
          case 1:
            return "파충류";
          case 2:
            return "포유류";
        }
      }
    }

     

    [그림 3] 세그먼트 위젯 사용한 동물 추가 화면

     


     

    그 외 쿠퍼티노 위젯

    Cupertino (iOS-style) widgets에서 다양한 쿠퍼티노 위젯에 대한 소개와 사용법을 볼 수 있다. 책에서는 자주 사용하는 쿠퍼티노 위젯 몇 가지를 다음과 같이 소개하고 있다.

    728x90
    반응형
    댓글