[Flutter] 스나이퍼팩토리 13일차

KWANWOO·2023년 2월 10일
1
post-thumbnail

스나이퍼팩토리 플러터 13일차

13일차에서는 Map 데이터타입을 학습하고 이와 관련된 코드를 작성해 보았다.

학습한 내용

  • Map
  • 단어 앱 제작

추가 내용 정리

Infinity Scorll (무한 스크롤)

12일차 마지막에 나왔던 내용인데 지금 정리해보고자 한다.

앱을 사용할 때 리스트 스크롤에서 마지막 부분에 가면 자동으로 이전 과거 내용이 이어지는 스크롤이 있다. 이를 Infinity Scroll 이라고 한다.

우선 아래와 같이 scrollControllerinitState 메소드에서 addListener를 작성한다.

final ScrollController scrollController = ScrollController();


  void initState() {
    scrollController.addListener(() { 
     if (scrollController.position.pixels ==
              scrollController.position.maxScrollExtent
          ) {
        _getData();
      }
    });
    super.initState();
  }

scrollController.position.pixels는 현재 스크롤의 위치를 픽셀값으로 가져오고, scrollController.position.maxScrollExtent는 스크롤의 마지막 값을 가져온다. 따라서 조건문이 충족할 때 새로운 데이터를 불러오면 무한 스크롤을 보여줄 수 있다.

addListener는 컨트롤러 안의 속성값이 변경되는 경우에 실행되는 함수이다. addListener는 거의 모든 컨트롤러에 사용할 수 있고 매개 변수를 void Function()으로 받는다.


  void addListener(VoidCallback listener) {

Map

Map은 Key와 Value 쌍으로 이루어진 객체이다. 키와 값은 어던 자료형이던지 상관 없다.

  • Map 선언

Map은 중괄호나 new Map() 등을 사용해 선언할 수 있다. 다만 new Map()을 사용하는 경우 값을 따로 초기화 해줘야 한다.

  • 데이터 삽입

데이터 삽입은 map[key] = value로 할 수 있다.

Map my_map = {};

Map my_map2 = new Map();
my_map2['name'] = 'my map 2';

이 방식 이외에도 Map으로 된 데이터를 addAll() 메소드를 사용하여 추가할 수도 있다.

Map my_map3 = {'type': 'tutorial'};
my_map.addAll(my_map3);
  • 데이터 변경

첫 번째 데이터 삽입 방식과 같은 방식으로 데이터 변경이 가능한데 키 값을 기존에 존재하는 값으로 입력하면 된다.

my_map['name'] = 'your map';

update() 메소드를 사용해도 데이터를 변경할 수 있는데 Map.update(key, function) 형태로 전달한다. 만약 키 값이 존재하지 않을 때 값을 업데이트하지 않고 초기 값으로 추가를 하고 싶다면 ifAbset를 같이 사용하면 된다.

Map<String, int> map = {};

map.update('a', (value) => value + 10, ifAbsent: () => 0);
  • 데이터 삭제

remove() 메소드를 사용하면 데이터를 삭제할 수 있다. 매개 변수로는 키 값을 전달해 준다.

my_map.remove('name');

clear()를 사용하면 Map 안의 모든 엔트리가 제거된다.

my_map.clear();
  • 데이터 확인
    Map에 어떤 키나 값이 포함되어 있는지를 확인하려면 containsKeycontainsValue를 사용할 수 있으며 boolean 값을 반환한다.
print(my_map.containsKey('name'));
print(my_map.containsValue('Kim'));

다른 Map의 유용한 속성들

  • .keys: Map에 담긴 key들을 Iterable 객체로 반환해 준다.
  • .values: Map에 담긴 value들을 Iterable 객체로 반환해 준다.
  • .entries: Map에 담긴 key-value쌍들을 Iterable 객체로 반환해 준다.
  • .isEmpty : Map이 비었다면 true, 그렇지 않다면 false를 반환한다.
  • .isNotEmpty : Map에 뭔가 들어 있다면 true, 비었다면 false를 반환한다.
  • .length: Map에 든 key-value 쌍이 몇 개인지 반환한다.

dynamic

Dart에는 입력된 정보를 통해 타입을 추론해서 데이터 형식을 정의하는 dynamic 타입이 있다.

var와 유사한 데이터타입인데 var의 경우 추론된 타입이 한번 입력되고 나면 다른 타입을 저장할 수 없지만 dynamic의 경우 어떤 형식이라도 항상 입력이 가능하다.

var varName = 'var test';
print(varName); // 출력 var test;

name = 123; // Error 발생

dynamic dynamicName = 'var test';
print(dynamicName); // 출력 var test;

name = 123; // name에 123 입력
print(dynamicName); // 123

따라서 하나의 리스트에 여러 값을 담거나 Map을 사용할 때 제네릭으로 dynamic을 자주 사용한다.

두 가지 ScrollPhysics 사용

아래에서 정리할 단어 앱 만들기를 진행하면서 두 개의 ScrollPhysics를 사용하게 되어 내용을 정리하고자 한다.

두 개의 ScrollPhysics를 함께 사용하려면 parent 속성을 사용하거나 applyTo() 메소드를 사용하면 된다.

우선 ScrollPhysics를 반환하는 applyTo() 메소드는 아래와 같은 구조로 작성되어 있다.

ScrollPhysics applyTo(ScrollPhysics? ancestor) {
  return ScrollPhysics(parent: buildParent(ancestor));
}

즉, 아래의 두 코드는 두 개의 ScrollPhysics를 연결하는 같은 기능을 수행한다.

final FooScrollPhysics x = const FooScrollPhysics().applyTo(const BarScrollPhysics());
const FooScrollPhysics y = FooScrollPhysics(parent: BarScrollPhysics());

자세한 내용은 아래의 공식 문서를 참고
applyTo method - ScrollPhysics class - widgets library - Dart API


13일차 과제

  1. 단어 앱 제작

1. 단어 앱 제작

제공되는 단어 데이터를 활용하여 단어 앱을 만든다.

데이터

기본 5개이며 추가가능, 본인의 단어 데이터 활용가능

List<Map<String, String>> words = [
  {
		"word": "apple", 
		"meaning": "사과", 
		"example": "I want to eat an apple"
	},
  {
		"word": "paper", 
		"meaning": "종이", 
		"example": "Could you give me a paper"
	},
  {
    "word": "resilient",
    "meaning": "탄력있는, 회복력있는",
    "example": "She's a resilient girl"
  },
  {
    "word": "revoke",
    "meaning": "취소하다",
    "example": "The authorities have revoked their original decision to allow development of this rural area."
  },
  {
    "word": "withdraw",
    "meaning": "철회하다",
    "example": "After lunch, we withdrew into her office to finish our discussion in private."
  },
];

요구사항

  • FloatingActionButton이 두 개가 떠있는 형태로, 양쪽에 위치합니다.

    • 왼쪽 버튼을 클릭하면, 이 전 단어로 이동합니다.
    • 오른쪽 버튼을 클릭하면, 이 다음 단어로 이동합니다.
  • ThemeData.dark() 를 활용합니다.

  • 단어와 뜻의 자간을 -1만큼 좁혀봅니다. 예문은 1만큼의 자간을 갖도록 합니다.

  • 단어는 최소 5개 이상으로 준비합니다.

  • 결과물 예시

코드 작성

  • main.dart
import 'package:first_app/page/home_page.dart';
import 'package:flutter/material.dart';

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

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

  // root Widget
  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: HomePage(), // 홈 페이지 호출
    );
  }
}

main.dart 에서는 다른 파일에 작성된 HomePage()를 호출한다. 테마는 ThemeData.dark()로 다크 모드를 설정했다.

  • home_page.dart
import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    //단어 리스트
    List<Map<String, String>> words = [
      {"word": "apple", "meaning": "사과", "example": "I want to eat an apple"},
      {
        "word": "paper",
        "meaning": "종이",
        "example": "Could you give me a paper"
      },
      {
        "word": "resilient",
        "meaning": "탄력있는, 회복력있는",
        "example": "She's a resilient girl"
      },
      {
        "word": "revoke",
        "meaning": "취소하다",
        "example":
            "The authorities have revoked their original decision to allow development of this rural area."
      },
      {
        "word": "withdraw",
        "meaning": "철회하다",
        "example":
            "After lunch, we withdrew into her office to finish our discussion in private."
      },
      {
        "word": "factory",
        "meaning": "공장",
        "example": "The factory has been earmarked for closure."
      },
    ];

    var pageViewController = PageController(); //PageView 컨트롤러

    return Scaffold(
      body: PageView.builder(
        physics: NeverScrollableScrollPhysics(
            parent: BouncingScrollPhysics()), //스크롤 동작
        controller: pageViewController, //컨트롤러 연결
        itemCount: words.length,
        itemBuilder: (context, index) {
          return Center(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text(
                    style: TextStyle(
                      fontSize: 36,
                      fontWeight: FontWeight.bold,
                      letterSpacing: -1, //자간
                    ),
                    words[index]['word']!,
                  ),
                  Text(
                    style: TextStyle(
                      color: Colors.grey,
                      letterSpacing: -1, //자간
                    ),
                    words[index]['meaning']!,
                  ),
                  SizedBox(height: 16),
                  Text(
                    style: TextStyle(
                      color: Colors.grey,
                      letterSpacing: 1, //자간
                    ),
                    textAlign: TextAlign.center, //텍스트 가운데 정렬
                    "\"${words[index]['example']!}\"",
                  ),
                ],
              ),
            ),
          );
        },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            //이전으로 이동 버튼
            FloatingActionButton(
              heroTag: 'back',
              onPressed: () => pageViewController.previousPage(
                duration: Duration(seconds: 1),
                curve: Curves.ease,
              ),
              child: Icon(
                size: 16,
                Icons.arrow_back_ios,
              ),
            ),
            // 다음으로 이동 버튼
            FloatingActionButton(
              heroTag: 'next',
              onPressed: () => pageViewController.nextPage(
                duration: Duration(milliseconds: 600),
                curve: Curves.ease,
              ),
              child: Icon(
                size: 16,
                Icons.arrow_forward_ios,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

HomePage 위젯에 주어진 단어 데이터를 words 리스트로 생성했다. 기존의 단어에 factory를 추가해 총 6개의 단어를 포함한다.

그리고 페이지의 동작을 제어할 pageViewControllerPageController()로 생성했다.

화면은 Scaffold 에서 PageView.builder로 만들었다. 스크롤 화면은 손으로 스와이핑이 불가능 하고 Bounce 효과를 넣어야 하므로 NeverScrollableScrollPhysicsparent 속성을 BouncingScrollPhysics로 설정했다. 또한 앞에서 만든 컨트롤러를 연결했다.

페이지 뷰의 본문 내용은 Column으로 구성하여 3개의 텍스트 뷰를 넣었다. 텍스트 뷰에서 letterSpacing 속성을 사용해 단어와 뜻의 자간은 -1로, 예문의 자간은 1로 설정했다. 예문에서는 텍스트를 가운데 정렬하기 위해 textAlign 속성을 TextAlign.center로 설정했다.

FAB은 먼저 위치를 centerFloat로 설정했고, Row를 사용해 두 개의 버튼을 위치시켰다. 각 버튼은 onPressed이벤트로 페이지 뷰의 컨트롤러에서 previousPage()nextPage() 메소드를 사용해 이전과 다음으로 이동하는 기능을 추가했다.

결과


3주차 마감

이번주도 끝났다... 뭔가 금방 지나간거 같기도 하고ㅎㅎ 오늘은 과제도 그렇고 포스팅도 그렇고 좀 빨리 끝났다. 어제 포스팅도 2개나 하고 과제가 좀 많아서 오래걸렸는데 상대적으로 짧게 느껴져서 그런가 어렵진 않았던거 같다. 주말에 주간평가랑 도전과제도 열심히 해보자!!!! 🫠🫠

📄 Reference

profile
관우로그

0개의 댓글

관련 채용 정보