본문 바로가기
프로그래밍/Flutter & Dart

Flutter 자주 쓰이는 위젯을 이용한 복잡한 UI 그리기

by 어느덧중반 2020. 5. 17.
반응형

주요 사용 위젯

- BottomNavigationBar : 하단 탭 구성

- AppBar : 상단 제목줄 구성

- Row, Column : 가로, 세로 레이아웃 구성

- GestureDetector : 클릭 이벤트 만들기 위한 위젯

- Opacity : 투명도

- CarouselSlider : 좌우 슬라이드하는 UI 작성을 위한 라이브러리

- Listview : ListTile과 함께 사용하여 스크롤 가능한 리스트를 만드는 위젯

   * ListView 안에 ListView 를 넣어야 하는 경우 physics / shrinkWrap 프로퍼티를 지정해줘야 함


Step

1. 전체 틀 잡기

2. BottomNavigationBar를 이용해 하단 탭 만들기 + AppBar 수정해서 상단부 만들기

3. 메인 화면(HomePage) 상/중/하단 만들기


1. 전체 틀 잡기 : 기본 코드에서 쓸데없는 것들 다 날리자.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(''),
      ),
      body: Center(
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

2. BottomNavigationBar를 이용해 하단 탭 만들기 + AppBar 속성 변경해서 노란 테마로 바꾸기

class _MyHomePageState extends State<MyHomePage> {

  var _pageIndex = 0; // 페이지 정보

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Complicated UI App'),
        centerTitle: true, // 중앙 정렬 여부
        elevation: 6, // 상단바 그림자 강도
        actions: <Widget>[
          IconButton(
            icon: Icon(
              Icons.add_circle,
              color: Colors.black87,
            ),
            onPressed: () {},
          ),
          IconButton(
            icon: Icon(
              Icons.drafts,
              color: Colors.black87,
            ),
            onPressed: () {},
          ),
        ],
      ),
      body: Center(
        child: Text('$_pageIndex 페이지', style: TextStyle(fontSize: 50)),
      ),
      bottomNavigationBar: BottomNavigationBar(
        onTap: (value) { // 하단 네비게이션 바를 눌러 페이지 이동
          setState(() {
            _pageIndex = value;
          });
        },
        currentIndex: _pageIndex, // 현재 페이지
        selectedItemColor: Colors.amber, // 선택된 NavigationBar의 색상
        unselectedItemColor: Colors.black26, // 선택안된 NavigationBar의 색상
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('home'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.add_a_photo),
            title: Text('photo'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.more_horiz),
            title: Text('more'),
          ),
        ]
      ),// ng comma makes auto-formatting nicer for build methods.
    );
  }
}

 

3. 메인 화면(HomePage) 상/중/하단 만들기

  - 크게 세 부분으로 나누기 (각 부분은 위젯 리턴형 메소드로 분리)

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        _pageOfTop(), // 상단
        _pageOfMiddle(), // 중단
        _pageOfBottom(), // 하단
      ],
    );
  }
}

Widget _pageOfTop() {
  return Text('pageOfTop');
}

Widget _pageOfMiddle() {
  return Text('pageOfMiddle');
}

Widget _pageOfBottom() {
  return Text('pageOfBottom');
}

 

  - 상단부분 만들기

  • 클릭할 아이콘과 텍스트 만들기
Widget _pageOfTop() {
  return Column(
    children: <Widget>[
      Icon(
        Icons.directions_bike,
        size: 40,
      ),
      Text(
        '자전거'
      ),
    ],
  );
}

  • 위 메뉴를 한 줄에 3개씩 만들기 (tip : mainAxisAlignment 프로퍼티 설정으로 위젯간 공간 동일하게 만들기)
    : Column을 Row위젯으로 감싸고 해당 Column위젯을 복사해서 3개로 만들자
Widget _pageOfTop() {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 위젯 사이 공간 동일하게 만들기
    children: <Widget>[
      Column(
        children: <Widget>[
          Icon(
            Icons.directions_bike,
            size: 40,
          ),
          Text(
            '자전거'
          ),
        ],
      ),
      Column(
        children: <Widget>[
          Icon(
            Icons.directions_run,
            size: 40,
          ),
          Text(
            '뛰기'
          ),
        ],
      ),
      Column(
        children: <Widget>[
          Icon(
            Icons.directions_bus,
            size: 40,
          ),
          Text(
              '버스'
          ),
        ],
      ),
    ],
  );
}

  • 총 두 줄로 만들기 (tip : Row와 Row 사이에 SizedBox 위치시켜서 여백 넣기)
    : Row위젯을 Column위젯으로 다시 감싸고 Row위젯을 복사시켜서 2줄을 만들자
Widget _pageOfTop() {
  return Column(
    children: <Widget>[
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          Column(
            children: <Widget>[
              Icon(
                Icons.directions_bike,
                size: 40,
              ),
              Text(
                '자전거'
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Icon(
                Icons.directions_run,
                size: 40,
              ),
              Text(
                  '달리기'
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Icon(
                Icons.directions_bus,
                size: 40,
              ),
              Text(
                  '버스'
              ),
            ],
          ),
        ],
      ),
      SizedBox(height: 30,), // Row와 Row사이에 위치시켜서 여백 넣기
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          Column(
            children: <Widget>[
              Icon(
                Icons.directions_car,
                size: 40,
              ),
              Text(
                  '자동차'
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Icon(
                Icons.directions_subway,
                size: 40,
              ),
              Text(
                  '지하철'
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Icon(
                Icons.directions_boat,
                size: 40,
              ),
              Text(
                  '보트'
              ),
            ],
          ),
        ],
      )
    ],
  );
}

  • 총 메뉴 개수 5개로 만들기
    : 아래줄의 Column을 2개로 줄이면 윗줄 3개의 간격과 달라 보기에 좋지 않다.
      지우고 싶은 Column에 Opacity위젯으로 감싸고 프로퍼티를 변경해 보이지 않게 바꾸자
          Opacity( // 안보이게 하고 싶은 위젯을 감싸기
            opacity: 0.0, // 완전 투명
            child: Column(
              children: <Widget>[
                Icon(
                  Icons.directions_boat,
                  size: 40,
                ),
                Text(
                    '보트'
                ),
              ],
            ),
          ),

  • 메뉴를 클릭할 수 있게 만들고 전체 여백 주기
    : Column을 GestureDetector 위젯으로 감싸고 onTap 프로퍼티를 통해 클릭이벤트를 만들자
      * InkWell 위젯으로 감싸면 클릭시 효과가 보여서 마음에 드는 것으로 선택하면 된다.
Widget _pageOfTop() {
  return Padding( // 여백을 위해 위젯 추가
    padding: const EdgeInsets.only(top: 30.0), 위쪽 여백만 30 주기
    child: Column(
      children: <Widget>[
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            GestureDetector( // 클릭이벤트를 위한 위젯 추가
              onTap: () { // 클릭시 실행
                print('자전거 아이콘 클릭');
              },
              child: Column(
                children: <Widget>[
                  Icon(
                    Icons.directions_bike,
                    size: 40,
                  ),
                  Text(
                    '자전거'
                  ),
                ],
              ),
            ),
            Column(
              children: <Widget>[
                Icon(
                  Icons.directions_run,
                  size: 40,
  ...
}

자전거 아이콘을 클릭하면 Console창에 print된 것을 확인할 수 있다.
상단 여백이 추가된 것을 확인할 수 있다.

  - 중단부분 만들기

package 버전 확인 후 get하기

 

carousel_slider | Flutter Package

A carousel slider widget, support infinite scroll and custom child widget.

pub.dev

   > 상단 페이지의 How to use 부분 복사해오자.

Widget _pageOfMiddle() {
  return CarouselSlider(
    options: CarouselOptions(height: 400.0), // 슬라이더 높이 설정
    items: [1,2,3,4,5].map((i) { //
      return Builder(
        builder: (BuildContext context) { // context 사용할 경우 활용 가능
          return Container(
              width: MediaQuery.of(context).size.width, // 기기 가로사이즈 받아오기
              margin: EdgeInsets.symmetric(horizontal: 5.0), // 좌우여백 설정
              decoration: BoxDecoration(
                  color: Colors.amber
              ),
              child: Text('text $i', style: TextStyle(fontSize: 16.0),)
          );
        },
      );
    }).toList(),
  );
}

- 슬라이더에 사진을 넣고 입맛에 맞게 변경

  • pubspec.yaml 파일에 assets폴더 추가 후 사진 assets 폴더로 옮기기

  • 리스트로 이미지 리스트 추가
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';

final pictureLists = [
  'assets/buildings-5070537__480.jpg',
  'assets/landscape-5137147__480.jpg',
  'assets/spring-5093894__480.jpg',
  'assets/temple-5142557__480.jpg',
];

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
   ...
  • 슬라이더 부분 수정
Widget _pageOfMiddle() {
  return CarouselSlider(
    options: CarouselOptions(
      height: 200,
      autoPlay: true, // 자동 슬라이더 설정
    ), // 슬라이더 높이 설정
    items: pictureLists.map((url) { // 리스트의 url 넘기기
      return Builder(
        builder: (BuildContext context) { // context 사용할 경우 활용 가능
          return Container(
              width: MediaQuery.of(context).size.width, // 기기 가로사이즈 받아오기
              margin: EdgeInsets.symmetric(horizontal: 5.0), // 좌우여백 설정
              child: ClipRRect( // 이미지 슬라이더에 활용할 위젯 
                borderRadius: BorderRadius.circular(10.0),
                child: Image.asset(
                  url, // 해당 url 값을 이미지로
                  fit: BoxFit.cover // 이미지 채우기
                ),
              ),
          );
        },
      );
    }).toList(),
  );
}

autoPlay를 설정해 자동으로 슬라이드가 된다. 보통 배너광고에 쓰이며 GestureDetector/InkWell위젯을 사용해 다른페이지 이동하도록 onTap() 주면 된다.

- HomePage의 return 위젯을 Column에서 ListView로 변경해 전체가 스크롤 되도록 변경

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView( // Column에서 변경
      children: <Widget>[
        _pageOfTop(),
        _pageOfMiddle(),
        _pageOfBottom(),
      ],
    );
  }
}

상하 스크롤 가능

- 하단 만들기

  • ListView / ListTile 이용해 구현하기

    > ListView위젯(HomePage) 안에 ListView위젯(_pageOfBottom)을 감싸고 있는 경우 shrinkWrap 프로퍼티를 true
       설정해줘야 하며 위젯에서 스크롤이 되려면 physics 프로퍼티의 NeverScrollableScrollPhysics 를 설정해줘야 한다.

Widget _pageOfBottom() {

  final items = List.generate(15, (i) {
    var num = i + 1;
    return ListTile(
      leading: Icon(Icons.notifications),
      title: Text('$num번째 ListTile'),
    );
  });

  return ListView(
    physics: NeverScrollableScrollPhysics(), // 해당 리스트의 스크롤 금지
    shrinkWrap: true, // 상위 리스트 위젯이 별도로 있다면 true 로 설정해야 스크롤이 가능
    children: items,
  );
}

스크롤까지 동작하면 최종 완료

반응형

댓글