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

KWANWOO·2023년 1월 30일
1
post-thumbnail

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

4일차 과정에서는 좀 더 다양한 위젯을 학습했다. 주로 스크롤과 관련된 위젯들이다.

학습한 내용

  • 다양한 원형 이미지 생성법
  • ListView 위젯
  • SingleChildScrollView 위젯
  • PageView 위젯
  • 로렘 입숨(Lorem Ipsum)

추가 내용 정리

원형 이미지 생성법

  • CircleAvatar 위젯
CircleAvatar(
	backgroundImage: NetworkImage('https://picsum.photos/100/100'),
),
  • ClipOvar 위젯
ClipOval(
	child: Image.network('https://picsum.photos/100/100'),
),
  • ClipRRect 위젯
ClipRRect(
	borderRadius: BorderRadius.circular(100),
    child: Image.network('https://picsum.photos/100/100'),
),
  • Container 위젯
Container(
	decoration: BoxDecoration(
    	shape: BoxShape.circle,
    ),
    clipBehavior: Clip.antiAlias,
    child: Image.network('https://picsum.photos/100/100'),
),

horizontal ListView에 ListTile을 넣으면 에러가 발생하는 이유

horizontal로 스크롤 되는 ListViewListTile을 넣으면 에러가 발생한다.

이유는 ListTile은 width를 설정하지 않으면 자동으로 양 끝까지 그려주게 되는데 ListView를 가로 스크롤로 설정하게 되면 infinity width가 되어 에러가 발생하게 된다.

(뒤에서 살펴볼 ListViewListView를 넣었을 때 에러가 발생하는 것도 비슷한 느낌이다.)

로렘 입숨(Lorem Impsum)

문서 디자인에서 의미가 있는 글을 담으면 사람들이 양식을 보지 않고 글의 내용에 집중하게 된다. 따라서 디자인에 집중하기 위해 실질적 의미가 없는 단어를 조합해서 만든 들을 로렘 입숨이라고 한다.


4일차 과제

  1. localhost란?
  2. PageView와 ListView의 유저 행동 제한
  3. ListView 코드 에러 분석
  4. 가로 스크롤 색상 변경 UI 만들기
  5. 기분을 담고있는 스크롤 페이징 UI 만들기

1. localhost란?

host

host란 네트워크를 이용하기 위해 네트워크에 연결된 장치를 의미한다.

컴퓨터, 스마트폰 등이 장치가 될 수 있으며, 검색을 하기 위해 사용하는 네트워크에 연결된 컴퓨터나 스마트폰을 host라고 한다.

localhost

localhost는 직역하면 지역(local) + 호스트(host)가 되는데 네트워크 상에서 자신의 컴퓨터 주소를 의미한다.

즉, localhost는 자신의 PC를 의미하고, 다른 PC는 이 주소에 접근할 수 없다.

127.0.0.1

127.0.0.1은 localhost와 같다. 만약 웹 브라우저에 www.google.com을 검색하면 DNS를 통해서 IP 주소로 변환된다. 컴퓨터는 기본적으로 이해할 수 있는 체계가 숫자이기 때문이다. localhost와 127.0.0.1은 이러한 개념과 같다.

127.0.0.1은 가상의 IP인데, 보통의 IP는 네트워크가 연결되었을 때 결정되지만, 127.0.0.1은 인터넷과 연결되어 있지 않아도 자체 IP에서 할당하여 작동한다.

localhost 8080

8080은 포트(port)를 의미한다. 컴퓨터는 어떤 서비스를 주거나 받는데 이를 효율적으로 관리하기 위해 포트를 사용한다. 아래는 일반적인 포트의 예시이다.

일반적으로 사용되는 포트의 예시

  • 80번 포트 : http
  • 20번 포트 : ftp
  • 23번 포트 : 원격 서비스
  • 8080번 포트 : 실험적으로 하는 서비스는 주로 8080번 포트를 사용

2. PageView와 ListView의 유저 행동 제한

PageViewListView는 스크롤을 지원하는 위젯이다. 이러한 두 위젯은 유저의 행동에 따라서 작동하는데 유저의 행동을 제한하기 위한 속성이 존재한다.

예를 들어 PageView의 페이지 변경을 제한하거나, ListView에서 스크롤이 되지 않게 하는 것이다.

이를 구현하기 위해서 physics 속성을 사용한다. physics 속성은 ScrollPhysics 타입을 받는다. 아래는 ScrollPhysics 타입의 값들이다.

ScrollPhysics

  • AlwaysScrollableScrollPhysics() : 항상 스크롤 할 수 있도록 만든다.
  • BouncingScrollPhysics() : IOS 기본 세팅과 유사, 끝 모서리에서 스크롤 하면 바운스 되어 다시 돌아오게 만든다.
  • ClampingScrollPhysics() : 안드로이 기본 설정 값, 시작과 끝에 도달했을 때 멈추는 효과를 보여준다.
  • FixedExtentScrollPhysics() : FixedExtentScrollController와 함께 써야지만 사용 가능하다. 항상 항목으로만 스크롤 가능하다.
  • NeverScrollableScrollPhysics() : 목록을 스크롤할 수 없게 만든다.
  • RangeMaintainingScrollPhysics() : 범위 내에서 스크롤 위치를 유지하도록 만든다.
  • PageScrollPhysics() : PageView에 대한 스크롤 방식을 만든다.
  • ScrollPhysics() : 기본 스크롤 방식을 사용해 새로운 오브젝트를 만들 때 사용한다.

위의 값들을 physics의 속성으로 주어 스크롤 행동을 제한하거나 방식이나 효과를 변경할 수 있다.

자세한 내용은 아래 링크를 통해 확인 가능하다.
Flutter ListView and ScrollPhysics: A Detailed Look

3. ListView 코드 에러 분석

아래의 코드는 에러가 발생하는 ListView의 코드이다. 이를 분석하고 에러를 해결해 보고자 한다.

  • 에러 발생 코드
ListView(
	children: [
		Text('안녕 난 1번 ListView의 자식이다'),
		Text('나도! 1번 ListView의 자식이야'),
		ListView(
			children: [
				Text('난 2번의 자식임'),
				Text('나도 2번의 자식임'),
			]
		),
		Text('난 멀리 떨어져있지만 1번의 자식이야'),
	]
)

위와 같은 코드를 작성하면 1번 ListView안에 2번 ListView가 생성될 것 같지만 에러가 발생한다. 그 이유는 ListView는 자체가 크기를 확보할 수 있는 만큼 확보하기 때문에 무한한 높이가 된다.

그렇기 때문에 ListView에서는 shrinkWrap 속성을 사용해 child의 크기만큼 높이가 할당되도록 설정할 수 있다. 즉, shrinkWrap 속성은 상위 리스트를 스크롤 가능하게 하는 것이다.

여기서 2번 ListView에서 shrinkWrap: true로 속성을 설정하면 오류가 나지 않고 앱이 실행되지만 physics 속성을 설정하지 않으면 1번 ListView와 2번ListView의 스크롤이 겹치게 되어 원하는 동작이 되지 않을 수 있다.

따라서 2번ListViewNeverScrollableScrollPhysics()를 설정하면 2번 ListView의 스크롤이 제한되므로 1번 ListView의 스크롤로 정상 작동된다. 이렇게 코드를 수정하면 아래와 같다.

  • 수정 코드
import 'package:flutter/material.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: ListView(children: [
            Text('안녕 난 1번 ListView의 자식이다'),
            Text('나도! 1번 ListView의 자식이야'),
            ListView(
                shrinkWrap: true,
                physics: NeverScrollableScrollPhysics(),
                children: [
                  Text('난 2번의 자식임'),
                  Text('나도 2번의 자식임'),
                ]),
            Text('난 멀리 떨어져있지만 1번의 자식이야'),
          ]),
        ),
      ),
    );
  }
}

수정한 코드를 실행한 결과는 아래와 같다.

  • 결과

4. 가로 스크롤 색상 변경 UI 만들기

  • 코드
import 'package:flutter/material.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          //1가지 요소 스크롤뷰
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal, //가로 스크롤
            physics: BouncingScrollPhysics(),
            child: Container(
              width: 7000, //가로 길이
              decoration: BoxDecoration(
                gradient: LinearGradient(
                    // 그라데이션
                    begin: Alignment.centerLeft,
                    end: Alignment.centerRight,
                    colors: [
                      Colors.yellow,
                      Colors.green,
                      Colors.blue,
                      Colors.indigo,
                      Colors.purple,
                      Colors.pink,
                    ]),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
  • 결과

하나의 Container에 그라데이션을 적용해 가로 스크롤 뷰를 만들었다.

우선 스크롤 뷰에 하나의 요소만 들어가기 때문에 'SingleChildScrollView'를 사용했다. 가로 스크롤을 위해 scrollDirection: Axis.horizontal'로 속성을 설정하고, 스크롤 끝 지점에서 바운스 효과를 주기 위해 'physics: BouncingScrollPhysics() 속성을 적용했다.

내부에는 Container를 생성하고, 가로의 길이를 늘이기 위해 width: 7000의 속성을 설정했다. 그라데이션은 6가지 색상을 사용했다.

5. 기분을 담고있는 스크롤 페이징 UI 만들기

  • 코드
import 'package:flutter/material.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  style: TextStyle(
                    fontSize: 30,
                    fontWeight: FontWeight.bold,
                  ),
                  '오늘의 하루는',
                ),
                Text(
                  style: TextStyle(
                    color: Colors.grey,
                    fontSize: 20,
                  ),
                  '어땠나요?',
                ),
                SizedBox(
                  height: 280, // 기분 카드 영역 크기 설정
                  child: PageView(
                  	physics: BouncingScrollPhysics(),
                    children: [
                      // 기분 컨테이너
                      Container(
                        margin: EdgeInsets.all(24.0), // 바깥여백
                        alignment: Alignment.center, // 내부 요소 가운데 정렬
                        //테두리 곡선
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.all(
                            Radius.circular(16),
                          ),
                          // 그라데이션
                          gradient: LinearGradient(
                            begin: Alignment.centerLeft,
                            end: Alignment.centerRight,
                            colors: [
                              Colors.grey,
                              Colors.white,
                            ],
                          ),
                        ),
                        child: Text(
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 20,
                          ),
                          '우울함',
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.all(24.0),
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.all(
                            Radius.circular(16),
                          ),
                          // 그라데이션
                          gradient: LinearGradient(
                            begin: Alignment.centerLeft,
                            end: Alignment.centerRight,
                            colors: [
                              Colors.orange,
                              Colors.yellow,
                            ],
                          ),
                        ),
                        child: Text(
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 20,
                          ),
                          '행복함',
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.all(24.0),
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.all(
                            Radius.circular(16),
                          ),
                          // 그라데이션
                          gradient: LinearGradient(
                            begin: Alignment.centerLeft,
                            end: Alignment.centerRight,
                            colors: [
                              Colors.blue,
                              Colors.green,
                            ],
                          ),
                        ),
                        child: Text(
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 20,
                          ),
                          '상쾌함',
                        ),
                      ),
                      Container(
                        margin: EdgeInsets.all(24.0),
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.all(
                            Radius.circular(16),
                          ),
                          // 그라데이션
                          gradient: LinearGradient(
                            begin: Alignment.centerLeft,
                            end: Alignment.centerRight,
                            colors: [
                              Colors.red,
                              Colors.black,
                            ],
                          ),
                        ),
                        child: Text(
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 20,
                          ),
                          '화남',
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
  • 결과

요소들이 가운데 위치하도록 Column 위젯을 Center 위젯으로 감싸서 생성하고 Column의 속성을 mainAxisAlignment: MainAxisAlignment.center로 설정했다.

Column에 설명 Text 위젯 두개를 생성하고 기분을 나타내는 박스를 만들기 위해 PageView 위젯을 사용했는데 높이를 제한하기 위해 SizedBox로 감싸주었다.

총 4개의 기분(우울함, 행복함, 상쾌함, 화남)을 나타내 주었는데 각 요소는 테두리 곡선과 배경 그라데이션을 적용한 Container를 생성하고, 내부 요소로 Text 위젯을 사용해 기분을 나타내 주었다.


점점 위젯들이 많아진다....ㅠ

4일차까지 진행하면서 꽤 많은 위젯들을 보고 학습했다. 이제 화면 UI를 만드는데 좀 더 다양한 위젯들을 섞고 활용하여 그려진다. 컬럼안에 컨테이너가 들어가고 컨테이너 안에 텍스트가 들어가고....점점 길어지고 있긴 하지만 그래도 제대로 이해하고 쓰니까 헷갈리거나 복잡하지는 않은것 같다. 이번에는 스크롤 되는 동작들이 생각보다 쉽게 만들어져서 좀 신기하네 ㅎㅎ 오히려 이번에 어려웠던거는 physics 속성의 ScrollPhysics 값들 공부하는 것이었다. 생각보다 검색해도 자료가 많지 않아서ㅠㅠ

📄 Reference

profile
관우로그

0개의 댓글