본 문서는 Fluter Example의 내용을 원저작자의 동의하에 번역한것 입니다.
원 저작자 Eric Windmill에게 감사를 전합니다.
이해하는데 불필요한 문장은 과감하게 버렸습니다. 오 번역에 대해서 의견 주시면 적극 반영 하겠습니다.

Reusable Custom Card Widget

1. Dog Card 위젯 만들기

우리는 우리 강아지들을 display할 나이스한 위젯이 필요하다.

첫번째로 다음과 같은 card를 만들 것이다.
card widget

'dog_card.dart’파일을 신규로 생성한다.
파일안에서 빈 상태의 StatefulWidget을 생성한다. 생성자에서 강아지를 가져와야 한다.
당분간 이 card는 강아지의 이름만 표시 할것 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// dog_card.dart

import 'package:flutter/material.dart';

import 'dog_model.dart';

class DogCard extends StatefulWidget {
final Dog dog;

DogCard(this.dog);

@override
_DogCardState createState() => _DogCardState(dog);
}

class _DogCardState extends State<DogCard> {
Dog dog;

_DogCardState(this.dog);

@override
Widget build(BuildContext context) {
return Text(widget.dog.name); // (역자주 : widget은 this와 동일 즉 this.dog.name으로 무관하다.)
}
}

DogCard를 나타나게 main.dart파일의 _MyHomePageState클래스의 build메서드를 수정해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ main.dart

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.black87,
),
body: Container(
child: DogCard(initialDoggos[1]), // New code
),
);
}

당연히 dog_cart.dart를 import해줘야 함.

1
2
3
4
5
6
// main.dart

import 'package:flutter/material.dart';

import 'dog_card.dart';
import 'dog_model.dart';

앱을 새로고침(refresh) 하면 바로 연결되어 있는것을(강아지의 이름) 확인 할 수 있을것이다. (역자주: 오른쪽 상단에 'Rex’라는 텍스트가 출력 된다)
자 이제 Card를 만들 시간이다.

2. Dog Card UI

이 카드는 두가지 주요 부분이 있다. 이미지와 그 밑에 있는 실제카드.

첫번째로 이미지를 만들어 보자.

아래 getter를 _DogCardState클래스에 추가.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// dog_card.dart

// URI를 나타내는 클래스 속성.
// Dog클래스에서 렌더링 된다.
String renderUrl;

Widget get dogImage {
return Container(
// 명시적으로 Container의 width, height를 설정할 수 있다.
// 그렇지 않으면 children의 공간만큼 차지한다.
width: 100.0,
height: 100.0,
// Decoration 속성은 컨테이너를 설정할 수 있게 해준다
// 이것은 BoxDecoration을 기대한다.
decoration: BoxDecoration(
// BoxDecorations have many possible properties.
// BoxDecorations은 여러가지 가능한 속성을 가지고 있다.
// BoxShape를 배경 이미지와 함께 사용하면 아바타 스타일로 잘라진 원circle)을 쉽게 만들 수 있다.
shape: BoxShape.circle,
image: DecorationImage(
// CSS의 imagesize와 동일한 속성.
fit: BoxFit.cover,
// NetworkImage위젯은 URL로 이미지를 가져 오는 위젯이다.
// ImageProviders (예) NetworkImage)는 이미지를 로드하거나 변경할 필요가 있을때 이상적이다.
// 오류를 방지하려면 Null체크를 사용하라.
image: NetworkImage(renderUrl ?? ''),
),
),
);
}

이미지를 보기 위해선 Dog클래스에 인터넷을 통해 이미지를 가져오게 해야 한다.
dog_cart.dart안의 _DogCardState클래스에 다음 코드를 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// dog_card.dart


// State 클래스들은 state가 생성될때 이 메서드를 실행한다.
// initState에선 비동기 작업을 해선 안되기 때문에 다른 메서드에서 처리되도록 연기 시킬 것이다.
void initState() {
super.initState();
renderDogPic();
}

// 우린 Dog클래스 자체적으로 이미지를 가져오길 원하지만
// 이것은 플러터의 기본을 설명 할 수 있는 손쉬운 방법이다. (역자주: Dog클래스가 이미지를 가져오는게 맞겠지만 플러터의 기본을 설명하는데엔 아래 방식이 더 쉽다는것으로 이해됨.)
void renderDogPic() async {
// 서비스 호출
await dog.getImageUrl();
// setState는 플러터에게 변경된 모든것을 다시 렌더링 하도록 지시한다.
// setState는 비동기가 될 수 없으므로 덮어 쓸 수 있는 변수를 사용한다.
setState(() {
renderUrl = dog.imageUrl;
});
}

이제 우리는 렌더링 할 URL을 제대로 가져오는 아바타를 가지고 있다.
카드의 겹침(overlap) 모양을 얻으러면 내장된 위젯 Stack을 사용해야 한다.
Stack 위젯은 가장 자리를 기준으로 자식들을 배치한다.

즉 CSS positiontop, bottom, left, right 속성과 동일하다.
Stack 내에서 Position위젯으로 자식들을 감쌀 수 있지만 꼭 할필요는 없다.

  • Position으로 감싸진 위젯은 웹 개발용어를 사용하자면 'document flow’의 외부에 있다.기본적으로 Stack위젯의 상단 모서리인 [0,0]위치에 있다.
  • 감싸지지 않은 위젯은 배치되지 않는다.(positioned). 기본적으로 위젯의 열(column)으로 배치된 정상적인 'document flow’을 유지한다.

다음은 stack을 시작하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// dog_card.dart

@override
Widget build(BuildContext context) {
// Start with a container so we can add layout and style props:
return Container(
// Arbitrary number that I decided looked good:
height: 115.0,
// A stack takes children, with a list of widgets.
child: Stack(
children: <Widget>[
// position our dog image, so we can explicitly place it.
// We'll place it after we've made the card.
Positioned(
child: dogImage,
),
],
),
);
}

앱을 새로고침하면 상단의 모서리에 강아지 그림이 보인다.

_DocCardState의 레이아웃을 만들어 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// dog_card.dart

Widget get dogCard {
// 새로운 컨테이너
// height와 width는 스타일링을 위한 임의의 숫자이다.
return Container(
width: 290.0,
height: 115.0,
child: Card(
color: Colors.black87,
// padding처리를 위해 자식을 Padding위젯으로 감싼다.
child: Padding(
// padding을 제어하는 클래스를 EdgeInsets라고 한다.
// EdgeInsets.only 생성자는 자식의 각 side에 명시적으로 padding을 설정하는데 사용.
padding: const EdgeInsets.only(
top: 8.0,
bottom: 8.0,
left: 64.0,
),
// Columm은 또다른 레이아웃 위젯 - stack과 비슷 -
// 자식들로 위젯의 목록을 취하고 위젯을 위에서 아래로 배치한다.
child: Column(
// 이들 정렬 속성은 CSS의 flexbox 속성과 정확하게 똑같이 작동한다.
// column의 주축은 세로축이고 `MainAxisAlignment.spaceAround` 는
// CSS의 세로로 배치된 flexbox의 'justify-content: space-around'와 동일하다.
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(widget.dog.name,
// 테마는 앱의 root에서 설정한 MaterialApp widget 이다.
// 우리가 우리 소유의 것을 정하지 않았기 때문에 기본값을 가지고 있다.
// 쉽게 변경 가능한 앱 차원의 일관된 스타일을 유지 하는데 탁월하다.
style: Theme.of(context).textTheme.headline),
Text(widget.dog.location,
style: Theme.of(context).textTheme.subhead),
Row(
children: <Widget>[
Icon(
Icons.star,
),
Text(': ${widget.dog.rating} / 10')
],
)
],
),
),
),
);
}

거의다 왔다!!, DogCard UI를 완성하기 위해 한 가지 더 할일이 있다.
build 메서드의 기본 위젯에 조금 더 스타일을 추가해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// dog_card.dart

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
height: 115.0,
child: Stack(
children: <Widget>[
Positioned(
left: 50.0,
child: dogCard,
),
Positioned(top: 7.5, child: dogImage),
],
),
),
);
}