Flutter - 리스트에 아이템 추가

유의선·2024년 3월 6일

리스트에 아이템을 추가로 등록하는 기능을 만들어보았다.
이전 프로젝트에 이어서 진행하였다.


secondPage.dartSecondApp 클래스에 해당 기능을 구현하였다.

먼저 FirstApp 클래스와 같게 list를 받도록 secondPage.dartmain.dart를 수정하였다.

// secondPage.dart

import 'package:flutter/material.dart';
import 'package:listview_example/animalItem.dart';

class SecondApp extends StatelessWidget {
  final List<Animal>? list;

  SecondApp({Key? key, this.list}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Text('두 번째 페이지'),
        ),
      ),
    );
  }

}

// main.dart

...

      body: TabBarView(
        children: <Widget>[FirstApp(list: animalList), SecondApp(list: animalList)],
        controller: tabController,
      ),

...

그 후 SecondAppStatefulWidget을 상속하도록 만들었고, createState()에서 반환할 State를 상속한 _SecondApp 클래스를 만들었다.

class SecondApp extends StatefulWidget {
  final List<Animal>? list;

  SecondApp({Key? key, this.list}) : super(key: key);

  
  State createState() => _SecondApp();
}

class _SecondApp extends State<SecondApp> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[],
          ),
        ),
      ),
    );
  }
}

dart에서는 반환값을 한 줄로 표현하고자 할 때 => 화살표를 이용할 수 있다.

  
  State createState() => _SecondApp();

children: <Widget>[] 배열 안에 동물 이름을 입력할 텍스트 필드를 추가했다.

class _SecondApp extends State<SecondApp> {

  final nameController = TextEditingController();

		...

            children: <Widget>[
              TextField(
                controller: nameController,
                keyboardType: TextInputType.text,
                maxLines: 1,
              )
            ],
          ),

		...

keyboardType으로 키보드 유형을 text로 설정하였고,
controller로 final로 선언한 nameController를 설정하였다.


라디오 버튼을 이용해 동물의 종류를 선택할 수 있도록 코드를 추가하였다.

class _SecondApp extends State<SecondApp> {
  final nameController = TextEditingController();

  int? _radioValue = 0;

		...
        
            children: <Widget>[
              TextField( ... ),
              Row(
                children: <Widget>[
                  Radio(
                      value: 0,
                      groupValue: _radioValue,
                      onChanged: _radioChange),
                  Text('양서류'),
                  Radio(
                      value: 1,
                      groupValue: _radioValue,
                      onChanged: _radioChange),
                  Text('파충류'),
                  Radio(
                      value: 2,
                      groupValue: _radioValue,
                      onChanged: _radioChange),
                  Text('포유류'),
                ],
              )
            ],
	
    ...

  _radioChange(int? value) {
    setState(() {
      _radioValue = value;
    });
  }
}

각 위젯이 가로로 배치되도록 Row안에 작성하였다.
value는 인덱스 값이다. 각각 0, 1, 2를 넣어 인덱스를 부여했다.
groupValue는 그룹화를 담당한다. int형인 _radioValue를 선언하여 넣었다.
onChanged는 이벤트 처리를 담당한다. _radioChange() 함수를 정의하여 넣었다.


동물이 날 수 있는지 선택하는 체크박스를 추가하였다.

class _SecondApp extends State<SecondApp> {
  final nameController = TextEditingController();
  int? _radioValue = 0;
  bool? flyExist = false;

		...
        
            children: <Widget>[
              TextField( ... ),
              Row( ... ),
              Row(
                children: <Widget>[
                  Text('날 수 있나요?'),
                  Checkbox(
                      value: flyExist,
                      onChanged: (bool? check) {
                        setState(() {
                          flyExist = check;
                        });
                      })
                ],
              )
            ],
            
       ...

동물 이미지를 고를 수 있도록 만들었다.
이미지를 클릭하면 해당 이미지를 선택하도록 만들었다.

class _SecondApp extends State<SecondApp> {
  final nameController = TextEditingController();
  int? _radioValue = 0;
  bool? flyExist = false;
  String? _imagePath;

  	...
    
            children: <Widget>[
              TextField( ... ),
              Row( ... ),
              Row( ... ),
              Row(
                children: <Widget>[
                  GestureDetector(
                    child: Image.asset(
                      'repo/images/cow.png',
                      width: 80,
                    ),
                    onTap: () {
                      _imagePath = 'repo/images/cow.png';
                    },
                  ),
                  GestureDetector(
                    child: Image.asset(
                      'repo/images/fox.png',
                      width: 80,
                    ),
                    onTap: () {
                      _imagePath = 'repo/images/fox.png';
                    },
                  ),
                  GestureDetector(
                    child: Image.asset(
                      'repo/images/bee.png',
                      width: 80,
                    ),
                    onTap: () {
                      _imagePath = 'repo/images/bee.png';
                    },
                  ),
                  GestureDetector(
                    child: Image.asset(
                      'repo/images/cat.png',
                      width: 80,
                    ),
                    onTap: () {
                      _imagePath = 'repo/images/cat.png';
                    },
                  ),
                ],
              )
            ],

Row 위젯은 이미지가 화면을 벗어나면 다 표시하지 못한다. 그래서 지금은 이미지를 4개만 넣었다.
이를 리스트뷰를 사용하면 이미지를 더 넣을 수 있다.
리스트뷰의 scrollDirection을 사용하면 스크롤 방향을 정할 수 있다.

Row로 만든 이미지 선택 영역을 가로 리스트로 변경하고, 동물들을 추가하였다.

            children: <Widget>[
              TextField( ... ),
              Row( ... ),
              Row( ... ),
              
              Container(
                height: 100,
                child: ListView(
                  scrollDirection: Axis.horizontal,
                  children: <Widget>[
                    GestureDetector(
                      child: Image.asset(
                        'repo/images/cow.png',
                        width: 80,
                      ),
                      onTap: () {
                        _imagePath = 'repo/images/cow.png';
                      },
                    ),
                    GestureDetector(
                      child: Image.asset(
                        'repo/images/fox.png',
                        width: 80,
                      ),
                      onTap: () {
                        _imagePath = 'repo/images/fox.png';
                      },
                    ),
                    GestureDetector(
                      child: Image.asset(
                        'repo/images/bee.png',
                        width: 80,
                      ),
                      onTap: () {
                        _imagePath = 'repo/images/bee.png';
                      },
                    ),
                    GestureDetector(
                      child: Image.asset(
                        'repo/images/cat.png',
                        width: 80,
                      ),
                      onTap: () {
                        _imagePath = 'repo/images/cat.png';
                      },
                    ),
                    GestureDetector(
                      child: Image.asset(
                        'repo/images/pig.png',
                        width: 80,
                      ),
                      onTap: () {
                        _imagePath = 'repo/images/pig.png';
                      },
                    ),
                    GestureDetector(
                      child: Image.asset(
                        'repo/images/wolf.png',
                        width: 80,
                      ),
                      onTap: () {
                        _imagePath = 'repo/images/wolf.png';
                      },
                    ),
                  ],
                ),
              )
            ],

위젯들을 양쪽 여백 사이에 균일하게 배치하기 위해 각 Row마다 mainAxisAlignment: MainAxisAlignment.spaceAround를 추가하였다.

            children: <Widget>[
              TextField(
                controller: nameController,
                keyboardType: TextInputType.text,
                maxLines: 1,
              ),
              Row(
              	children: <Widget>[ ... ],
                mainAxisAlignment: MainAxisAlignment.spaceAround),
              Row(
              	children: <Widget>[ ... ],
              	mainAxisAlignment: MainAxisAlignment.spaceAround),
                
                Container( ... )
            ],

동물을 목록에 추가하는 버튼을 만들었다.
버튼을 누르면 추가하기 전에 알람 창을 띄우고 확인을 누르면 동물이 추가되도록 만들었다.

            children: <Widget>[
              TextField( ... ),
              Row( ... ),
              Row( ... ),
              Container( ... ),
              
              ElevatedButton(
                  onPressed: () {
                    var animal = Animal(
                        animalName: nameController.value.text,
                        kind: getKind(_radioValue),
                        imagePath: _imagePath,
                        flyExist: flyExist);
                    AlertDialog dialog = AlertDialog(
                      title: Text('동물 추가하기'),
                      content: Text(
                        '이 동물은 ${animal.animalName} 입니다. '
                        '동물의 종류는 ${animal.kind} 입니다. \n이 동물을 추가하시겠습니까?',
                        style: TextStyle(fontSize: 30.0),
                      ),
                      actions: [
                        ElevatedButton(
                            onPressed: () {
                              widget.list?.add(animal);
                              Navigator.of(context).pop();
                            },
                            child: Text('예')),
                        ElevatedButton(
                            onPressed: () {
                              Navigator.of(context).pop();
                            },
                            child: Text('아니요'))
                      ],
                    );
                    showDialog(
                        context: context,
                        builder: (BuildContext context) => dialog);
                  },
                  child: Text('동물 추가하기'))
            ],
  
  ...

  _radioChange(int? value) { ... }

  getKind(int? radioValue) {
    switch (radioValue) {
      case 0:
        return "양서류";
      case 1:
        return "파충류";
      case 2:
        return "포유류";
    }
  }

AlertDialogTitle은 알림 창에 표시할 제목이고, content는 내용이다.
actions는 배열 형태로 위젯을 가져올 수 있다. 여기서는 버튼을 두 개 선언해 각각 "예", "아니오" 를 Text로 보이도록 하였고, "예"를 누르면 리스트에 추가하고 꺼지도록, "아니오"를 누르면 그냥 꺼지도록 하였다.


0개의 댓글