1. 카메라로 촬영한 이미지에 대해 작은 아바타를 만들어보자.
- place.dart
import 'dart:io';
import 'package:uuid/uuid.dart';
const uuid = Uuid();
class Place {
Place({required this.title, required this.image}) : id = uuid.v4();
final String id;
final String title;
final File image;
}
1) 모델 값에서 image 추가
- user_places.dart
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:favorite_places/models/place.dart';
class UserPlacesNotifier extends StateNotifier<List<Place>> {
UserPlacesNotifier() : super(const []);
void addPlace(String title, File image) {
final newPlace = Place(title: title, image: image);
state = [newPlace, ...state];
}
}
final userPlacesProvider =
StateNotifierProvider<UserPlacesNotifier, List<Place>>(
(ref) => UserPlacesNotifier(),
);
- 프로바이더에서 place모델에 대해 image추가
- add_place.dart
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/material.dart';
import 'package:favorite_places/widgets/image_input.dart';
import 'package:favorite_places/providers/user_places.dart';
class AddPlaceScreen extends ConsumerStatefulWidget {
const AddPlaceScreen({super.key});
@override
ConsumerState<AddPlaceScreen> createState() {
return _AddPlaceScreenState();
}
}
class _AddPlaceScreenState extends ConsumerState<AddPlaceScreen> {
final _titleController = TextEditingController();
File? _selectedImage;
void _savePlace() {
final enteredTitle = _titleController.text;
if (enteredTitle.isEmpty || _selectedImage == null) {
return;
}
ref
.read(userPlacesProvider.notifier)
.addPlace(enteredTitle, _selectedImage!);
Navigator.of(context).pop();
}
@override
void dispose() {
_titleController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add new Place'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Column(
children: [
TextField(
decoration: const InputDecoration(labelText: 'Title'),
controller: _titleController,
style: TextStyle(
color: Theme.of(context).colorScheme.onBackground,
),
),
const SizedBox(height: 10),
ImageInput(
onPickImage: (image) {
_selectedImage = image;
},
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _savePlace,
icon: const Icon(Icons.add),
label: const Text('Add Place'),
),
],
),
),
);
}
}
1) void _savePlace() {
final enteredTitle = _titleController.text;
if (enteredTitle.isEmpty || _selectedImage == null) {
return;
}
ref
.read(userPlacesProvider.notifier)
.addPlace(enteredTitle, _selectedImage!);
Navigator.of(context).pop();
}
--> 프로바이더 addPlace에 image값 전달
2) ImageInput(
onPickImage: (image) {
_selectedImage = image;
},
),
--> imageInput위젯에서 image값을 담아와서, _selectedImage에 할당한다. 그리고 _위 savePlace의 값으로 쓰이는 것이다.
- Image.input.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class ImageInput extends StatefulWidget {
const ImageInput({super.key, required this.onPickImage});
final void Function(File image) onPickImage;
@override
State<ImageInput> createState() {
return _ImageInputState();
}
}
class _ImageInputState extends State<ImageInput> {
File? _selectedImage;
void _takePicture() async {
final imagePicker = ImagePicker();
final pickedImage =
await imagePicker.pickImage(source: ImageSource.camera, maxWidth: 600);
if (pickedImage == null) {
return;
}
setState(() {
_selectedImage = File(pickedImage.path);
});
widget.onPickImage(_selectedImage!);
}
@override
Widget build(BuildContext context) {
Widget content = TextButton.icon(
icon: const Icon(Icons.camera),
label: const Text('Take Picture'),
onPressed: _takePicture,
);
if (_selectedImage != null) {
content = GestureDetector(
onTap: _takePicture,
child: Image.file(
_selectedImage!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
);
}
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Theme.of(context).colorScheme.primary.withOpacity(0.2),
),
),
height: 250,
width: double.infinity,
alignment: Alignment.center,
child: content,
);
}
}
1) 자녀 위젯에서 부모 위젯으로 값을 전달하기 onPickImage 함수를 만든다. 해당 Image값을 부모위젯으로 건내준다.
- places_list.dart
import 'package:favorite_places/screens/place_detail.dart';
import 'package:flutter/material.dart';
import 'package:favorite_places/models/place.dart';
class PlacesList extends StatelessWidget {
const PlacesList({super.key, required this.places});
final List<Place> places;
@override
Widget build(BuildContext context) {
if (places.isEmpty) {
return Center(
child: Text(
'No places added yet',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
);
}
return ListView.builder(
itemCount: places.length,
itemBuilder: (ctx, index) => ListTile(
leading: CircleAvatar(
radius: 26,
backgroundImage: FileImage(places[index].image),
),
title: Text(
places[index].title,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => PlaceDetailScreen(place: places[index]),
),
);
},
),
);
}
}
1) CircleAvatar를 사용하여, 위 방법들을 통해 저장된 이미지 값을 이용해 아바타처럼 꾸며준다. (아래 사진 참조)
- place_detail.dart
import 'package:flutter/material.dart';
import 'package:favorite_places/models/place.dart';
class PlaceDetailScreen extends StatelessWidget {
const PlaceDetailScreen({super.key, required this.place});
final Place place;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(place.title),
),
body: Stack(
children: [
Image.file(
place.image,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
],
),
);
}
}
1) 디테일 화면을 계속 꾸며줄 예정이다. Stack을 활용하여 여러 위젯을 곂쳐서 쓸 예정. 일단 해당 place값의 이미지를 배경에 깔아주었다.