19일차에서는 지금까지 사용하지 않았던 유용한 위젯과 패키지들에 대해 학습했다.
학습한 내용
- Widget
- Stack
- Divider
- AnimatedOpacity
- AnimatedContainer
- AspectRatio
- Wrap
- Package
- url_launcher
- cached_network_image
- intl
- Stack
- 위젯을 쌓고 싶을 때 사용
- Positiond 위젯과 함께 사용
- (참고) [Flutter] 스나이퍼팩토리 2주차 도전하기 마지막에 간단한 설명과 사용 방법이 있음
- Divider
- 구분선을 넣고 싶을 때 사용
- AnimatedOpacity
- 불투명도 애니메이션
- Opacity 값을 바꿔주면 사이 값에 자동으로 애니메이션 효과를 적용
- (참고) [Flutter] 스나이퍼팩토리 12일차 part1에서 정리한 내용이 있음
- AnimatedContainer
- 캐치되는 속성값을 감지하는 애니메이션
- AspectRatio
- 자식 위젯의 사이즈 비율을 정확히 맞춰야 할 때 사용
- 비디오를 넣어야 할 때 (4:3)
- 정사각형을 보여줄 때 (1:1)
- Wrap
- Row는 화면을 벗어나면 오버플로우가 발생하지만 Wrap은 자동으로 다음줄로 넘어가도록 함
- (참고) [Flutter] 스나이퍼팩토리 7일차에서 정리한 내용이 있음
- url_launcher
- 전화걸기, 메세지 보내기, 특정 URL 웹으로 이동
- 카카오톡 채널로 이동도 가능
- cached_network_image
- 이미지를 캐싱할 수 있도록 함
- intl
- 화폐표시, 시간표시
지금까지 플러터를 계속 학습하면서 URI과 URL의 차이를 정리하고 싶었다.
URI
URI는 특정 리소스를 식별하는 통합 자원 식별자(Uniform Resource Identifier)를 의미한다. 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스이다.
URL
URL은 웹 주소이며, 컴퓨터 네트워크 상에서 리소스가 어디 있는지 알려주기 위한 규약이다.
즉, URL을 URI의 서브넷으로 아래 그림과 같이 URI의 안에 URL이 포함된다.
URI의 구조는 아래와 같다.
URI의 구조
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
- scheme : 사용할 프로토콜을 뜻하며 웹에서는 http 또는 https를 사용
- user와 password : (서버에 있는) 데이터에 접근하기 위한 사용자의 이름과 비밀번호
- host와 port : 접근할 대상(서버)의 호스트명과 포트번호
- path : 접근할 대상(서버)의 경로에 대한 상세 정보
- query : 접근할 대상에 전달하는 추가적인 정보 (파라미터)
- fragment : 메인 리소스 내에 존재하는 서브 리소스에 접근할 때 이를 식별하기 위한 정보
지난번에 시리님이 패키지를 사용하면 다른사람이 만든 UI를 사용할 수 있다고 하셨다. 추가로 플러그인 이라는게 있다고 하셨는데 플러그인은 무엇일까?
결론부터 말하자면 플러그인도 다른 사람의 코드를 사용하는 패키지 이지만, 패키지는 Dart 코드로만 이루어져 있고, 플러그인은 Dart 이외의 Java, Kotlin, Swift, Javascript 등 다른 언어의 코드를 포함하고 있다.
- 패키지(Package) : Dart 코드로만 구성됨
- 플러그인(Plugins : Dart 이외의 다른 언어도 포함됨
앞에서 소개한 패키지 중 하나인 url_lancher
를 통해 살펴보자
url_lancher
의 기능 중 하나는 웹 브라우저를 열어 주는 것인데 이 기능은 IOS, Android, macOs 등 어떤 플랫폼에서든지 같은 기능을 보여준다.
url_lancher
패키지의 소스코드를 보면 아래와 같다.
// ...
LaunchStatus launch(
String url,
Bundle headersBundle,
boolean useWebView,
boolean enableJavaScript,
boolean enableDomStorage) {
if (activity == null) {
return LaunchStatus.NO_ACTIVITY;
}
Intent launchIntent;
if (useWebView) {
launchIntent =
WebViewActivity.createIntent(
activity, url, enableJavaScript, enableDomStorage, headersBundle);
} else {
launchIntent =
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(url))
.putExtra(Browser.EXTRA_HEADERS, headersBundle);
}
try {
activity.startActivity(launchIntent);
} catch (ActivityNotFoundException e) {
return LaunchStatus.ACTIVITY_NOT_FOUND;
}
return LaunchStatus.OK;
}
// ...
// ...
- (void)launchURL:(NSString *)urlString
call:(FlutterMethodCall *)call
result:(FlutterResult)result {
NSURL *url = [NSURL URLWithString:urlString];
UIApplication *application = [UIApplication sharedApplication];
if (@available(iOS 10.0, *)) {
NSNumber *universalLinksOnly = call.arguments[@"universalLinksOnly"] ?: @0;
NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly};
[application openURL:url
options:options
completionHandler:^(BOOL success) {
result(@(success));
}];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
BOOL success = [application openURL:url];
#pragma clang diagnostic pop
result(@(success));
}
}
// ...
위의 코드들 처럼 Android에서 브라우저를 열기 위해서는 Java 코드를 사용하고, IOS에서 사파리를 열기 위해서는 Objective-C 코드를 사용한다.
url_lancher
패키지처럼 플랫폼에 따라 다른 작업이 필요해 Dart 이외의 다른 언어의 코드가 포함되어 있는 경우에 해당 패키지를 플러그인이라고 한다.
- Hero 위젯을 사용하여 주어진 앱 만들기
Hero 위젯을 사용하여 아래의 결과물을 제작하세요.
아래 링크는 Hero
위젯의 공식문서이다. 해당 문서에서 Hero
위젯의 사용법과 예시 등을 확인할 수 있다.
Flutter - Hero animations
dependencies:
dio: ^5.0.0
cached_network_image: ^3.2.3
pubspec.yaml
에 네트워크 통신을 위해 필요한 dio
를 설치하고, 이번에 학습한 이미지 캐싱을 사용해 보기 위해 cached_network_image
를 설치했다.
import 'package:flutter/material.dart';
import 'package:my_app/page/home_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(), //홈 페이지 호출
);
}
}
main.dart
에서는 HomePage
위젯을 호출한다.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:my_app/page/hero_page.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
Future<List> getData() async {
var url =
'https://sfacassignment-default-rtdb.firebaseio.com/.json'; // 데이터 요청 URL
Dio dio = Dio(); //Dio 객체
var res = await dio.get(url); //데이터 요청
return res.data['body']; //결과 반환
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('19일차 과제'),
),
body: FutureBuilder(
future: getData(), //데이터 요청 바인딩
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
//데이터를 받아온 뒤 리스트 뷰 출력
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: snapshot.data!.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
GestureDetector(
child: Hero(
tag: index, //히어로 태그
child: CachedNetworkImage(
width: 64,
height: 64,
fit: BoxFit.fill,
imageUrl: snapshot.data![index]['url'],
),
),
//이미지 상세 페이지로 이동
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HeroPage(
imgUrl: snapshot.data![index]['url'],
heroTag: index,
),
),
),
),
SizedBox(width: 16),
Text(snapshot.data![index]['msg']), //이미지관련 메세지
],
),
),
);
} else {
return Center(child: CircularProgressIndicator()); // 로딩 중
}
},
),
);
}
}
HomePage
에서는 우선 데이터를 받아오는 getData()
메소드를 작성했다. 데이터는 dio
의 get
메소드를 사용해 가져오고 결과를 리턴한다.
본문에서는 FutureBuilder
를 통해 결과 값이 정상적으로 받아오면 리스트 뷰를 출력한다. 리스트 뷰에서는 Row
로 요소들을 구성했다. 이미지는 CachedNetworkImage
를 사용해 보았다. 이렇게 캐시를 사용하면 한 번 가져온 이미지를 캐시에 저장하고 사용하기 때문에 데이터가 많아졌을 때 서버에 요청을 하지 않아도 돼서 성능이 좋아질 수 있다.
이렇게 출력한 이미지를 Hero
로 감싸고 태그를 index
로 설정하여 리스트의 각 항목들이 다른 고유한 태그 값을 가지도록 했다.
이러한 이미지 전체를 GestureDetector
를 사용해 onTap
이벤트를 구현해 주었다. 이미지를 클릭시 이미지가 확대되는 상세 페이지로 이동하도록 HeroPage
를 작성했는데 이 코드는 뒤에서 설명한다.
마지막으로 이미지의 메세지는 Text
로 출력했다.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class HeroPage extends StatelessWidget {
const HeroPage({super.key, required this.heroTag, required this.imgUrl});
final int heroTag; //히어로 태그
final String imgUrl; //이미지 URL
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Page'),
),
body: Center(
child: Hero(
tag: heroTag, //받아온 히어로 태그
// 이미지 출력
child: CachedNetworkImage(
imageUrl: imgUrl,
),
),
),
);
}
}
HeroPage
는 히어로 애니메이션을 위해 사용할 태그와 이미지 Url을 매개변수로 받는다. 이를 사용해 화면에 이미지를 출력하는데 역시 CachedNetworkImage
를 사용해 보았고, 전달 받은 히어로 태그를 설정했다. 이렇게 Hero
위젯에 같은 태그를 설정하면 페이지를 이동할 때 애니메이션이 적용된다.
정말 오랜만에 포스팅이 짧게 끝난 것 같다. 오늘 학습한 내용이 간단한 위젯들과 패키지이고, 과제도 어렵지 않고 양도 적어서 금방 끝났다. 추가 내용 정리를 어떤걸 할까 고민하다가. 예전부터 생각했던 URL과 URI 차이를 정리해 보았고, 지난번에 시리님이 플러그인도 찾아보면 좋을거 같다고 하셨는데 갑자기 생각나서 오늘 포스팅에 사용했다. ㅎㅎ 그런 이유로 오늘은 추가 내용 정리가 학습한 내용과 크게 연관은 없다. ㅋㅋㅋㅋ 근데 쓰다가 어딘지 모르겠는데 마크다운이 혼자 고장나서 약간 애를 먹었다. ㅠㅠ 그럼 이만 이틀 뒤에 월간평가가 있어서 복습을 열심히 하러 가보겠습니다!!
(사실 월간평가가 있어서 오늘 포스팅 내용과 과제가 많지 않았던 것입니다..ㅠㅠ 아마 내일도 내용은 적을 듯...?)