Flutter 일기 55번째
참고 1 : https://api.flutter.dev/flutter/material/CircleAvatar-class.html
참고 2 : https://api.flutter.dev/flutter/painting/ImageProvider-class.html
Google에서는 User Profile을 띄울 때, 설정한 사진이 없으면 이렇게 사용자 이름의 첫 글자를 대문자로 띄워준다. 한글 이름이 다 뜨는 경우도 물론 있긴 하더라.
아무튼 저런 동그란 위젯을 쓸 경우가 빈번히 발생하는데, 이럴 때 유용한 것이 CircleAvatar이다.
CircleAvatar의 생성자는 이렇게 생겼다.
const CircleAvatar({
super.key,
this.child,
this.backgroundColor,
this.backgroundImage,
this.foregroundImage,
this.onBackgroundImageError,
this.onForegroundImageError,
this.foregroundColor,
this.radius,
this.minRadius,
this.maxRadius,
})
그 동안 여기저기 보고 다닌 결과 child, backgroundColor, backgroundImage, radius 이 네가지 속성을 많이 쓰는 것 같았다.
지금까지 Flutter위젯은 주로 child에 띄울 것을 설정하기에, 여기서도 그런줄 알았더니 아니었다.
final Widget? child;
Typically a [Text] widget.
If the [CircleAvatar] is to have an image,
use [backgroundImage] instead.
child에는 보통 Text 위젯을 쓰고, CircleAvatar가 이미지를 가져야 한다면 backgroundImage를 대신 쓰라고 한다.
우선 child, radius, backgroundColor 속성까지 설정해서 위젯을 하나 그려보았다.
CircleAvatar(
radius: 50, // CircleAvatar의 크기 결정
child: Text(
'User',
style: TextStyle(fontSize: 30),
),
backgroundColor: Colors.black,
)
이제 backgroundImage 도 설정해보자. 여기서 한참 헤맸다.
{ImageProvider<Object>? backgroundImage}
backgroundImage 속성은 ImageProvider 타입의 데이터를 받는다. 그런데 ImageProvider 는 Abstract class이기 때문에, 이를 구현한 다른 클래스의 객체를 넣어주어야 한다.
Abstract class(추상 클래스)는 이 자체의 인스턴스를 생성할 수 없다는 특징이 있다.
CircleAvatar(
backgroundImage: ImageProvider(),
),
CircleAvatar의 backgroundImage속성에 ImageProvider타입 데이터가 들어간다고 해서, ImageProvider를 적어보면
'추상 클래스는 인스턴스화 안 된다'고 다른 거 넣어달라고 한다.
ImageProvider 클래스를 상속받아 구현한 자식 클래스의 객체를 사용해야 한다. FileImage(File), MemoryImage(Uint8List), NetworkImage(Url), AssetImage(Asset) 등의 클래스가 ImageProvider를 구현한 자식 클래스이다. 나는 이번에 Asset에 User Image를 jpg 파일로 하나 넣어두었기 때문에, AssetImage를 backgroundImage에 넣어보도록 하겠다.
CircleAvatar(
radius: 50,
backgroundImage: AssetImage('images/user_image.jpg'),
),
backgroundColor속성을 적용해도 backgroundImage가 있으면 나타나지 않는다. 다만 child에 Text위젯을 설정했다면, 아래처럼 이미지와 겹쳐서 뜨게 된다.
Implementation(구현)은 예전에 C++과 자바를 배울 때 접한 적이 있는데, Abstract class나 Interface에 적용되는 개념이다.
Ailyn님의 블로그에 차이점이 깔끔하게 설명되어 있었다. 내가 나중에 까먹으면 가서 봐야지.
추상 클래스의 경우 다른 클래스에서 이를 상속받아서 구현해야, 추상 클래스의 요소들을 사용할 수 있다. 추상 클래스 내부의 (abstract 키워드가 붙지 않은) 일반 메소드는 상속 시 바로 사용할 수 있는데, abstract로 선언된 method는 반드시 자식 클래스에서 구현해야한다. 이걸 overriding이라고 함! 참고 - Overloading, Overriding
클래스 생성 후 인스턴스도 못 만들면 뭔 소용이 있을까?
만약 클래스를 여러 개 만들어야 하는데, 뭔가 비슷한 함수를 구현해야 할 일이 있다면? -> abstract method의 경우 함수 인자와 반환값, 함수명 선언만 하고 본문은 없는 형태로 만들어놓을 수 있으니, 이 메소드를 상속받아 각각의 클래스에서 구현할 수 있다.
상속 관계에 있는 클래스의 경우, 자식 클래스의 인스턴스(객체)는 부모 클래스 자료형인 것처럼 사용할 수 있다. 즉, NetworkImage나 AssetImage를 ImageProvider타입의 데이터가 필요한 곳에 넣어서 쓸 수 있다는 말이다.
다만 반대는 성립하지 않는다. 부모 클래스 객체를 자식 클래스 자료형으로는 못 쓴다. ImageProvider클래스가 추상 클래스가 아니더라도, ImageProvider의 인스턴스를 NetworkImage, AssetImage자료형으로는 사용할 수 없다. 점프 투 자바 : IS-A관계 여기에 잘 나와 있다.
재미로 NetworkImage 클래스를 한번 살펴보자. 자세히는 말고 간단히만...
https://api.flutter.dev/flutter/painting/NetworkImage-class.html 여기에 가보면
요런 상속관계를 갖는다고 한다.
우선 Object class설명 페이지에 가보면
The base class for all Dart objects except null.
이렇게 적혀 있다.
Object 클래스는 아래와 같이 2가지의 메소드를 갖고 있다.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed.
toString() → String
A string representation of this object.
그 다음으로 ImageProvider는 어떤 메소드를 갖고 있는지 보자.
createStream(ImageConfiguration configuration) → ImageStream
Called by resolve to create the ImageStream it returns.
evict({ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty}) → Future<bool>
Evicts an entry from the image cache.
load(T key, DecoderCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
('Implement loadBuffer for faster image loading. ' 'This feature was deprecated after v2.13.0-1.0.pre.'),
loadBuffer(T key, DecoderBufferCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed.
inherited
obtainCacheStatus({required ImageConfiguration configuration, ImageErrorListener? handleError}) → Future<ImageCacheStatus?>
Returns the cache location for the key that this ImageProvider creates.
obtainKey(ImageConfiguration configuration) → Future<T>
Converts an ImageProvider's settings plus an ImageConfiguration to a key that describes the precise image to load.
resolve(ImageConfiguration configuration) → ImageStream
Resolves this image provider using the given configuration, returning an ImageStream.
resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) → void
Called by resolve with the key returned by obtainKey.
toString() → String
A string representation of this object.
override
noSuchMethod 와 toString 메소드 아래에는 override라는 키워드가 적혀 있다. Object클래스의 메소드를 상속받아 함수 본문을 변경한 것이다.
이제 NetworkImage 클래스의 메소드를 한번 보자.
createStream(ImageConfiguration configuration) → ImageStream
Called by resolve to create the ImageStream it returns.
, inherited
evict({ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty}) → Future<bool>
Evicts an entry from the image cache.
inherited
load(NetworkImage key, DecoderCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
override
loadBuffer(NetworkImage key, DecoderBufferCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
override
noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed.
inherited
obtainCacheStatus({required ImageConfiguration configuration, ImageErrorListener? handleError}) → Future<ImageCacheStatus?>
Returns the cache location for the key that this ImageProvider creates.
inherited
obtainKey(ImageConfiguration configuration) → Future<NetworkImage>
Converts an ImageProvider's settings plus an ImageConfiguration to a key that describes the precise image to load.
inherited
resolve(ImageConfiguration configuration) → ImageStream
Resolves this image provider using the given configuration, returning an ImageStream.
, inherited
resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, NetworkImage key, ImageErrorListener handleError) → void
Called by resolve with the key returned by obtainKey.
, inherited
toString() → String
A string representation of this object.
inherited
뭔가 키워드가 많아졌다. override라고 표시된 메소드는 부모 클래스로부터 상속받아 재정의한 메소드이고, inherited 라고 표시된 메소드는 부모 클래스의 메소드를 그대로 받은 것이라 보면 된다.
공식 문서에 들어가서, NetworkImage 메소드 중 inherited 키워드가 있는 메소드를 클릭하면 ImageProvider의 메소드로 연결이 된다.
NetworkImage 메소드를 찾으면서 발견했는데, NetworkImage도 abstract class였다...! 근데 어떻게 생성이 된 걸까?
그래서 Stackoverflow에 처음으로 질문을 해봤다. 잘 안되는 영어 쓰기로 겨우겨우 질문을 했는데, 누군가 내 질문을 잘 알아듣고 거의 바로 답변을 해주었다^^ 신기해
stackoverflow 질문 답변 에 따르면,
const factory NetworkImage(
String url,
{ double scale, Map<String, String>? headers
}) = network_image.NetworkImage;
실제로는 _network_image_io.dart의 factory생성자를 호출하는 형태라, 추상 클래스 생성자가 아니라고 했다. 그리고 해당 파일로 가보니
/// The dart:io implementation of [image_provider.NetworkImage].
class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkImage>
implements image_provider.NetworkImage {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
const NetworkImage(this.url, { this.scale = 1.0, this.headers })
: assert(url != null),
assert(scale != null);
...
}
정말 NetworkImage를 구현한 클래스가 또 있었다.
CircleAvatar 위젯 간단히 쓰고 끝날 줄 알았는데, 거의 최장의 일기가 된 거 같다. factory 생성자도 제대로 공부해야하게 생겼네...