Flutter 특정 영역에서 이미지 크기/위치 조절하기 개발/Flutter2023. 5. 17. 20:09
일전에 이미지를 특정 영역에서 크기와 위치가 변경될 수 있는 기능을 만들어야 하는 일이 있었다.
당시 pub.dev에서 해당 기능이 구현되어 있는 패키지를 찾아보았지만 딱 맞는 것이 없었다.
결국 제스처로 크기(scale)와 x축 y축을 통해 위치를 자유롭게 조정할 수 있는 matrix_gesture_detector를 발견해서
아래 소스를 작성하여 원하는 기능을 구현할 수 있었다.
1. yaml을 작성한다.
path_provider: 2.0.1
matrix_gesture_detector: 0.1.0
2. moveImage.dart를 작성한다.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
import 'package:path_provider/path_provider.dart';
class moveImage extends StatefulWidget {
String path;
String fileType;
double width;
double height;
@override
_moveImageState createState() => _moveImageState();
moveImage({
@required this.path,
this.fileType = "storage",
this.width = 450,
this.height = 250,
Key key})
: super(key: key);
}
class _moveImageState extends State<moveImage> {
Map<String, dynamic> item = {};
@override
void initState() {
super.initState();
setImageFileInfo();
}
@override
Widget build(BuildContext context) {
return Container(
width: widget.width,
height: widget.height,
child: moveImage(this.item)
);
}
Widget moveImage(item) {
return new FutureBuilder<File>(
future: item["fileType"] == "assets" ? getImageFileFromAssets(item["path"]) : getFile(item["path"]),
builder: (context, snapshot) {
if (snapshot.data == null || item["matrix"] == null) {
return Container();
} else {
return MatrixGestureDetector(
shouldRotate: false,
shouldScale: true,
onMatrixUpdate: (m, tm, sm, rm) {
setState(() {
var maxWidth = widget.width;
var maxHeight = widget.height;
var width = item["width"];
var height = item["height"];
Matrix4 matrix = Matrix4.identity();
matrix = MatrixGestureDetector.compose(item["matrix"], tm, sm, rm);
Offset translation = MatrixGestureDetector.decomposeToValues(matrix).translation;
Size changeSize = MatrixUtils.transformRect(matrix, item["transformKey"].currentContext.findRenderObject().paintBounds).size;
var initialScale = item["initialScale"];
var changeWidth = changeSize.width;
var changeHeight = changeSize.height;
var chkRatio = changeHeight/maxHeight;
if (initialScale / 2.0 <= chkRatio && chkRatio <= initialScale + 2.0) {
Offset currentTranslation = MatrixGestureDetector.decomposeToValues(item["matrix"]).translation;
var fullHeight = height * maxWidth / width;
var fullChangeWidth = width * changeHeight / height;
var fullChangeHeight = height * changeWidth / width;
if (fullHeight >= maxHeight) {
if (translation.dy > 0 || translation.dx > 0
|| changeHeight + translation.dy < maxHeight || fullChangeWidth + translation.dx < maxWidth) {
var dy = translation.dy, dx = translation.dx;
if (changeHeight >= maxHeight) {
if (translation.dy > 0) {
dy = 0.0;
}
if (changeHeight + translation.dy < maxHeight) {
if (changeHeight > maxHeight) {
dy = maxHeight - changeHeight;
} else {
dy = currentTranslation.dy;
}
}
} else {
if (maxHeight < changeHeight + translation.dy) {
dy = maxHeight - changeHeight;
} else if (0.0 > currentTranslation.dy + translation.dy) {
dy = 0.0;
} else {
dy = translation.dy;
}
}
if (fullChangeWidth >= maxWidth) {
if (translation.dx > 0){
dx = 0.0;
}
if (fullChangeWidth + translation.dx < maxWidth) {
if (fullChangeWidth > maxWidth) {
dx = maxWidth - fullChangeWidth;
} else {
dx = currentTranslation.dx;
}
}
} else {
if (maxWidth < fullChangeWidth + translation.dx) {
dx = maxWidth - fullChangeWidth;
} else if (0.0 > currentTranslation.dx + translation.dx) {
dx = 0.0;
} else {
dx = translation.dx;
}
}
tm = translateMatrix(new Offset(dx - currentTranslation.dx, dy - currentTranslation.dy));
matrix = MatrixGestureDetector.compose(item["matrix"], tm, sm, rm);
}
} else {
if (translation.dy > 0 || translation.dx > 0
|| fullChangeHeight + translation.dy < maxHeight || changeWidth + translation.dx < maxWidth) {
var dy = translation.dy, dx = translation.dx;
if (fullChangeHeight >= maxHeight) {
if (translation.dy > 0) {
dy = 0.0;
}
if (fullChangeHeight + translation.dy < maxHeight) {
if (fullChangeHeight > maxHeight) {
dy = maxHeight - fullChangeHeight;
} else {
dy = currentTranslation.dy;
}
}
} else {
if (maxHeight < fullChangeHeight + translation.dy) {
dy = maxHeight - fullChangeHeight;
} else if (0.0 > currentTranslation.dy + translation.dy) {
dy = 0.0;
} else {
dy = translation.dy;
}
}
if (changeWidth >= maxWidth) {
if (translation.dx > 0){
dx = 0.0;
}
if (changeWidth + translation.dx < maxWidth) {
if (changeWidth > maxWidth) {
dx = maxWidth - changeWidth;
} else {
dx = currentTranslation.dx;
}
}
} else {
if (maxWidth < changeWidth + translation.dx) {
dx = maxWidth - changeWidth;
} else if (0.0 > currentTranslation.dx + translation.dx) {
dx = 0.0;
} else {
dx = translation.dx;
}
}
tm = translateMatrix(new Offset(dx - currentTranslation.dx, dy - currentTranslation.dy));
matrix = MatrixGestureDetector.compose(item["matrix"], tm, sm, rm);
}
}
item["matrix"] = matrix;
}
});
}, child: new Transform( key: item["transformKey"], transform: item["matrix"],
child: new Image.file(snapshot.data, fit:BoxFit.contain, alignment: Alignment.topLeft,)
)
);
}
});
}
Matrix4 translateMatrix(Offset translation) {
var dx = translation.dx;
var dy = translation.dy;
return Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
Future setImageFileInfo() async {
this.item["path"] = widget.path;
this.item["fileType"] = widget.fileType;
if (this.item["transformKey"] == null) {
this.item["transformKey"] = new GlobalKey();
}
File image;
if (this.item["fileType"] == "assets") {
image = await getImageFileFromAssets(this.item["path"]);
} else {
image = new File(this.item["path"]);
}
var decodedImage = await decodeImageFromList(image.readAsBytesSync());
this.item["width"] = decodedImage.width.toDouble();
this.item["height"] = decodedImage.height.toDouble();
var maxWidth = widget.width;
var maxHeight = widget.height;
var width = this.item["width"];
var height = this.item["height"];
var widthRatio = (maxWidth/width);
var heightRatio = (maxHeight/height);
var initialScale = (heightRatio/widthRatio);
var fullHeight = height * maxWidth / width;
if (fullHeight >= maxHeight) {
initialScale = (widthRatio/heightRatio);
}
setState(() {
this.item["initialScale"] = initialScale;
this.item["matrix"] = Matrix4.identity().scaled(initialScale);
});
}
}
Future<File> getImageFileFromAssets(String path) async {
final byteData = await rootBundle.load('assets/$path');
String tmpPath = '${(await getTemporaryDirectory()).path}/$path';
final file = File(tmpPath);
await file.create(recursive: true);
await file.writeAsBytes(byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
return file;
}
Future<File> getFile(String path) async {
return File(path);
}
3. 작성한 moveImage.dart를 widget으로 사용할 화면의 dart 소스에서 불러온다.
moveImage(
path: "test.jpeg",
fileType: "assets",
),
위 예시는 assets에 있는 이미지 기준으로 작성되어 있으며
휴대폰에 있는 사진첩에서 이미지를 가져온다면(권한 관련 처리도 필수!)
fileType를 없애고 path에 이미지의 실제 경로(사진 선택 기능을 통해 알게 된 경로)를 넣어주면 된다.
'개발 > Flutter' 카테고리의 다른 글
Flutter TextField에서 숫자 입력 시 천 단위 마다 구분자(콤마) 표시되도록 하는 기능 구현하기 (0) | 2022.09.28 |
---|---|
javascript/flutter(dart)에서 Byte 단위 값을 알맞는 용량 형식(KB/MB...)으로 변환하기 (0) | 2022.08.23 |
Flutter에서 Flick 화면 만들기 (0) | 2022.08.03 |
Flutter에서 TextField의 "붙여넣기" 도구 설명 이름을 한국어로 변경하는 방법 (0) | 2022.01.27 |
Flutter에서 new_version으로 업데이트 체크 시 Bad state: No element 오류 해결 (Android) (0) | 2021.12.27 |