기본 위젯 2 (입력,다이얼로그,이벤트,에니메이션,쿠퍼티노)

Updated:


1.입력용 위젯

1-1.TextField

  • 글자를 입력받는 위젯입니다. InputDecoration 클래스와 함께 사용하면 힌트 메시지나 외곽선 등의 꾸밈 효과를 간단히 추가 할 수 있습니다.

-decoration 프로퍼티를 활영하면 다양한 효과를 줄 수 있습니다. 여기서는 InputDecoration 클래스의 labelText 프로퍼티를 활용하여 힌트를 나타 낼 수 있습니다.

  • InputDecoration 클래스의 border 프로퍼티에 OutlineInputBorder 클래스의 인스턴스를 지정하면 외곽선과 힌트를 표현하는 material UI 가 나타 납니다.
body: Padding(
  padding: const EdgeInsets.all(8.0),
  child: Center(
    child: Column(
      children: const <Widget>[
        TextField(),
        SizedBox(
          height: 40,
        ),
        TextField(
          decoration: InputDecoration(
            labelText: 'ID를 입력하세요', // 힌트
          ),
        ),
        SizedBox(
          height: 40,
        ),
        TextField(
            decoration: InputDecoration(
          border: OutlineInputBorder(),
          labelText: 'Password 도 입력하세요',
        )),
      ],
    ),
  ),
)

image

1-2.CheckBox / Switch

  • 설정 화면 등에 많이 사용되는 체크박스, 라디오 버튼, 스위치를 표현하는 위젯입니다.

  • Checkbox 와 Switch 는 모양만 다를 뿐 사용방법은 동일합니다.

  • 상태를 나타낼 boolean type 의 변수가 필요하며, 이 변수를 value 프로퍼티에 설정합니다. onChanged 이벤트는 체크값이 변할 때마다 발생하는데 변경된 값이 boolean value 인수로 넘어 오면서 setState() 함수를 통해 value 프로퍼티에 지정한 변수값을 변경해서 UI 를 다시 그립니다.

  • 상태를 나타내는 변수가 사용됨으로 StatefulWidget 이여야 합니다.

body: Padding(
  padding: const EdgeInsets.all(8.0),
  child: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Switch(
            value: _isChecked,
            onChanged: (value) {
              setState(() {
                _isChecked = value;
              });
            })
      ],
    ),
  ),
),

image

1-3.Radio / RadioListTile

  • 선택 그룹 중 하나를 선택 할 때 사용하는 위젯입니다. 어디까지를 터치 영역으로 볼것이냐에 따라서 Radio 를 사용하거나 RadioListTitle 을 사용합니다

  • Radio 는 그룹내에서 하나만 선택할 때 사용합니다. 그룹이 되는 항목을 enum 으로 정의 한 후, groupValue 프로퍼티에 enum 으로 정의 된 변수를 지정하고 onChanged 이벤트에서 변경된 값을 반양합니다.

  • ListTile 대신에 RadioListTitle 을 사용하면 가로 전체가 터치 영역이 됩니다.

body: Padding(
  padding: const EdgeInsets.all(8.0),
  child: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        ListTile(
          title: Text('남자'),
          leading: Radio(
            value: Gender.MAN,
            groupValue: _gender,
            onChanged: (value) {
              setState(() {
                _gender = value;
              });
            },
          ),
        ),
        ListTile(
          title: Text('여자'),
          leading: Radio(
            value: Gender.WOMEN,
            groupValue: _gender,
            onChanged: (value) {
              setState(() {
                _gender = value;
              });
            },
          ),
        ),
        SizedBox(
          height: 40,
        ),
        RadioListTile(
          title: Text('남자'),
          value: Gender.MAN,
          groupValue: _gender,
          onChanged: (value) {
            setState(() {
              _gender = value;
            });
          },
        ),
        RadioListTile(
          title: Text('여자'),
          value: Gender.WOMEN,
          groupValue: _gender,
          onChanged: (value) {
            setState(() {
              _gender = value;
            });
          },
        ),
      ],
    ),
  ),
),

image

1-4.DropDownButton

  • 여러 아이템 중 하나를 고를 수 있는 콤보박스 형태의 위젯입니다.

  • value 프로퍼티에 표시할 값을 지정합니다. items 프로퍼티에는 표시할 항목을 DropdownMenuItem 클래스의 인스턴스들을 담은 리스트로 지정해야 합니다.

  • map() 함수를 사용해서 _valueList 리스트 문자열 3개를 DropdownMenuItem 인스턴스 3개로 반환합니다

  • toList() 함수를 사용하여 다시 리스트로 변환시켜 items 프로퍼티에 리스트를 저장합

  • 주의할점은 setState(){} 에서 value 값이 type error 가 발생됩니다. String? _selectedValue 에서 null 값도 포함을 시키고 DropdownButton<String> 처럼 type 지정을 해줘야 합니다

  • type error 해결 다른 방법은 setState(){}valuevalue as String 으로 직접적으로 type 을 지정할 수 있습니다.

class _MyHomePageState extends State<MyHomePage> {
  // State 클래스 필드에 작성
  final _valueList = ['첫번째', '두번쩨', '세번째'];
  String? _selectValue = '첫번째';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DropDownButton'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: DropdownButton<String>(
              value: _selectValue,
              items: _valueList
                  .map((value) => DropdownMenuItem(
                        value: value,
                        child: Text(value),
                      ))
                  .toList(),
              onChanged: (value) {
                setState(() {
                  _selectValue = value;
                });
              }),
        ),
      ),

image

2.다이얼로그

다이얼로그는 사용자의 확인을 요구하거나 메시지를 표시하는 용도로 자주 사용합니다.

2-1.AlertDialog

  • meterial UI 유저 확인용 다이얼 로그 입니다

  • AlertDialog 표시 하려면 showDialog() 함수의 builder 프로퍼티에 AlertDialog 클래스의 인스턴스를 반환하는 함수를 작성하면 됩니다. showDialog() 함수의 barrierDismissible 프로퍼티는 다이얼로그 바깥 부분의 탭을 해도 닫히게 할 것인지 정합니다.

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AlertDialog'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _neverSatisfied(context);
          },
          child: const Text('Alert Dialog'),
        ),
      ),
    );
  }
}

Future<void> _neverSatisfied(BuildContext context) async {
  return showDialog<void>(
      context: context,
      // 사용자가 다이얼로그 바깥을 터치하면 닫히지 않음
      barrierDismissible: false,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('제목'),
          content: SingleChildScrollView(
            child: ListBody(
              children: const <Widget>[
                Text('Alert Dialog 입니다'),
                Text('Ok 를 눌러 닫습니다'),
              ],
            ),
          ),
          actions: <Widget>[
            TextButton(
              child: const Text('OK'),
              onPressed: () {
                // 다이얼로그 닫기
                Navigator.of(context).pop();
              },
            ),
            TextButton(
                onPressed: () {
                  // 다이얼로그 닫기
                  Navigator.of(context).pop();
                },
                child: const Text('Cancel'))
          ],
        );
      });
}

Kapture 2021-10-11 at 16 19 26

2-2.DatePicker

  • 날짜를 선택할 때 사용합니다.

  • showDatePicker() 함수를 호출하면 달력이 표시되며 날짜를 선택하고 ‘OK’ 버튼을 누르면 날짜를 변환합니다.

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  DateTime? _selectedTime;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DatePicker'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: Column(
            children: <Widget>[
              ElevatedButton(
                onPressed: () {
                  Future<DateTime?> selectedDate = showDatePicker(
                    context: context, // context 인수전달
                    initialDate: DateTime.now(), // 초깃값
                    firstDate: DateTime(2021), // 시작일 2021년 1월 1일
                    lastDate: DateTime(2030), // 마지막일 2030년 1월 1일
                    builder: (BuildContext context, Widget? child) {
                      return Theme(
                        // 따로 정의 하지 않으면 default 값이 설정 됨
                        data: ThemeData.dark(), // 다크테마
                        child: child as Widget,
                      );
                    },
                  );
                  selectedDate.then((dateTime) {
                    setState(() {
                      _selectedTime = dateTime;
                    });
                  });
                },
                child: Text('Date Picker'),
              ),
              Text('$_selectedTime')
            ],
          ),
        ),
      ),
    );
  }
}

Kapture 2021-10-11 at 16 57 08

2-3.TimePicker

  • showTimePicker() 함수를 호출해서 timePicker 를 표시할 수 있습니다.

  • TimeOfDay 클래스에는 시간(hour), 분(minute) 정보가 들어 있습니다.

class _MyHomePageState extends State<MyHomePage> {
  var _selectedTime;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TimePicker'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: Column(
            children: <Widget>[
              ElevatedButton(
                onPressed: () {
                  Future<TimeOfDay?> selectedTime = showTimePicker(
                    context:
                        context, // context 는 Future 타입으로 TimeOfDay 타입의 값을 반환 합니다
                    initialTime: TimeOfDay.now(), // 프로퍼티에 초깃값을 지정합니다.
                  );
                  selectedTime.then((value) {
                    setState(() {
                      _selectedTime = '${value?.hour} : ${value?.minute}';
                    });
                  });
                },
                child: Text('Time Picker'),
              ),
              Text('$_selectedTime'),
            ],
          ),
        ),
      ),
    );
  }
}

Kapture 2021-10-11 at 17 16 40

3.이벤트

GestureDetector / InWell

  • 글자나 그림 같이 이벤트 프로퍼티가 없는 위젯에 이벤트를 적용할 때 사용하는 위젯입니다.

  • GestureDetectorInWell 은 터치 이벤트를 발생시킵니다. onTap 프로퍼티를 가지고 있어서 child 프로퍼티에 어떠한 위젯이 와도 클릭 이벤트를 만들 수 잇습니다. Text, Image 등에 위젯에도 간단히 클릭 이벤트를 추가할 수 있습니다.


class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GestureDetector / InWell'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              GestureDetector(
                onTap: () {
                  print('GestureDetoctor 클릭');
                },
                child: Text('GestureDetoctor 클릭'),
              ),
              const SizedBox(
                height: 40,
              ),
              InkWell(
                onTap: () {
                  print('InKWell 클릭!');
                },
                child: Text('Inkwell 클릭'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

image

Kapture 2021-10-11 at 17 28 13

4.에니메이션

4-1.Hero

  • Hero 위젯은 화면전환시 자연스럽게 연결되는 에니메이션을 지원합니다. 이전 화면으로 돌아 갈때고 자연스럽게 애니메이션이 동작합니다.

  • 사용방법은 에니메이션 효과의 대상이 되는 양쪽 화면의 위젯을 Hero 위젯으로 감싸고, tag 프로퍼티를 반드시 동일하게 지장해야 합니다.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hero'),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const HeroDetailPage()),
            );
          },
          child: Hero(
              tag: 'image', // 여기서 작성한 태그와 두 번째 페이지의 태그가 동일해야 함
              child: Image.asset(
                'assets/sample.png',
                width: 100,
                height: 100,
              )),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hero Detail'),
      ),
      body: Hero(
        tag: 'image', // 여기서 작성한 태그와 첫 번째 페이지의 태그가 동일해야 함
        child: Image.asset('assets/sample.png'),
      ),
    );
  }
}

Kapture 2021-10-11 at 19 55 06

4-2.AnimatedContainer

  • Hero 위젯이 화면 전환시 애니메이션 효과를 지원했다면 AnimatedContainer 위젯은 한 화면 내에서 setState() 함수 화면을 새로 그릴 때 변경된 프로퍼티에 의해 애니메이션이 되도록 해줍니다.

  • Container 위젯과 쓰임새는 비슷하지만 duration, curve 등의 애니메이션 관련 프로퍼티가 있습니다. duration 프로퍼티는 필수이며 에니메이션되는 데 걸리는 시간을 Duration 클래스를 사용해 정의할 수 있습니다. Curves 클래스에는 더 많은 미리 정의된 여러 애니메이션 효과가 있습니다.

더 많은 Curves 클래스 에니메이션 보기..

import 'dart:math'; // Random 클래스 사용에 필요

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _size = 100.0; // 초기값 100.0 을 가지고 있음

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimatedContainer"),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            final random = Random(); // Random 클래스 사용 준비
            setState(() {
              // 클래할 때마다 100.0 ~ 299.0 사이의 실수를 랜덤하게 얻기
              _size = random.nextInt(200).toDouble() +
                  100; // 사이즈를 클릭할 때마다 랜덤하게 100.0 ~ 299.0 사이의 값이 됩니다.
            });
          },
          child: AnimatedContainer(
            // 1초동안 fastOutSlowIn 효과를 random 한 width 와 height 으로 변경 시킵니다
            duration: const Duration(seconds: 1),
            width: _size,
            height: _size,
            child: Image.asset('assets/sample.png'),
            curve: Curves.fastOutSlowIn,
          ),
        ),
      ),
    );
  }
}

Kapture 2021-10-11 at 20 23 28

랜덤 계산식 check !!

1.nextInt(200) : 0~199 사이의 난수를 정수로 반환

2.toDouble() : 정수를 실수로 변환 => 0.0 ~ 199.0

  1. +100 : 100 더해서 범위를 변경 => 100.0 ~ 299.0

4-3.SilverAppBar /SilverFillRemaining

  • SliverAppBarSilverFillRemaining 은 화면 헤더를 동적으로 표현하는 위젯입니다. 헤더를 위로 스크롤하면 에더 부분이 작아지면서 해더 하단에 있던 정적인 내용만 보이는 AppBar 형태로 에니메이션이 되는데 이것을 Silver 효과라고 합니다

  • pinned: 축소될때 상단에 AppBar 가 고정될지 사라질지 설정합니다.

  • expandedHeight: 확대 될때의 최대 높이를 정합니다

  • flexibleSpace: 확대 / 축소되는 영역의 UI를 작성합니다.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // appBar 를 지정하지 않고 CustomScrollView 인스턴스를 지정함
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            // 헤더 영역
            pinned: true, // 축소시 상단에 AppBar 가 고정되는지 설정
            expandedHeight: 180.0, // 해더의 최대 높이
            flexibleSpace: FlexibleSpaceBar(
              // 늘어나는 영역의 UI 정의
              title: const Text('Silver'),
              background: Image.network(
                'https://images.unsplash.com/photo-1611329532992-0b7ba27d85fb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80',
                fit: BoxFit.cover,
              ),
            ),
          ),
          const SliverFillRemaining(
            // 내용 영역
            child: Center(
              child: Text('Center'),
            ),
          )
        ],
      ),
    );
  }
}

Kapture 2021-10-12 at 10 31 55

4-4.SliverAppBar / SliverList

  • SliverFillRemaining 위젯은 하나의 정적인 화면을 구성할 때 사용하는 반면 ListView 를 사용해서 Silver 효과를 넣고자 하면 ListView 대신 SilverList 를 사용하면 됩니다.
class MyHomePage extends StatelessWidget {
  MyHomePage({Key? key}) : super(key: key);

// 0 부터 49 표시하는 ListTile을 담은 리스트
  final _items = List.generate(
      50,
      (index) => ListTile(
            title: Text('No. $index'),
          ));

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            pinned: true,
            expandedHeight: 180.0,
            flexibleSpace: FlexibleSpaceBar(
              title: const Text('Silver'),
              background: Image.network(
                'https://images.unsplash.com/photo-1611329532992-0b7ba27d85fb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80',
                fit: BoxFit.cover,
              ),
            ),
          ),
          SliverList(
            // delegate 프로퍼티에 SilverChildListDelegate 클래스를 인스턴스로 지정
            // SilverChildListDelegate 클래스의 생성자에 표시할 위젯 리스트 (_items) 를 인수로 전달해야 합니다.
            delegate: SliverChildListDelegate.fixed(_items),
          ),
        ],
      ),
    );
  }
}

Kapture 2021-10-12 at 10 44 29

5.쿠포티노 디자인

좀 더 아이폰 스러운 디자인을 적용하려면 쿠퍼티노 디자인을 사용합니다. flutter/cupertino.dart 패키지에는 다양한 쿠퍼티노 디자인용 UI 위젯이 준비됩니다.

5-1.쿠퍼티노 기본 UI

  • 쿠퍼티노 디자인에서는 AppBar 대신 CupertinoNavigationBar 를 사용하며, CupertinoSwitch, CupertinoButton
class _MyHomePageState extends State<MyHomePage> {
  var _isOn = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CupertinoNavigationBar(
        // material AppBar에 대응
        middle: Text('쿠퍼티노 디자인'), // material AppBar의 title 에 대응
      ),
      body: Center(
        child: Column(
          children: [
            CupertinoSwitch( // material 의 Switch
                value: _isOn,
                onChanged: (bool value) {
                  setState(() {
                    _isOn = value;
                  });
                }),
            CupertinoButton( // ElevatedButton 에 대응
              child: Text('쿠퍼티노 AlertDialog'),
              borderRadius: BorderRadius.circular(16.0),
              onPressed: () {},
            ),
            CupertinoButton(
              child: Text('쿠퍼티노 Picker'),
              onPressed: () {},
            ),
          ],
        ),
      ),
    );
  }
}

Kapture 2021-10-12 at 11 04 15

5-2.CupertinoAlertDialog

class _MyHomePageState extends State<MyHomePage> {
  var _isOn = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CupertinoNavigationBar(
        // material AppBar에 대응
        middle: Text('쿠퍼티노 디자인'), // material AppBar의 title 에 대응
      ),
      body: Center(
        child: Column(
          children: [
            CupertinoSwitch(
                // material 의 Switch
                value: _isOn,
                onChanged: (bool value) {
                  setState(() {
                    _isOn = value;
                  });
                }),
            CupertinoButton(
              // ElevatedButton 에 대응
              child: const Text('쿠퍼티노 AlertDialog'),
              borderRadius: BorderRadius.circular(16.0),
              onPressed: () {
                _showCupertinoDialog();
              },
            ),
            CupertinoButton(
              child: const Text('쿠퍼티노 Picker'),
              onPressed: () {},
            ),
          ],
        ),
      ),
    );
  }

  _showCupertinoDialog() {
    showDialog(
      context: context,
      builder: (context) => CupertinoAlertDialog(
        title: const Text('제목'),
        content: const Text('내용'),
        actions: [
          const CupertinoDialogAction(
            child: Text('Cancel'),
          ),
          CupertinoDialogAction(
            child: const Text('OK'),
            onPressed: () {
              Navigator.of(context).pop(); // 다이얼로그 닫기
            },
          )
        ],
      ),
    );
  }
}

Kapture 2021-10-12 at 11 16 11

5-3.CupertinoPicker

  • iOS 에서 자주 사용되는 피커 입니다. 위 아래로 스크롤하고 피커 바깥을 클릭하면 선택한 값이 적용됩니다.
class _MyHomePageState extends State<MyHomePage> {
  var _isOn = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CupertinoNavigationBar(
        // material AppBar에 대응
        middle: Text('쿠퍼티노 디자인'), // material AppBar의 title 에 대응
      ),
      body: Center(
        child: Column(
          children: [
            CupertinoSwitch(
                // material 의 Switch
                value: _isOn,
                onChanged: (bool value) {
                  setState(() {
                    _isOn = value;
                  });
                }),
            CupertinoButton(
              // ElevatedButton 에 대응
              child: const Text('쿠퍼티노 AlertDialog'),
              borderRadius: BorderRadius.circular(16.0),
              onPressed: () {
                _showCupertinoDialog();
              },
            ),
            CupertinoButton(
              child: const Text('쿠퍼티노 Picker'),
              onPressed: () {
                _showCupertinoPicker();
              },
            ),
          ],
        ),
      ),
    );
  }

  _showCupertinoDialog() {
    showDialog(
      context: context,
      builder: (context) => CupertinoAlertDialog(
        title: const Text('제목'),
        content: const Text('내용'),
        actions: [
          const CupertinoDialogAction(
            child: Text('Cancel'),
          ),
          CupertinoDialogAction(
            child: const Text('OK'),
            onPressed: () {
              Navigator.of(context).pop(); // 다이얼로그 닫기
            },
          )
        ],
      ),
    );
  }

  // Future 사용을 위해 async 사용
  _showCupertinoPicker() async {
    // 0부터 9까지의 숫자 리스트 생성
    final _items = List.generate(10, (index) => index);
    var result = _items[0]; // 기본값 0

    // showCupertinoModalPopup() 함수는 Future 타입을 반환하기 때문에 await 피커가 닫힐때까지 대기한 후, result 변수의 값을 출력합니다.
    await showCupertinoModalPopup(
      context: context,
      builder: (context) => Container(
        height: 200.0,
        child: CupertinoPicker(
          children: _items.map((e) => Text('No. $e')).toList(),
          itemExtent: 50.0,
          onSelectedItemChanged: (int value) {
            result = _items[value];
          },
        ),
      ),
    );
  }
}

Kapture 2021-10-12 at 11 29 32

🔶 🔷 📌 🔑

Reference

Categories:

Updated:

Leave a comment