728x90
반응형
photo_manager 를 이용하여 간단한 photo picker 를 구현하려고 합니다.
photo_manager 의 기본 셋팅과 사용법은 아래 링크에서 확인해주세요.
구현하려는 photo picker 의 기능은 다음과 같으며 이번 포스팅에서는 빨간 글씨로 된 부분까지만 구현하겠습니다.
- 기기에 저장되어있는 이미지 목록을 불러온다.
- 이때 이미지는 그리드 형태로 한줄에 3개 보여줍니다.
- 드롭다운 형태로 앨범 이동이 가능해야 하며 '모든 사진'을 볼 수 있어야 한다.
- '모든 사진' 선택 시 첫번째 칸에는 카메라 촬영을 통해 이미지를 가지고 올 수 있어야 한다.
- 이미지 선택 시 순서가 매겨지며 dim 처리가 되고 상단에 작은 이미지로 보인다.
- 이미지 선택 해제는 선택된 이미지를 누르거나 상단의 이미지의 x 버튼을 눌렀을 때 해제된다.
- 이미지 선택 개수 제한을 정할 수 있어야 한다.
- 확인 버튼을 누르면 선택한 이미지 정보를 이전 페이지에 전달한다.
- 이미지를 선택하지 않고 확인 버튼을 누르면 이미지를 선택해달라는 alert 을 띄운다.
필요한 것들을 정리해봅니다.
- 파일 접근에 대한 권한 확인
- 권한 수락 후 PhotoManager.getAssetPathList() 를 통해 받아오는 AssetPathEntity 목록
- 드롭다운으로 앨범 이동을 구현해야 하니 앨범 id 와 앨범 name 을 가진 클래스의 목록
- 선택된 앨범
- 선택된 앨범의 이미지 목록
- 이미지 목록의 현재 페이지
album.dart
- 앨범 id 와 앨범 name 을 가진 클래스를 만들어 줍니다.
class Album {
String id;
String name;
Album({
required this.id,
required this.name,
});
}
sample_screen.dart
- 상태를 활용해야 하기 때문에 StatefulWidget 으로 만듭니다.
- 위에서 언급한 필요한 변수들을 선언합니다.
import 'package:flutter/material.dart';
import 'album.dart';
class PhotoSelectScreen extends StatefulWidget {
const PhotoSelectScreen({Key? key}) : super(key: key);
@override
State<PhotoSelectScreen> createState() => _PhotoSelectScreenState();
}
class _PhotoSelectScreenState extends State<PhotoSelectScreen> {
List<AssetPathEntity>? _paths; // 모든 파일 정보
List<Album> _albums = []; // 드롭다운 앨범 목록
late List<AssetEntity> _images; // 앨범의 이미지 목록
int _currentPage = 0; // 현재 페이지
late Album _currentAlbum; // 드롭다운 선택된 앨범
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child : Text('Photo Picker')
),
);
}
}
권한 확인
- 파일 접근에 대한 권한을 가져오기 위해 initState 에서 권한을 확인합니다.
- initState 에서는 비동기 작업을 할 수 없으니 따로 만들어서 권한을 확인합니다.
@override
void initState() {
super.initState();
checkPermission();
}
// 권한 확인
Future<void> checkPermission() async {
final PermissionState ps = await PhotoManager.requestPermissionExtend();
if (ps.isAuth) {
// 권한 수락
await getAlbum();
} else {
// 권한 거절
await PhotoManager.openSetting();
}
}
AssetPathEntity 목록, 드롭다운 앨범 목록
- PhotoManager.getAssetPathList() 를 통해 모든 파일의 정보를 불러옵니다.
- 이 정보를 이용하여 드롭다운에 사용될 앨범의 목록을 만들어 줍니다.
- AssetPathEntity 에서 '모든 사진'은 isAll 이 true 일 때이며 기본 name 인 'Recents' 가 아닌 '모든 사진' 으로 넣어줍니다.
Future<void> getAlbum() async {
_paths = await PhotoManager.getAssetPathList(
type: RequestType.image,
);
_albums = _paths!.map((e) {
return Album(
id: e.id,
name: e.isAll ? '모든 사진' : e.name,
);
}).toList();
await getPhotos(_albums[0], albumChange: true);
}
앨범의 이미지 목록
- 앨범의 이미지 목록을 불러오는 함수이며 호출되는 시기는 다음과 같습니다.
- 이 페이지에 처음 진입했을 때
- 드롭다운으로 다른 앨범을 선택했을 때
- 스크롤이 끝에 다다랐을 때
Future<void> getPhotos(
Album album, {
bool albumChange = false,
}) async {
_currentAlbum = album;
albumChange ? _currentPage = 0 : _currentPage++;
final loadImages = await _paths!
.singleWhere((AssetPathEntity e) => e.id == album.id)
.getAssetListPaged(
page: _currentPage,
size: 20,
);
setState(() {
if (albumChange) {
_images = loadImages;
} else {
_images.addAll(loadImages);
}
});
}
- Album 과 앨범의 변경 여부를 판단하는 albumChange 를 인자로 받으며 디폴트 값으로 false 로 지정했습니다.
- 이미지 목록은 _paths 를 singleWhere 를 이용하여 AssetPathEntity 의 id 값을 통해 getAssetListPaged() 으로 불러옵니다.
- 앨범이 변경되었다면 불러온 이미지를 _images 에 set 해주고 아니라면 addAll 해준 후 setState 해줍니다.
드롭다운 구현
- 예제에서는 AppBar 를 통해 구현하겠습니다.
- _albums 가 비어 있지 않다면 드롭다운 버튼이 생겨납니다.
- 드롭다운 메뉴를 변경하면 onChanged 가 호출되고 그때마다 getPhotos 를 호출합니다.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
child: _albums.isNotEmpty
? DropdownButton(
value: _currentAlbum,
items: _albums.map((e) => DropdownMenuItem(
value: e,
child: Text(e.name),
)).toList(),
onChanged: (Album? value) => getPhotos(value!, albumChange: true),
) : const SizedBox()),
),
body: Center(
child : Text('Photo Picker')
),
);
}
이미지를 보여줄 GridView 구현
- List<AssetEntity> 를 인자로 받으며 이후 이미지 선택 처리를 위해 StatefulWidget 으로 만듭니다.
- 받은 이미지를 AssetEntityImage 를 통해 구현하면 끝납니다.
import 'package:flutter/material.dart';
import 'package:photo_manager/photo_manager.dart';
class GridPhoto extends StatefulWidget {
List<AssetEntity> images;
GridPhoto({
required this.images,
Key? key,
}) : super(key: key);
@override
State<GridPhoto> createState() => _GridPhotoState();
}
class _GridPhotoState extends State<GridPhoto> {
@override
Widget build(BuildContext context) {
return GridView(
physics: const BouncingScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
children: widget.images.map((AssetEntity e) {
return AssetEntityImage(
e,
isOriginal: false,
fit: BoxFit.cover,
);
}).toList(),
);
}
}
스크롤 감지 구현
- NotificationListener 위젯을 통하여 구현합니다.
- onNotification 을 통해 스크롤의 끝과 스크롤의 현재 위치를 받을 수 있습니다.
- 현재 스크롤 위치와 스크롤 끝을 나눕니다. 스크롤이 끝에 가까워질수록 1에 근접합니다.
- 스크롤이 다다를 때쯤인 0.7 정도에 다음 페이지의 이미지를 불러옵니다.
- child 는 위에서 구현한 GridPhoto 로 해놓습니다.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
child: _albums.isNotEmpty
? DropdownButton(
value: _currentAlbum,
items: _albums.map((e) => DropdownMenuItem(
value: e,
child: Text(e.name),
)).toList(),
onChanged: (value) => getPhotos(value!, albumChange: true),
): const SizedBox()),
),
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scroll) {
// 현재 스크롤 위치 - scroll.metrics.pixels
// 스크롤 끝 위치 scroll.metrics.maxScrollExtent
final scrollPixels =
scroll.metrics.pixels / scroll.metrics.maxScrollExtent;
if (scrollPixels > 0.7) getPhotos(_currentAlbum);
return false;
},
child: SafeArea(
child: _paths == null
? const Center(child: CircularProgressIndicator())
: GridPhoto(images: _images),
),
),
);
}
결과
- 정상적으로 구동되는 것을 볼 수 있습니다.
- 나머지 부분은 다음 포스팅에서 진행하겠습니다!
지금까지 구현한 코드입니다.
sample_screen.dart
import 'package:flutter/material.dart';
import 'package:photo_manager/photo_manager.dart';
import 'album.dart';
import 'grid_photo.dart';
class SampleScreen extends StatefulWidget {
const SampleScreen({Key? key}) : super(key: key);
@override
State<SampleScreen> createState() => _SampleScreenState();
}
class _SampleScreenState extends State<SampleScreen> {
List<AssetPathEntity>? _paths;
List<Album> _albums = [];
late List<AssetEntity> _images;
int _currentPage = 0;
late Album _currentAlbum;
Future<void> checkPermission() async {
final PermissionState ps = await PhotoManager.requestPermissionExtend();
if (ps.isAuth) {
await getAlbum();
} else {
await PhotoManager.openSetting();
}
}
Future<void> getAlbum() async {
_paths = await PhotoManager.getAssetPathList(
type: RequestType.image,
);
_albums = _paths!.map((e) {
return Album(
id: e.id,
name: e.isAll ? '모든 사진' : e.name,
);
}).toList();
await getPhotos(_albums[0], albumChange: true);
}
Future<void> getPhotos(
Album album, {
bool albumChange = false,
}) async {
_currentAlbum = album;
albumChange ? _currentPage = 0 : _currentPage++;
final loadImages = await _paths!
.singleWhere((element) => element.id == album.id)
.getAssetListPaged(
page: _currentPage,
size: 20,
);
setState(() {
if (albumChange) {
_images = loadImages;
} else {
_images.addAll(loadImages);
}
});
}
@override
void initState() {
super.initState();
checkPermission();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
child: _albums.isNotEmpty
? DropdownButton(
value: _currentAlbum,
items: _albums
.map((e) => DropdownMenuItem(
value: e,
child: Text(e.name),
))
.toList(),
onChanged: (value) => getPhotos(value!, albumChange: true),
)
: const SizedBox()),
),
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scroll) {
final scrollPixels =
scroll.metrics.pixels / scroll.metrics.maxScrollExtent;
print('scrollPixels = $scrollPixels');
if (scrollPixels > 0.7) getPhotos(_currentAlbum);
return false;
},
child: SafeArea(
child: _paths == null
? const Center(child: CircularProgressIndicator())
: GridPhoto(images: _images),
),
),
);
}
}
album.dart
class Album {
String id;
String name;
Album({
required this.id,
required this.name,
});
}
grid_photo.dart
import 'package:flutter/material.dart';
import 'package:photo_manager/photo_manager.dart';
class GridPhoto extends StatefulWidget {
List<AssetEntity> images;
GridPhoto({
required this.images,
Key? key,
}) : super(key: key);
@override
State<GridPhoto> createState() => _GridPhotoState();
}
class _GridPhotoState extends State<GridPhoto> {
@override
Widget build(BuildContext context) {
return GridView(
physics: const BouncingScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
children: widget.images.map((e) {
return AssetEntityImage(
e,
isOriginal: false,
fit: BoxFit.cover,
);
}).toList(),
);
}
}
728x90
반응형
'Flutter > Sample' 카테고리의 다른 글
[Flutter] photo picker - ② (0) | 2022.08.14 |
---|