본문에서 소스는, Android 만을 예제로 만들어졌음을 미리 말씀드립니다.
이미지를 회전하고 자르는 방법은 여러가지가 있겠지만, image_cropper 라는 Flutter SDK를 이용하시면,
그 어떤 방법보다 간단하고 신속하게 해당 기능을 사용할 수 있습니다.
# 이번 샘플 작성을 위한 dependency 추가
image_cropper: ^3.0.0
dotted_border: ^2.0.0+2
image_picker: ^0.8.5+3
터미널에서 flutter pub get 실행
[AndroidManifest.xml]
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
AndroidManifest.xml 에 해당 내용을 추가해주도록 합니다.
-설정 필요 없음-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.css" />
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.js"></script>
해당 내용을 index.html 의 <head> 태그 내부에 기입해주도록 합니다.
이로써, cropper 사용에 대한 준비는 마쳤습니다.
[main.dart]
import 'dart:io';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
highlightColor: const Color(0xFFEA6357),
backgroundColor: const Color(0xFFFDF5EC),
canvasColor: const Color(0xFFFDF5EC),
textTheme: TextTheme(
headline5: ThemeData.light()
.textTheme
.headline5!
.copyWith(color: const Color(0xFFEA6357)),
),
iconTheme: IconThemeData(
color: Colors.grey[600],
),
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFFEA6357),
centerTitle: false,
foregroundColor: Colors.white,
actionsIconTheme: IconThemeData(color: Colors.white),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith(
(states) => const Color(0xFFEA6357)),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
foregroundColor: MaterialStateColor.resolveWith(
(states) => const Color(0xFFEA6357),
),
side: MaterialStateBorderSide.resolveWith(
(states) => const BorderSide(color: Color(0xFFEA6357))),
),
)),
home: const HomePage(title: 'Image Cropping'),
);
}
}
class HomePage extends StatefulWidget {
final String title;
const HomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
XFile? _pickedFile;
CroppedFile? _croppedFile;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: BodyWidget()),
],
),
);
}
Widget BodyWidget() {
if (_croppedFile != null || _pickedFile != null) {
return ImageCardWiget();
} else {
return UploaderCardWidget();
}
}
/**
* ImageCard Widget
*/
Widget ImageCardWiget() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0),
child: Card(
elevation: 4.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ImageWidget(),
),
),
),
const SizedBox(height: 24.0),
MenuWidget(),
],
),
);
}
/**
* 실제 이미지 Widget
*/
Widget ImageWidget() {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
if (_croppedFile != null) {
final path = _croppedFile!.path;
return ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 0.8 * screenWidth,
maxHeight: 0.7 * screenHeight,
),
child: Image.file(File(path)),
);
} else if (_pickedFile != null) {
final path = _pickedFile!.path;
return ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 0.8 * screenWidth,
maxHeight: 0.7 * screenHeight,
),
child: Image.file(File(path)),
);
} else {
return const SizedBox.shrink();
}
}
/**
* 이미지 카드 위젯 내 버튼
*/
Widget MenuWidget() {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
onPressed: () {
fn_clear();
},
backgroundColor: Colors.redAccent,
tooltip: 'Delete',
child: const Icon(Icons.delete),
),
if (_croppedFile == null)
Padding(
padding: const EdgeInsets.only(left: 32.0),
child: FloatingActionButton(
onPressed: () {
fn_cropImage();
},
backgroundColor: const Color(0xFFEA6357),
tooltip: 'Crop',
child: const Icon(Icons.crop),
),
)
],
);
}
/*
* Image Upload Widget
* */
Widget UploaderCardWidget() {
return Center(
child: Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: SizedBox(
width: 320.0,
height: 300.0,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: DottedBorder(
radius: const Radius.circular(12.0),
borderType: BorderType.RRect,
dashPattern: const [8, 4],
color: Theme.of(context).highlightColor.withOpacity(0.4),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.image,
color: Theme.of(context).highlightColor,
size: 80.0,
),
const SizedBox(height: 24.0),
Text(
'Upload an image to start',
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(
color:
Theme.of(context).highlightColor),
)
],
),
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: ElevatedButton(
onPressed: () {
fn_uploadImage();
},
child: const Text('Upload'),
),
),
],
),
),
),
);
}
/**
* 수정된 이미지를 받아서 기존 변수 _croppedFile에 수정된 이미지로 덮어씌움.
*/
Future<void> fn_cropImage() async {
if (_pickedFile != null) {
final croppedFile = await ImageCropper().cropImage(
sourcePath: _pickedFile!.path,
compressFormat: ImageCompressFormat.jpg,
compressQuality: 100,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
],
);
if (croppedFile != null) {
setState(() {
_croppedFile = croppedFile;
});
}
}
}
/**
* 이미지 업로드 함수
*/
Future<void> fn_uploadImage() async {
final pickedFile =
await ImagePicker().pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
_pickedFile = pickedFile;
});
}
}
void fn_clear() {
setState(() {
_pickedFile = null;
_croppedFile = null;
});
}
}
[Flutter] WebView사용시 Cookie 사용하기 (0) | 2022.09.30 |
---|---|
[Flutter] BottomNavigation에서 image 사용하는 예제 (0) | 2022.09.26 |
[Flutter] Facebook Login - Android (1) | 2022.09.20 |
[Flutter] Google Login - Android 구현 (0) | 2022.09.16 |
[Xcode + Flutter ] Naver Login IOS 구현 (1) | 2022.09.16 |