4일차 과정에서는 좀 더 다양한 위젯을 학습했다. 주로 스크롤과 관련된 위젯들이다.
학습한 내용
- 다양한 원형 이미지 생성법
- ListView 위젯
- SingleChildScrollView 위젯
- PageView 위젯
- 로렘 입숨(Lorem Ipsum)
CircleAvatar(
backgroundImage: NetworkImage('https://picsum.photos/100/100'),
),
ClipOval(
child: Image.network('https://picsum.photos/100/100'),
),
ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network('https://picsum.photos/100/100'),
),
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
),
clipBehavior: Clip.antiAlias,
child: Image.network('https://picsum.photos/100/100'),
),
horizontal로 스크롤 되는 ListView
에 ListTile
을 넣으면 에러가 발생한다.
이유는 ListTile
은 width를 설정하지 않으면 자동으로 양 끝까지 그려주게 되는데 ListView
를 가로 스크롤로 설정하게 되면 infinity width가 되어 에러가 발생하게 된다.
(뒤에서 살펴볼 ListView
에 ListView
를 넣었을 때 에러가 발생하는 것도 비슷한 느낌이다.)
문서 디자인에서 의미가 있는 글을 담으면 사람들이 양식을 보지 않고 글의 내용에 집중하게 된다. 따라서 디자인에 집중하기 위해 실질적 의미가 없는 단어를 조합해서 만든 들을 로렘 입숨이라고 한다.
- localhost란?
- PageView와 ListView의 유저 행동 제한
- ListView 코드 에러 분석
- 가로 스크롤 색상 변경 UI 만들기
- 기분을 담고있는 스크롤 페이징 UI 만들기
host란 네트워크를 이용하기 위해 네트워크에 연결된 장치를 의미한다.
컴퓨터, 스마트폰 등이 장치가 될 수 있으며, 검색을 하기 위해 사용하는 네트워크에 연결된 컴퓨터나 스마트폰을 host라고 한다.
localhost는 직역하면 지역(local) + 호스트(host)가 되는데 네트워크 상에서 자신의 컴퓨터 주소를 의미한다.
즉, localhost는 자신의 PC를 의미하고, 다른 PC는 이 주소에 접근할 수 없다.
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에서 할당하여 작동한다.
8080
은 포트(port)를 의미한다. 컴퓨터는 어떤 서비스를 주거나 받는데 이를 효율적으로 관리하기 위해 포트를 사용한다. 아래는 일반적인 포트의 예시이다.
일반적으로 사용되는 포트의 예시
- 80번 포트 : http
- 20번 포트 : ftp
- 23번 포트 : 원격 서비스
- 8080번 포트 : 실험적으로 하는 서비스는 주로 8080번 포트를 사용
PageView
와 ListView
는 스크롤을 지원하는 위젯이다. 이러한 두 위젯은 유저의 행동에 따라서 작동하는데 유저의 행동을 제한하기 위한 속성이 존재한다.
예를 들어 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
아래의 코드는 에러가 발생하는 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번ListView
에 NeverScrollableScrollPhysics()
를 설정하면 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번의 자식이야'),
]),
),
),
);
}
}
수정한 코드를 실행한 결과는 아래와 같다.
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가지 색상을 사용했다.
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 값들 공부하는 것이었다. 생각보다 검색해도 자료가 많지 않아서ㅠㅠ