달력

11

« 2024/11 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
반응형

일전에 이미지를 특정 영역에서 크기와 위치가 변경될 수 있는 기능을 만들어야 하는 일이 있었다.
당시 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에 이미지의 실제 경로(사진 선택 기능을 통해 알게 된 경로)를 넣어주면 된다.
 
 

 
 
참고 : https://pub.dev/packages/matrix_gesture_detector

반응형
:
Posted by 리샤씨