├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc └── README_ZH.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── crop_index.dart │ ├── image_result_page.dart │ ├── index.dart │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── gif └── 5e941f8d-39a2-45da-9c8c-9eb2f6513498.gif ├── lib ├── crop_box.dart └── src │ ├── box_border.dart │ ├── grid_line.dart │ ├── image_crop.dart │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test └── crop_box_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f30b7f4db93ee747cd727df747941a28ead25ff5 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "crop_box", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "example", 14 | "cwd": "example", 15 | "request": "launch", 16 | "type": "dart" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.0] - 2021-01-13. 2 | 3 | * Release new package, support all kinds of material cutting operation and region calculation. 4 | 5 | ## [0.1.1] - 2021-02-03. 6 | 7 | * update doc. 8 | 9 | ## [0.1.2] - 2021-03-16. 10 | 11 | * add Circle CropBox. Now you can use `cropBoxType: CropBoxType.Circle` to change the UI of CropBox,And the return params is also `Rect` type; 12 | 13 | ## [0.1.3] - 2021-03-22. 14 | 15 | * add `borderRadius` `borderWidth` and `needInnerBorder`; 16 | 17 | ## [0.1.4] - 2021-03-23. 18 | 19 | * add `gridLine` property; 20 | 21 | ## [0.1.5] - 2021-03-24. 22 | 23 | * add `CropBoxBorder cropBoxBorder` property, remove `borderColor` `borderRadius` `borderWidth` properties; 24 | 25 | ## [0.1.6] - 2021-03-26. 26 | 27 | * add `backgroundColor` property; 28 | 29 | ## [0.1.7] - 2021-03-26. 30 | 31 | * change mask color to `rgba(0,0,0,0.5)`; 32 | 33 | ## [0.1.8] - 2021-04-03. 34 | 35 | * add `ImageCrop.getResult` to get real image crop result Uint8List; 36 | 37 | ## [0.1.9] - 2021-04-12. 38 | 39 | * add `ImageCrop.getImageSize` to get real image size; 40 | 41 | ## [0.1.10] - 2021-04-25. 42 | 43 | * bug fix; 44 | 45 | ## [0.1.13] - 2021-04-25. 46 | 47 | * add `quality` and `size` to `ImageCrop.getResult`; 48 | 49 | ## [1.0.1] - 2021-05-08. 50 | 51 | * add `quality` and `size` to `ImageCrop.getResult`; 52 | 53 | ## [1.0.2] - 2021-06-02. 54 | 55 | * add `maskColor`; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 godaangel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Background: why develop this component? At present, video synthesis technology is used in our flitter app, which involves the clipping of video or picture materials. At present, the common components in the market are based on pictures, and basically use canvas for rendering and clipping, which does not meet our business needs, so we need to develop a clipping component ourselves. 2 | 3 | [中文文档](https://github.com/godaangel/flutter_crop_box/blob/master/doc/README_ZH.md) 4 | 5 | ## Demo 6 | 7 | ![](https://github.com/godaangel/flutter_crop_box/blob/master/gif/5e941f8d-39a2-45da-9c8c-9eb2f6513498.gif) 8 | 9 | ## Demand analysis 10 | 11 | At the beginning of requirement design, considering the habit of gesture and referring to most editing tools, the following requirements are defined: 12 | 13 | - The clipping Box **is fixed** in a position on the screen. The position and size of the material can be adjusted by **dragging with one finger and zooming with two fingers** to frame the clipping range 14 | 15 | - The minimum edge of the material cannot be less than the corresponding edge of the clipping box, that is, the clipping Box **can only move relatively within the range of the material** 16 | 17 | - The types of supporting materials include **pictures and videos** 18 | 19 | ## Parameter 20 | 21 | | name | type | desc | default | 22 | | --- | --- | --- | --- | 23 | | cropRect | Rect | If you do not fill in the initial clipping region, it will be filled and centered by default, which is similar to cover | - | 24 | | clipSize | Size | Size of material to be cut | Required | 25 | | cropRatio | Size | Crop box scale, default`16:9` | `Size(16, 9)` | 26 | | child | Widget | Material to be cut | Required | 27 | | maxCropSize | Size | The maximum width and height of the current scale of the clipping box is mainly used when the size of the clipping box needs to be adjusted actively. If there is no special requirement, it does not need to be configured | Calculate based on parent component | 28 | | maxScale | Double | Maximum size allowed to enlarge | `10.0` | 29 | | needInnerBorder | bool | Whether the inner border decoration is required in the square mode (if there are rounded corners, it will not be displayed) | `false` | 30 | | cropBoxType | CropBoxType | Crop box style, default square, can be changed to circle. If it is a circle, the value set by `CropRatio` will be invalid and forced to `1:1` | CropBoxType.Square | 31 | | gridLine | GridLine | Clipping gridlines | - | 32 | | backgroundColor | Color | Background Color | `Color(0xff141414)` | 33 | | maskColor | Color | Mask Color | `Color.fromRGBO(0, 0, 0, 0.5)` | 34 | | cropBoxBorder | CropBoxBorder | Crop box style, including color, width and rounded corner information | `cropBoxBorder.width = 2` `cropBoxBorder.color = Colors.white` `cropBoxBorder.radius = Radius.circular(0)` | 35 | | cropRectUpdateStart | Function | Callback when crop region begins to change | - | 36 | | cropRectUpdate | Function(Rect rect) | Callback when clipping region changes | - | 37 | | cropRectUpdateEnd | Function(Rect rect) | Callback when clipping region end | Required | 38 | 39 | ## Demo code 40 | 41 | > Can see `example` in github 42 | 43 | #### git 44 | ```yaml 45 | crop_box: 46 | git: 47 | url: https://github.com/godaangel/flutter_crop_box.git 48 | ``` 49 | 50 | #### pub.dev 51 | ```yaml 52 | crop_box: ^0.1.5 53 | ``` 54 | 55 | #### Code 56 | ```dart 57 | import 'package:crop_box/crop_box.dart'; 58 | 59 | // ... 60 | 61 | CropBox( 62 | // cropRect: Rect.fromLTRB(1 - 0.4083, 0.162, 1, 0.3078), // 2.4倍 随机位置 63 | // cropRect: Rect.fromLTRB(0, 0, 0.4083, 0.1457), //2.4倍,都是0,0 64 | cropRect: Rect.fromLTRB(0, 0, 1, 0.3572), // 1倍 65 | clipSize: Size(200, 315), 66 | cropRatio: Size(16, 9), 67 | cropRectUpdateEnd: (rect) { 68 | print("rect final $rect"); 69 | }, 70 | cropRectUpdate: (rect) { 71 | print("rect change $rect"); 72 | }, 73 | child: Image.network( 74 | "https://img1.maka.im/materialStore/beijingshejia/tupianbeijinga/9/M_7TNT6NIM/M_7TNT6NIM_v1.jpg", 75 | width: double.infinity, 76 | height: double.infinity, 77 | fit: BoxFit.cover, 78 | loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) { 79 | if (loadingProgress == null) 80 | return child; 81 | return Center( 82 | child: CircularProgressIndicator( 83 | value: loadingProgress.expectedTotalBytes != null 84 | ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes 85 | : null, 86 | ), 87 | ); 88 | }, 89 | ), 90 | ) 91 | ``` 92 | 93 | ### Real Image Crop 94 | 95 | You can Use `ImageCrop.getResult` to get real image bytes. 96 | 97 | > more details in `example` 98 | 99 | ```dart 100 | /// get origin image uint8List 101 | Uint8List bytes = (await NetworkAssetBundle(Uri.parse(imageUrl)) 102 | .load(imageUrl)) 103 | .buffer 104 | .asUint8List(); 105 | /// get result uint8List 106 | Uint8List result = await ImageCrop.getResult( 107 | clipRect: _resultRect, 108 | image: bytes 109 | ); 110 | ``` 111 | 112 | ## TODO 113 | 114 | * [x] Dynamically transform crop box scale 115 | 116 | * [x] Support circle clipping box drawing 117 | 118 | * [ ] Optimize boundary calculation code 119 | 120 | * [x] Support the drawing of fillet clipping box 121 | 122 | * [ ] Support rotation 123 | 124 | * [x] Support Gridlines 125 | 126 | * [x] Real image crop to Uint8List 127 | -------------------------------------------------------------------------------- /doc/README_ZH.md: -------------------------------------------------------------------------------- 1 | > 背景:为啥要开发这个组件呢?因为目前在我们的flutter app中,用到了视频合成技术,这里就涉及到视频或者图片素材的裁剪,目前市面上普遍的组件都是基于图片的,并且基本上都是使用canvas进行渲染和裁剪,不太符合我们的业务需求,所以要自己开发一个裁剪组件。 2 | 3 | ## 效果展示 4 | ![](https://github.com/godaangel/flutter_crop_box/blob/master/gif/5e941f8d-39a2-45da-9c8c-9eb2f6513498.gif) 5 | 6 | ## 需求分析 7 | 在移动端,基本都是用手势操作,所以在需求设计之初,就考虑到手势的习惯,以及参考大部分编辑工具,定义出了以下几个需求点: 8 | - 裁剪框**固定**在屏幕上的一个位置,通过**单指拖动,双指缩放**的形式调整素材位置和大小,来框定裁剪范围 9 | - 素材的最小边不能小于裁剪框上与其对应的边,即裁剪框**只能相对在素材范围内移动** 10 | - 支持素材的类型包含**图片和视频** 11 | 12 | ## 参数设计 13 | 14 | | 参数名 | 类型 | 描述 | 默认值 | 15 | | --- | --- | --- | --- | 16 | | cropRect | Rect | 初始裁剪区域,如果不填,默认会填充并居中,表现形式类似cover | - | 17 | | clipSize | Size | 待裁剪素材的尺寸 | 必填 | 18 | | cropRatio | Size | 裁剪框比例,默认`16:9` | `Size(16, 9)` | 19 | | child | Widget | 待裁剪素材 | 必填 | 20 | | maxCropSize | Size | 裁剪框当前比例下最大宽高,主要是用于需要主动调整裁剪框大小时使用 如果没有特殊需求,不需要配置 | 根据父组件计算 | 21 | | maxScale | Double | 允许放大的最大尺寸 | `10.0` | 22 | | needInnerBorder | bool | 方形模式下是否需要内边框修饰(如果有圆角,则不显示) | `false` | 23 | | cropBoxType | CropBoxType | 裁剪框样式,默认方形,可以换成圆形,如果是圆形,则`cropRatio`设置的值将失效,强制变为`1:1` | CropBoxType.Square | 24 | | gridLine | GridLine | 裁剪网格线 | - | 25 | | backgroundColor | Color | 背景色 | `Color(0xff141414)` | 26 | | maskColor | Color | 遮罩颜色 | `Color.fromRGBO(0, 0, 0, 0.5)` | 27 | | cropBoxBorder | CropBoxBorder | 裁剪框样式,包含颜色、宽度和圆角信息 | `cropBoxBorder.width = 2` `cropBoxBorder.color = Colors.white` `cropBoxBorder.radius = Radius.circular(0)` | 28 | | cropRectUpdateStart | Function | 裁剪区域开始变化时的回调 | - | 29 | | cropRectUpdate | Function(Rect rect) | 裁剪区域变化时的回调 | - | 30 | | cropRectUpdateEnd | Function(Rect rect) | 返回 | 必填 | 31 | 32 | ## 使用Demo 33 | > 可参考 `git` 的 `example`,可以直接运行 34 | 35 | #### git引入 36 | ```yaml 37 | crop_box: 38 | git: 39 | url: https://github.com/godaangel/flutter_crop_box.git 40 | ``` 41 | 42 | #### pub.dev引入 43 | ```yaml 44 | crop_box: ^0.1.6 45 | ``` 46 | 47 | #### 代码 48 | > 更多属性参考`example`代码 49 | ```dart 50 | import 'package:crop_box/crop_box.dart'; 51 | 52 | // ... 53 | 54 | CropBox( 55 | // cropRect: Rect.fromLTRB(1 - 0.4083, 0.162, 1, 0.3078), // 2.4倍 随机位置 56 | // cropRect: Rect.fromLTRB(0, 0, 0.4083, 0.1457), //2.4倍,都是0,0 57 | // cropBoxType: CropBoxType.Circle, 58 | cropRect: Rect.fromLTRB(0, 0, 1, 0.3572), // 1倍 59 | clipSize: Size(200, 315), 60 | cropRatio: Size(16, 9), 61 | cropRectUpdateEnd: (rect) { 62 | print("裁剪区域最终确定 $rect"); 63 | }, 64 | cropRectUpdate: (rect) { 65 | print("裁剪区域变化 $rect"); 66 | }, 67 | child: Image.network( 68 | "https://img1.maka.im/materialStore/beijingshejia/tupianbeijinga/9/M_7TNT6NIM/M_7TNT6NIM_v1.jpg", 69 | width: double.infinity, 70 | height: double.infinity, 71 | fit: BoxFit.cover, 72 | loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) { 73 | if (loadingProgress == null) 74 | return child; 75 | return Center( 76 | child: CircularProgressIndicator( 77 | value: loadingProgress.expectedTotalBytes != null 78 | ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes 79 | : null, 80 | ), 81 | ); 82 | }, 83 | ), 84 | ) 85 | ``` 86 | 87 | ### 导出图片 88 | 89 | 可以使用 `ImageCrop.getResult` 获取裁剪后图片的数据,并且使用这些数据去展示或导出图片 90 | 91 | > more details in `example` 92 | 93 | ```dart 94 | /// get origin image uint8List 95 | Uint8List bytes = (await NetworkAssetBundle(Uri.parse(imageUrl)) 96 | .load(imageUrl)) 97 | .buffer 98 | .asUint8List(); 99 | /// get result uint8List 100 | Uint8List result = await ImageCrop.getResult( 101 | clipRect: _resultRect, 102 | image: bytes 103 | ); 104 | ``` 105 | 106 | ## TODO 107 | 108 | * [x] 动态变换裁剪框比例 109 | * [x] 支持圆形裁剪框绘制 110 | * [ ] 优化边界计算代码 111 | * [x] 支持圆角裁剪框绘制 112 | * [ ] 支持旋转 113 | * [x] 支持网格线 114 | * [x] 支持导出图片 115 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f30b7f4db93ee747cd727df747941a28ead25ff5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | 6 | systemProp.http.proxyHost=127.0.0.1 7 | systemProp.http.proxyPort=12639 8 | systemProp.https.proxyHost=127.0.0.1 9 | systemProp.https.proxyPort=12639 10 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - image_editor (0.0.1): 4 | - Flutter 5 | - image_gallery_saver (1.5.0): 6 | - Flutter 7 | - image_picker (0.0.1): 8 | - Flutter 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - image_editor (from `.symlinks/plugins/image_editor/ios`) 13 | - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) 14 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 15 | 16 | EXTERNAL SOURCES: 17 | Flutter: 18 | :path: Flutter 19 | image_editor: 20 | :path: ".symlinks/plugins/image_editor/ios" 21 | image_gallery_saver: 22 | :path: ".symlinks/plugins/image_gallery_saver/ios" 23 | image_picker: 24 | :path: ".symlinks/plugins/image_picker/ios" 25 | 26 | SPEC CHECKSUMS: 27 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 28 | image_editor: c1d038630eedea60d2dee9c14f36aa66c7f9cfab 29 | image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2 30 | image_picker: 50e7c7ff960e5f58faa4d1f4af84a771c671bc4a 31 | 32 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 33 | 34 | COCOAPODS: 1.10.1 35 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | F703CDBC5EA7BFA0A05AC082 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A951B5BC79AC4933DE38E18 /* Pods_Runner.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 0A951B5BC79AC4933DE38E18 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 35 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 8DD009DC31863A0A5163208F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 9B5B2E376CDF7C30C1A9C31B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 49 | D011E50C08F5969127A22930 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | F703CDBC5EA7BFA0A05AC082 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 8DBC9F4CB7F9A2E29EB936F8 /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 0A951B5BC79AC4933DE38E18 /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 9740EEB11CF90186004384FC /* Flutter */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 76 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 77 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 78 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 79 | ); 80 | name = Flutter; 81 | sourceTree = ""; 82 | }; 83 | 97C146E51CF9000F007C117D = { 84 | isa = PBXGroup; 85 | children = ( 86 | 9740EEB11CF90186004384FC /* Flutter */, 87 | 97C146F01CF9000F007C117D /* Runner */, 88 | 97C146EF1CF9000F007C117D /* Products */, 89 | DA82EE1EFC12646F4F975A61 /* Pods */, 90 | 8DBC9F4CB7F9A2E29EB936F8 /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 97C146EF1CF9000F007C117D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 97C146EE1CF9000F007C117D /* Runner.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 97C146F01CF9000F007C117D /* Runner */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 106 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 107 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 108 | 97C147021CF9000F007C117D /* Info.plist */, 109 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 110 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 111 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 112 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 113 | ); 114 | path = Runner; 115 | sourceTree = ""; 116 | }; 117 | DA82EE1EFC12646F4F975A61 /* Pods */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | D011E50C08F5969127A22930 /* Pods-Runner.debug.xcconfig */, 121 | 9B5B2E376CDF7C30C1A9C31B /* Pods-Runner.release.xcconfig */, 122 | 8DD009DC31863A0A5163208F /* Pods-Runner.profile.xcconfig */, 123 | ); 124 | path = Pods; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 97C146ED1CF9000F007C117D /* Runner */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 133 | buildPhases = ( 134 | E297A4F05395FB4017928359 /* [CP] Check Pods Manifest.lock */, 135 | 9740EEB61CF901F6004384FC /* Run Script */, 136 | 97C146EA1CF9000F007C117D /* Sources */, 137 | 97C146EB1CF9000F007C117D /* Frameworks */, 138 | 97C146EC1CF9000F007C117D /* Resources */, 139 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 140 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 141 | EF8D9232D306D061B748AB39 /* [CP] Embed Pods Frameworks */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = Runner; 148 | productName = Runner; 149 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 97C146E61CF9000F007C117D /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 1020; 159 | ORGANIZATIONNAME = ""; 160 | TargetAttributes = { 161 | 97C146ED1CF9000F007C117D = { 162 | CreatedOnToolsVersion = 7.3.1; 163 | LastSwiftMigration = 1100; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 168 | compatibilityVersion = "Xcode 9.3"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = 97C146E51CF9000F007C117D; 176 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 97C146ED1CF9000F007C117D /* Runner */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | 97C146EC1CF9000F007C117D /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 191 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 192 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 193 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXShellScriptBuildPhase section */ 200 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "Thin Binary"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 213 | }; 214 | 9740EEB61CF901F6004384FC /* Run Script */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Run Script"; 222 | outputPaths = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 227 | }; 228 | E297A4F05395FB4017928359 /* [CP] Check Pods Manifest.lock */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputFileListPaths = ( 234 | ); 235 | inputPaths = ( 236 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 237 | "${PODS_ROOT}/Manifest.lock", 238 | ); 239 | name = "[CP] Check Pods Manifest.lock"; 240 | outputFileListPaths = ( 241 | ); 242 | outputPaths = ( 243 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | shellPath = /bin/sh; 247 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 248 | showEnvVarsInLog = 0; 249 | }; 250 | EF8D9232D306D061B748AB39 /* [CP] Embed Pods Frameworks */ = { 251 | isa = PBXShellScriptBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | inputFileListPaths = ( 256 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 257 | ); 258 | name = "[CP] Embed Pods Frameworks"; 259 | outputFileListPaths = ( 260 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | shellPath = /bin/sh; 264 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 265 | showEnvVarsInLog = 0; 266 | }; 267 | /* End PBXShellScriptBuildPhase section */ 268 | 269 | /* Begin PBXSourcesBuildPhase section */ 270 | 97C146EA1CF9000F007C117D /* Sources */ = { 271 | isa = PBXSourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 275 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXSourcesBuildPhase section */ 280 | 281 | /* Begin PBXVariantGroup section */ 282 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 283 | isa = PBXVariantGroup; 284 | children = ( 285 | 97C146FB1CF9000F007C117D /* Base */, 286 | ); 287 | name = Main.storyboard; 288 | sourceTree = ""; 289 | }; 290 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 291 | isa = PBXVariantGroup; 292 | children = ( 293 | 97C147001CF9000F007C117D /* Base */, 294 | ); 295 | name = LaunchScreen.storyboard; 296 | sourceTree = ""; 297 | }; 298 | /* End PBXVariantGroup section */ 299 | 300 | /* Begin XCBuildConfiguration section */ 301 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ALWAYS_SEARCH_USER_PATHS = NO; 305 | CLANG_ANALYZER_NONNULL = YES; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 311 | CLANG_WARN_BOOL_CONVERSION = YES; 312 | CLANG_WARN_COMMA = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 315 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 330 | COPY_PHASE_STRIP = NO; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | ENABLE_NS_ASSERTIONS = NO; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | GCC_C_LANGUAGE_STANDARD = gnu99; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 337 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 338 | GCC_WARN_UNDECLARED_SELECTOR = YES; 339 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 340 | GCC_WARN_UNUSED_FUNCTION = YES; 341 | GCC_WARN_UNUSED_VARIABLE = YES; 342 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 343 | MTL_ENABLE_DEBUG_INFO = NO; 344 | SDKROOT = iphoneos; 345 | SUPPORTED_PLATFORMS = iphoneos; 346 | TARGETED_DEVICE_FAMILY = "1,2"; 347 | VALIDATE_PRODUCT = YES; 348 | }; 349 | name = Profile; 350 | }; 351 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 352 | isa = XCBuildConfiguration; 353 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 354 | buildSettings = { 355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 356 | CLANG_ENABLE_MODULES = YES; 357 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 358 | ENABLE_BITCODE = NO; 359 | FRAMEWORK_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "$(PROJECT_DIR)/Flutter", 362 | ); 363 | INFOPLIST_FILE = Runner/Info.plist; 364 | LD_RUNPATH_SEARCH_PATHS = ( 365 | "$(inherited)", 366 | "@executable_path/Frameworks", 367 | ); 368 | LIBRARY_SEARCH_PATHS = ( 369 | "$(inherited)", 370 | "$(PROJECT_DIR)/Flutter", 371 | ); 372 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 373 | PRODUCT_NAME = "$(TARGET_NAME)"; 374 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 375 | SWIFT_VERSION = 5.0; 376 | VERSIONING_SYSTEM = "apple-generic"; 377 | }; 378 | name = Profile; 379 | }; 380 | 97C147031CF9000F007C117D /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 386 | CLANG_CXX_LIBRARY = "libc++"; 387 | CLANG_ENABLE_MODULES = YES; 388 | CLANG_ENABLE_OBJC_ARC = YES; 389 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 390 | CLANG_WARN_BOOL_CONVERSION = YES; 391 | CLANG_WARN_COMMA = YES; 392 | CLANG_WARN_CONSTANT_CONVERSION = YES; 393 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 401 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 404 | CLANG_WARN_STRICT_PROTOTYPES = YES; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNREACHABLE_CODE = YES; 407 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 408 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 409 | COPY_PHASE_STRIP = NO; 410 | DEBUG_INFORMATION_FORMAT = dwarf; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | ENABLE_TESTABILITY = YES; 413 | GCC_C_LANGUAGE_STANDARD = gnu99; 414 | GCC_DYNAMIC_NO_PIC = NO; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_OPTIMIZATION_LEVEL = 0; 417 | GCC_PREPROCESSOR_DEFINITIONS = ( 418 | "DEBUG=1", 419 | "$(inherited)", 420 | ); 421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 423 | GCC_WARN_UNDECLARED_SELECTOR = YES; 424 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 425 | GCC_WARN_UNUSED_FUNCTION = YES; 426 | GCC_WARN_UNUSED_VARIABLE = YES; 427 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 428 | MTL_ENABLE_DEBUG_INFO = YES; 429 | ONLY_ACTIVE_ARCH = YES; 430 | SDKROOT = iphoneos; 431 | TARGETED_DEVICE_FAMILY = "1,2"; 432 | }; 433 | name = Debug; 434 | }; 435 | 97C147041CF9000F007C117D /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ALWAYS_SEARCH_USER_PATHS = NO; 439 | CLANG_ANALYZER_NONNULL = YES; 440 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 441 | CLANG_CXX_LIBRARY = "libc++"; 442 | CLANG_ENABLE_MODULES = YES; 443 | CLANG_ENABLE_OBJC_ARC = YES; 444 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 445 | CLANG_WARN_BOOL_CONVERSION = YES; 446 | CLANG_WARN_COMMA = YES; 447 | CLANG_WARN_CONSTANT_CONVERSION = YES; 448 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 449 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 450 | CLANG_WARN_EMPTY_BODY = YES; 451 | CLANG_WARN_ENUM_CONVERSION = YES; 452 | CLANG_WARN_INFINITE_RECURSION = YES; 453 | CLANG_WARN_INT_CONVERSION = YES; 454 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 455 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 456 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 457 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 458 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 459 | CLANG_WARN_STRICT_PROTOTYPES = YES; 460 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 461 | CLANG_WARN_UNREACHABLE_CODE = YES; 462 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 463 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 464 | COPY_PHASE_STRIP = NO; 465 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 466 | ENABLE_NS_ASSERTIONS = NO; 467 | ENABLE_STRICT_OBJC_MSGSEND = YES; 468 | GCC_C_LANGUAGE_STANDARD = gnu99; 469 | GCC_NO_COMMON_BLOCKS = YES; 470 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 471 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 472 | GCC_WARN_UNDECLARED_SELECTOR = YES; 473 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 474 | GCC_WARN_UNUSED_FUNCTION = YES; 475 | GCC_WARN_UNUSED_VARIABLE = YES; 476 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 477 | MTL_ENABLE_DEBUG_INFO = NO; 478 | SDKROOT = iphoneos; 479 | SUPPORTED_PLATFORMS = iphoneos; 480 | SWIFT_COMPILATION_MODE = wholemodule; 481 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | VALIDATE_PRODUCT = YES; 484 | }; 485 | name = Release; 486 | }; 487 | 97C147061CF9000F007C117D /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 490 | buildSettings = { 491 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 492 | CLANG_ENABLE_MODULES = YES; 493 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 494 | DEVELOPMENT_TEAM = F3228W89AS; 495 | ENABLE_BITCODE = NO; 496 | FRAMEWORK_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "$(PROJECT_DIR)/Flutter", 499 | ); 500 | INFOPLIST_FILE = Runner/Info.plist; 501 | LD_RUNPATH_SEARCH_PATHS = ( 502 | "$(inherited)", 503 | "@executable_path/Frameworks", 504 | ); 505 | LIBRARY_SEARCH_PATHS = ( 506 | "$(inherited)", 507 | "$(PROJECT_DIR)/Flutter", 508 | ); 509 | PRODUCT_BUNDLE_IDENTIFIER = com.godaangel.crop.box; 510 | PRODUCT_NAME = "$(TARGET_NAME)"; 511 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 512 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 513 | SWIFT_VERSION = 5.0; 514 | VERSIONING_SYSTEM = "apple-generic"; 515 | }; 516 | name = Debug; 517 | }; 518 | 97C147071CF9000F007C117D /* Release */ = { 519 | isa = XCBuildConfiguration; 520 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 521 | buildSettings = { 522 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 523 | CLANG_ENABLE_MODULES = YES; 524 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 525 | DEVELOPMENT_TEAM = F3228W89AS; 526 | ENABLE_BITCODE = NO; 527 | FRAMEWORK_SEARCH_PATHS = ( 528 | "$(inherited)", 529 | "$(PROJECT_DIR)/Flutter", 530 | ); 531 | INFOPLIST_FILE = Runner/Info.plist; 532 | LD_RUNPATH_SEARCH_PATHS = ( 533 | "$(inherited)", 534 | "@executable_path/Frameworks", 535 | ); 536 | LIBRARY_SEARCH_PATHS = ( 537 | "$(inherited)", 538 | "$(PROJECT_DIR)/Flutter", 539 | ); 540 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.crop.box; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 543 | SWIFT_VERSION = 5.0; 544 | VERSIONING_SYSTEM = "apple-generic"; 545 | }; 546 | name = Release; 547 | }; 548 | /* End XCBuildConfiguration section */ 549 | 550 | /* Begin XCConfigurationList section */ 551 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 552 | isa = XCConfigurationList; 553 | buildConfigurations = ( 554 | 97C147031CF9000F007C117D /* Debug */, 555 | 97C147041CF9000F007C117D /* Release */, 556 | 249021D3217E4FDB00AE95B9 /* Profile */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | defaultConfigurationName = Release; 560 | }; 561 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 97C147061CF9000F007C117D /* Debug */, 565 | 97C147071CF9000F007C117D /* Release */, 566 | 249021D4217E4FDB00AE95B9 /* Profile */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | /* End XCConfigurationList section */ 572 | }; 573 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 574 | } 575 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | App需要您的同意,才能访问相机 27 | NSMicrophoneUsageDescription 28 | App需要您的同意,才能访问麦克风 29 | NSPhotoLibraryUsageDescription 30 | App需要您的同意,才能访问相册 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/crop_index.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:example/image_result_page.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'dart:ui'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | 10 | import 'package:crop_box/crop_box.dart'; 11 | import 'package:image_picker/image_picker.dart'; 12 | 13 | enum ClipType { 14 | networkImage, 15 | localImage 16 | } 17 | 18 | class CropIndex extends StatefulWidget { 19 | final double width; 20 | final double height; 21 | final ClipType clipType; 22 | final Uint8List? localImageData; 23 | final String? imageUrl; 24 | CropIndex({Key? key, required this.width, required this.height, required this.clipType, this.localImageData, this.imageUrl}) : super(key: key); 25 | 26 | @override 27 | _CropIndexState createState() => _CropIndexState(); 28 | } 29 | 30 | class _CropIndexState extends State { 31 | Rect _resultRect = Rect.zero; 32 | /// 最大裁剪区域 会结合裁剪比例计算实际裁剪框范围 33 | Size _maxCropSize = Size(300, 300); 34 | /// 裁剪比例 35 | Size _cropRatio = Size(16, 9); 36 | /// 裁剪区域 37 | Rect? _cropRect; 38 | 39 | bool exportLoading = false; 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Scaffold( 44 | appBar: AppBar( 45 | title: Text('Crop Detail'), 46 | ), 47 | body: Container( 48 | child: Column( 49 | children: [ 50 | Expanded( 51 | child: CropBox( 52 | // cropRect: Rect.fromLTRB(1 - 0.4083, 0.162, 1, 0.3078), // 2.4倍 模拟随机位置 53 | // cropRect: Rect.fromLTRB(0, 0, 0.4083, 0.1457), //2.4倍,都是0,0 54 | // cropRect: Rect.fromLTRB(0, 0, 1, 0.3572), // 1倍 55 | // cropBoxType: CropBoxType.Circle, 56 | // borderColor: Colors.white, 57 | gridLine: GridLine(), 58 | cropRect: _cropRect, 59 | clipSize: Size(widget.width, widget.height), 60 | maxCropSize: _maxCropSize, 61 | cropRatio: _cropRatio, 62 | cropBoxBorder: CropBoxBorder( 63 | color: Colors.white, 64 | radius: Radius.circular(5), 65 | ), 66 | cropRectUpdateEnd: (rect) { 67 | _resultRect = rect; 68 | print("裁剪区域最终确定 $rect"); 69 | setState(() {}); 70 | }, 71 | cropRectUpdate: (rect) { 72 | _resultRect = rect; 73 | print("裁剪区域变化 $rect"); 74 | setState(() {}); 75 | }, 76 | child: widget.clipType == ClipType.networkImage ? Image.network( 77 | widget.imageUrl!, 78 | width: double.infinity, 79 | height: double.infinity, 80 | fit: BoxFit.contain, 81 | loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { 82 | if (loadingProgress == null) 83 | return child; 84 | return Center( 85 | child: CircularProgressIndicator( 86 | value: loadingProgress.expectedTotalBytes != null 87 | ? loadingProgress.cumulativeBytesLoaded.toDouble() / loadingProgress.expectedTotalBytes!.toDouble() 88 | : null, 89 | ), 90 | ); 91 | }, 92 | ) : Image.memory( 93 | widget.localImageData!, 94 | width: double.infinity, 95 | height: double.infinity, 96 | fit: BoxFit.contain, 97 | ), 98 | ), 99 | ), 100 | SizedBox( 101 | height: 36.w, 102 | ), 103 | Container( 104 | padding: EdgeInsets.only( 105 | bottom: MediaQuery.of(context).padding.bottom, 106 | ), 107 | child: Container( 108 | height: 250.w, 109 | padding: EdgeInsets.all(16.w), 110 | child: Row( 111 | children: [ 112 | Expanded( 113 | flex: 0, 114 | child: Container( 115 | width: 150, 116 | child: Column( 117 | crossAxisAlignment: CrossAxisAlignment.start, 118 | children: [ 119 | Text( 120 | "裁剪区域: ", 121 | style: TextStyle( 122 | fontFamily: "PingFang SC", 123 | fontSize: 28.sp, 124 | fontStyle: FontStyle.normal, 125 | fontWeight: FontWeight.w500, 126 | ), 127 | ), 128 | Text( 129 | "left: ${_resultRect.left.toStringAsFixed(5)}", 130 | style: TextStyle( 131 | fontFamily: "PingFang SC", 132 | fontSize: 28.sp, 133 | fontStyle: FontStyle.normal, 134 | fontWeight: FontWeight.w500, 135 | ), 136 | ), 137 | Text( 138 | "top: ${_resultRect.top.toStringAsFixed(5)}", 139 | style: TextStyle( 140 | fontFamily: "PingFang SC", 141 | fontSize: 28.sp, 142 | fontStyle: FontStyle.normal, 143 | fontWeight: FontWeight.w500, 144 | ), 145 | ), 146 | Text( 147 | "right: ${_resultRect.right.toStringAsFixed(5)}", 148 | style: TextStyle( 149 | fontFamily: "PingFang SC", 150 | fontSize: 28.sp, 151 | fontStyle: FontStyle.normal, 152 | fontWeight: FontWeight.w500, 153 | ), 154 | ), 155 | Text( 156 | "bottom: ${_resultRect.bottom.toStringAsFixed(5)}", 157 | style: TextStyle( 158 | fontFamily: "PingFang SC", 159 | fontSize: 28.sp, 160 | fontStyle: FontStyle.normal, 161 | fontWeight: FontWeight.w500, 162 | ), 163 | ), 164 | ], 165 | ), 166 | ), 167 | ), 168 | Expanded( 169 | child: Column( 170 | children: [ 171 | CupertinoButton( 172 | color: Colors.blue, 173 | child: Text( 174 | "1:1", 175 | style: TextStyle( 176 | fontFamily: "PingFang SC", 177 | fontSize: 28.sp, 178 | fontStyle: FontStyle.normal, 179 | fontWeight: FontWeight.w500, 180 | ), 181 | ), 182 | onPressed: () { 183 | setState(() { 184 | _cropRatio = Size(1, 1); 185 | }); 186 | }, 187 | ), 188 | SizedBox( 189 | height: 10, 190 | ), 191 | CupertinoButton( 192 | color: Colors.blue, 193 | child: Text( 194 | exportLoading ? "Exporting" : "Export", 195 | style: TextStyle( 196 | fontFamily: "PingFang SC", 197 | fontSize: 28.sp, 198 | fontStyle: FontStyle.normal, 199 | fontWeight: FontWeight.w500, 200 | ), 201 | ), 202 | onPressed: exportLoading ? null : () async { 203 | setState(() { 204 | exportLoading = true; 205 | }); 206 | 207 | /// get origin image uint8List 208 | Uint8List bytes; 209 | if(widget.clipType == ClipType.networkImage) { 210 | bytes = (await NetworkAssetBundle(Uri.parse(widget.imageUrl!)) 211 | .load(widget.imageUrl!)) 212 | .buffer 213 | .asUint8List(); 214 | }else{ 215 | bytes = widget.localImageData!; 216 | } 217 | /// get result uint8List 218 | Uint8List result = (await ImageCrop.getResult( 219 | clipRect: _resultRect, 220 | image: bytes 221 | ))!; 222 | 223 | setState(() { 224 | exportLoading = false; 225 | }); 226 | 227 | /// if you need to export to gallery 228 | /// you can use this https://pub.dev/packages/image_gallery_saver 229 | /// ... your export code ... 230 | /// 231 | /// my code is only to show result in other page 232 | Navigator.of(context).push(new MaterialPageRoute(builder: (_) { 233 | return ImageResultPage(imageBytes: result,); 234 | })); 235 | }, 236 | ), 237 | ], 238 | ), 239 | ) 240 | ], 241 | ), 242 | ), 243 | ), 244 | ], 245 | ), 246 | ), 247 | ); 248 | } 249 | } -------------------------------------------------------------------------------- /example/lib/image_result_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | 6 | class ImageResultPage extends StatefulWidget { 7 | final Uint8List imageBytes; 8 | ImageResultPage({Key? key, required this.imageBytes}) : super(key: key); 9 | 10 | @override 11 | _ImageResultPageState createState() => _ImageResultPageState(); 12 | } 13 | 14 | class _ImageResultPageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar( 19 | title: Text('Result'), 20 | ), 21 | body: Center( 22 | child: Container( 23 | width: 700.w, 24 | child: Image.memory( 25 | widget.imageBytes, 26 | fit: BoxFit.contain, 27 | ), 28 | ), 29 | ), 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /example/lib/index.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:example/crop_index.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | import 'package:crop_box/crop_box.dart'; 8 | 9 | 10 | 11 | class PageIndex extends StatefulWidget { 12 | PageIndex({Key? key}) : super(key: key); 13 | 14 | @override 15 | _PageIndexState createState() => _PageIndexState(); 16 | } 17 | 18 | class _PageIndexState extends State { 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: Text('Crop Box'), 25 | ), 26 | body: Center( 27 | child: Column( 28 | children: [ 29 | SizedBox( 30 | height: 100, 31 | ), 32 | /// NetWork Image 33 | TextButton( 34 | onPressed: () { 35 | Navigator.push(context, MaterialPageRoute(builder: (context) => CropIndex( 36 | width: 200, 37 | height: 315, 38 | imageUrl: "https://img1.maka.im/materialStore/beijingshejia/tupianbeijinga/9/M_7TNT6NIM/M_7TNT6NIM_v1.jpg", 39 | clipType: ClipType.networkImage, 40 | ))); 41 | }, 42 | child: Text('Test NetWork Image'), 43 | ), 44 | /// Local Image 45 | TextButton( 46 | onPressed: () async { 47 | PickedFile? pickedFile = await ImagePicker().getImage(source: ImageSource.gallery); 48 | if(pickedFile != null) { 49 | Uint8List bytes = await pickedFile.readAsBytes(); 50 | Size imageSize = await ImageCrop.getImageSize(bytes); 51 | Navigator.push(context, MaterialPageRoute(builder: (context) => CropIndex( 52 | width: imageSize.width, 53 | height: imageSize.height, 54 | localImageData: bytes, 55 | clipType: ClipType.localImage, 56 | ))); 57 | } 58 | }, 59 | child: Text('Choose Local Image'), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/index.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'dart:ui'; 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | 7 | void main() { 8 | runApp(MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return ScreenUtilInit( 15 | designSize: Size(750, 1334), 16 | builder: () { 17 | return MaterialApp( 18 | title: 'Flutter Demo', 19 | theme: ThemeData( 20 | primaryColor: Color(0xff44D7B6), 21 | visualDensity: VisualDensity.adaptivePlatformDensity, 22 | ), 23 | home: PageIndex(), 24 | ); 25 | }, 26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.1.1" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.6.1" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.2.0" 39 | clock: 40 | dependency: transitive 41 | description: 42 | name: clock 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.15.0" 53 | convert: 54 | dependency: transitive 55 | description: 56 | name: convert 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.0" 60 | crop_box: 61 | dependency: "direct dev" 62 | description: 63 | path: ".." 64 | relative: true 65 | source: path 66 | version: "1.0.2" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.0.3" 74 | exif: 75 | dependency: transitive 76 | description: 77 | name: exif 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.2.0" 81 | fake_async: 82 | dependency: transitive 83 | description: 84 | name: fake_async 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.2.0" 88 | flutter: 89 | dependency: "direct main" 90 | description: flutter 91 | source: sdk 92 | version: "0.0.0" 93 | flutter_plugin_android_lifecycle: 94 | dependency: transitive 95 | description: 96 | name: flutter_plugin_android_lifecycle 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "2.0.2" 100 | flutter_screenutil: 101 | dependency: "direct main" 102 | description: 103 | name: flutter_screenutil 104 | url: "https://pub.dartlang.org" 105 | source: hosted 106 | version: "5.0.0+2" 107 | flutter_test: 108 | dependency: "direct dev" 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | flutter_web_plugins: 113 | dependency: transitive 114 | description: flutter 115 | source: sdk 116 | version: "0.0.0" 117 | hashcodes: 118 | dependency: transitive 119 | description: 120 | name: hashcodes 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "2.0.0" 124 | http: 125 | dependency: transitive 126 | description: 127 | name: http 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.13.3" 131 | http_parser: 132 | dependency: transitive 133 | description: 134 | name: http_parser 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "4.0.0" 138 | image_editor: 139 | dependency: transitive 140 | description: 141 | name: image_editor 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.0.0" 145 | image_gallery_saver: 146 | dependency: "direct main" 147 | description: 148 | name: image_gallery_saver 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.6.9" 152 | image_picker: 153 | dependency: "direct main" 154 | description: 155 | name: image_picker 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "0.7.5+3" 159 | image_picker_for_web: 160 | dependency: transitive 161 | description: 162 | name: image_picker_for_web 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.0" 166 | image_picker_platform_interface: 167 | dependency: transitive 168 | description: 169 | name: image_picker_platform_interface 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "2.1.0" 173 | image_size_getter: 174 | dependency: "direct main" 175 | description: 176 | name: image_size_getter 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.0.0" 180 | js: 181 | dependency: transitive 182 | description: 183 | name: js 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "0.6.3" 187 | matcher: 188 | dependency: transitive 189 | description: 190 | name: matcher 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.12.10" 194 | meta: 195 | dependency: transitive 196 | description: 197 | name: meta 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.3.0" 201 | path: 202 | dependency: transitive 203 | description: 204 | name: path 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "1.8.0" 208 | pedantic: 209 | dependency: transitive 210 | description: 211 | name: pedantic 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.11.0" 215 | plugin_platform_interface: 216 | dependency: transitive 217 | description: 218 | name: plugin_platform_interface 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.0.0" 222 | sky_engine: 223 | dependency: transitive 224 | description: flutter 225 | source: sdk 226 | version: "0.0.99" 227 | source_span: 228 | dependency: transitive 229 | description: 230 | name: source_span 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "1.8.1" 234 | sprintf: 235 | dependency: transitive 236 | description: 237 | name: sprintf 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "6.0.0" 241 | stack_trace: 242 | dependency: transitive 243 | description: 244 | name: stack_trace 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "1.10.0" 248 | stream_channel: 249 | dependency: transitive 250 | description: 251 | name: stream_channel 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "2.1.0" 255 | string_scanner: 256 | dependency: transitive 257 | description: 258 | name: string_scanner 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "1.1.0" 262 | term_glyph: 263 | dependency: transitive 264 | description: 265 | name: term_glyph 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "1.2.0" 269 | test_api: 270 | dependency: transitive 271 | description: 272 | name: test_api 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "0.3.0" 276 | typed_data: 277 | dependency: transitive 278 | description: 279 | name: typed_data 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "1.3.0" 283 | vector_math: 284 | dependency: transitive 285 | description: 286 | name: vector_math 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "2.1.0" 290 | sdks: 291 | dart: ">=2.12.0 <3.0.0" 292 | flutter: ">=2.0.0" 293 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.2 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | flutter_screenutil: ^5.0.0+2 27 | image_gallery_saver: ^1.6.9 28 | image_picker: ^0.7.4 29 | image_size_getter: ^1.0.0 30 | 31 | 32 | # The following adds the Cupertino Icons font to your application. 33 | # Use with the CupertinoIcons class for iOS style icons. 34 | cupertino_icons: ^1.0.3 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | crop_box: 40 | path: ../ 41 | 42 | # For information on the generic Dart part of this file, see the 43 | # following page: https://dart.dev/tools/pub/pubspec 44 | 45 | # The following section is specific to Flutter. 46 | flutter: 47 | 48 | # The following line ensures that the Material Icons font is 49 | # included with your application, so that you can use the icons in 50 | # the material Icons class. 51 | uses-material-design: true 52 | 53 | # To add assets to your application, add an assets section, like this: 54 | # assets: 55 | # - images/a_dot_burr.jpeg 56 | # - images/a_dot_ham.jpeg 57 | 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.dev/assets-and-images/#resolution-aware. 60 | 61 | # For details regarding adding assets from package dependencies, see 62 | # https://flutter.dev/assets-and-images/#from-packages 63 | 64 | # To add custom fonts to your application, add a fonts section here, 65 | # in this "flutter" section. Each entry in this list should have a 66 | # "family" key with the font family name, and a "fonts" key with a 67 | # list giving the asset and other descriptors for the font. For 68 | # example: 69 | # fonts: 70 | # - family: Schyler 71 | # fonts: 72 | # - asset: fonts/Schyler-Regular.ttf 73 | # - asset: fonts/Schyler-Italic.ttf 74 | # style: italic 75 | # - family: Trajan Pro 76 | # fonts: 77 | # - asset: fonts/TrajanPro.ttf 78 | # - asset: fonts/TrajanPro_Bold.ttf 79 | # weight: 700 80 | # 81 | # For details regarding fonts from package dependencies, 82 | # see https://flutter.dev/custom-fonts/#from-packages 83 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /gif/5e941f8d-39a2-45da-9c8c-9eb2f6513498.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godaangel/flutter_crop_box/dffe2dc08d2c4827bbc50273dc072384c2201732/gif/5e941f8d-39a2-45da-9c8c-9eb2f6513498.gif -------------------------------------------------------------------------------- /lib/crop_box.dart: -------------------------------------------------------------------------------- 1 | library crop_box; 2 | 3 | export 'src/main.dart'; 4 | export 'src/grid_line.dart'; 5 | export 'src/box_border.dart'; 6 | export 'src/image_crop.dart'; -------------------------------------------------------------------------------- /lib/src/box_border.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CropBoxBorder { 4 | /// 方形模式下,边框的圆角 5 | final Radius? radius; 6 | Radius get noNullRaidus => radius ?? Radius.circular(0); 7 | 8 | /// 边框宽度 9 | /// 10 | /// 默认 [2] 11 | final double width; 12 | 13 | /// 边框颜色 14 | /// 15 | /// 默认 [Colors.white] 16 | final Color color; 17 | 18 | CropBoxBorder({this.radius, this.width = 2, this.color = Colors.white}); 19 | } -------------------------------------------------------------------------------- /lib/src/grid_line.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GridLine { 4 | /// 网格线颜色 5 | /// 默认 `Colors.white` 6 | Color color; 7 | /// 网格线宽度 8 | double width; 9 | /// 网格线padding 10 | EdgeInsets? padding; 11 | 12 | /// 网格线 13 | GridLine({this.color = Colors.white, this.width = 0.5, this.padding}); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/image_crop.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_editor/image_editor.dart'; 5 | import 'package:image_size_getter/image_size_getter.dart' as imageGetter; 6 | import 'package:exif/exif.dart'; 7 | 8 | class ImageCropOutputFormatQuality { 9 | static const int VeryHigh = 100; 10 | static const int High = 75; 11 | static const int Middle = 50; 12 | static const int Low = 25; 13 | static const int VeryLow = 5; 14 | } 15 | class ImageCrop { 16 | /// get crop result image, type is Uint8List 17 | static Future getResult({required Rect clipRect, required Uint8List image, int? outputQuality, Size? outputSize}) async { 18 | 19 | final Size memoryImageSize = await getImageSize(image); 20 | final editorOption = ImageEditorOption(); 21 | 22 | editorOption.addOption(ClipOption( 23 | x: clipRect.left * memoryImageSize.width, 24 | y: clipRect.top * memoryImageSize.height, 25 | width: clipRect.width * memoryImageSize.width, 26 | height: clipRect.height * memoryImageSize.height 27 | )); 28 | 29 | if(outputSize != null) { 30 | editorOption.addOption(ScaleOption(outputSize.width.toInt(), outputSize.height.toInt())); 31 | } 32 | 33 | editorOption.outputFormat = OutputFormat.jpeg(outputQuality ?? ImageCropOutputFormatQuality.High); 34 | final result = await ImageEditor.editImage(image: image, imageEditorOption: editorOption); 35 | return result; 36 | } 37 | 38 | /// get image size with exif 39 | static Future getImageSize(Uint8List bytes) async { 40 | try { 41 | Map? data = 42 | await readExifFromBytes(bytes); 43 | double width = data?['EXIF ExifImageWidth']?.values?[0].toDouble(); 44 | double height = data?['EXIF ExifImageLength']?.values?[0].toDouble(); 45 | if(width > height) { 46 | if (data!['Image Orientation']!.printable!.contains('Horizontal')) { 47 | return Size(width.toDouble(), height); 48 | }else { 49 | return Size(height, width); 50 | } 51 | }else{ 52 | return Size(width, height); 53 | } 54 | } catch (e) { 55 | imageGetter.ImageInput imageInput = imageGetter.MemoryInput(bytes); 56 | double width = imageGetter.ImageSizeGetter.getSize(imageInput).width.toDouble(); 57 | double height = imageGetter.ImageSizeGetter.getSize(imageInput).height.toDouble(); 58 | final Size memoryImageSize = Size(width, height); 59 | return Size(memoryImageSize.width, memoryImageSize.height); 60 | } 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /lib/src/main.dart: -------------------------------------------------------------------------------- 1 | library crop_box; 2 | 3 | import 'dart:math'; 4 | import 'dart:ui'; 5 | 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'box_border.dart'; 10 | import 'grid_line.dart'; 11 | 12 | enum CropBoxType { 13 | Square, 14 | Circle 15 | } 16 | 17 | /// 回调函数的类型定义 18 | typedef _CropRectUpdate = void Function(Rect rect); 19 | 20 | class CropBox extends StatefulWidget { 21 | 22 | /// 初始裁剪区域(LTRB均是 0 到 1 的double类型) 23 | /// 24 | /// 如果不填,默认会填充并居中,表现形式类似cover 25 | final Rect? cropRect; 26 | /// 待裁剪素材的尺寸 27 | final Size clipSize; 28 | /// 子组件 29 | /// 30 | /// 一般是待裁剪素材 31 | final Widget child; 32 | /// 裁剪框比例 33 | /// 34 | /// 默认 16:9 35 | final Size? cropRatio; 36 | /// 裁剪框当前比例下最大宽高 37 | /// 38 | /// 主要是用于需要主动调整裁剪框大小时使用 如果没有特殊需求,不需要配置 39 | final Size? maxCropSize; 40 | /// 最大放大尺寸 41 | /// 42 | /// 允许放大的最大尺寸,默认10.0 43 | final double maxScale; 44 | /// 裁剪区域开始变化时的回调 45 | final Function? cropRectUpdateStart; 46 | /// 裁剪区域变化时的回调 47 | /// 48 | /// 可用于初始生成裁剪区域,以及手势触发的回调 49 | final _CropRectUpdate? cropRectUpdate; 50 | /// 裁剪区域停止变化时的回调函数,可以获得最终裁剪区域 51 | /// 52 | /// 返回值 `Rect rect` 为裁剪区域在素材上的比例 53 | /// 54 | /// `rect`的LTRB值均为0到1的`double`值,代表在本轴上的百分比位置 55 | /// 56 | /// 这个百分比只是LTRB分别相对于原素材宽高的百分比,各个LTRB之间这个**百分比值没有联系** 57 | /// 58 | /// LTRB的绝对值有比例关系,比例等于裁剪比例 59 | final _CropRectUpdate cropRectUpdateEnd; 60 | 61 | /// 裁剪框类型 62 | /// 63 | /// [cropBoxType] 有两种类型 64 | /// 65 | /// [CropBoxType.Square] 表示方形框 66 | /// [CropBoxType.Circle] 表示圆形框,圆形裁剪框模式下[cropRatio]会强制为`1:1`,且`needInnerBorder`和`borderRadius`不生效 67 | /// 68 | /// [cropBoxType] 默认值为 [CropBoxType.Square] 69 | final CropBoxType cropBoxType; 70 | 71 | /// 是否需要内边框 72 | /// 73 | /// default [false] 74 | final bool needInnerBorder; 75 | 76 | /// 网格线 77 | final GridLine? gridLine; 78 | 79 | /// 裁剪框边框样式 80 | /// 81 | /// 包含颜色、宽度、圆角等信息 82 | final CropBoxBorder? cropBoxBorder; 83 | 84 | /// 裁剪框背景颜色 85 | /// 86 | /// default [Color(0xff141414)] 87 | final Color? backgroundColor; 88 | 89 | /// 遮罩层颜色 90 | /// 91 | /// default [Color.fromRGBO(0, 0, 0, 0.5)] 92 | final Color? maskColor; 93 | 94 | /// ### 裁剪素材组件 95 | /// 96 | /// 通过传入裁剪素材宽高[clipSize],裁剪区域比例[cropRatio]以及待裁剪的内容[child],就可以生成裁剪器,支持手势移动、放大缩小操作 97 | /// 98 | /// 再通过[cropRectUpdateEnd]回调拿到裁剪区域的值,对应到素材进行裁剪操作 99 | /// 100 | /// {@tool dartpad --template=stateless_widget_material} 101 | /// 102 | /// 代码示例 103 | /// ```dart 104 | /// CropBox( 105 | /// // cropRect: Rect.fromLTRB(1 - 0.4083, 0.162, 1, 0.3078), // 2.4倍 随机位置 106 | /// // cropRect: Rect.fromLTRB(0, 0, 0.4083, 0.1457), //2.4倍,都是0,0 107 | /// cropRect: Rect.fromLTRB(0, 0, 1, 0.3572), // 1倍 108 | /// clipSize: Size(200, 315), 109 | /// cropRatio: Size(16, 9), 110 | /// cropRectUpdateEnd: (rect) { 111 | /// print("裁剪区域移动 $rect"); 112 | /// }, 113 | /// child: Image.network( 114 | /// "https://img1.maka.im/materialStore/beijingshejia/tupianbeijinga/9/M_7TNT6NIM/M_7TNT6NIM_v1.jpg", 115 | /// width: double.infinity, 116 | /// height: double.infinity, 117 | /// fit: BoxFit.cover, 118 | /// loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) { 119 | /// if (loadingProgress == null) 120 | /// return child; 121 | /// return Center( 122 | /// child: CircularProgressIndicator( 123 | /// value: loadingProgress.expectedTotalBytes != null 124 | /// ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes 125 | /// : null, 126 | /// ), 127 | /// ); 128 | /// }, 129 | /// ), 130 | /// ) 131 | /// ``` 132 | /// {@end-tool} 133 | CropBox({this.cropRect, required this.clipSize, required this.child, required this.cropRectUpdateEnd, this.cropRectUpdateStart, this.cropRectUpdate, this.cropRatio, this.maxCropSize, this.maxScale = 10.0, this.cropBoxType = CropBoxType.Square, this.needInnerBorder = false, this.gridLine, this.cropBoxBorder, this.backgroundColor, this.maskColor}); 134 | 135 | @override 136 | _CropBoxState createState() => _CropBoxState(); 137 | } 138 | 139 | class _CropBoxState extends State { 140 | /// 临时比例缩放大小 141 | double _tmpScale = 1.0; 142 | /// 最终比例缩放大小 143 | double _scale = 1.0; 144 | 145 | /// 待裁剪素材上次偏移量 146 | Offset _lastFocalPoint = Offset(0.0, 0.0); 147 | /// 待裁剪素材初始偏移量 148 | Offset _deltaPoint = Offset(0, 0); 149 | /// 待裁剪的素材本身尺寸 - 由外部传入 150 | late Size _originClipSize; 151 | /// 待裁剪的素材计算后的初始尺寸 152 | Size _resizeClipSize = Size(0, 0); 153 | 154 | /// 组件自身宽 155 | double _containerWidth = 0; 156 | /// 组件自身高 157 | double _containerHeight = 0; 158 | /// 头部padding高度 159 | double _containerPaddingTop = 0; 160 | /// 底部padding高度 161 | double _containerPaddingBottom = 10; 162 | /// 左右两边padding高度 163 | double _containerPaddingRL = 10; 164 | /// 裁剪框最大尺寸 165 | Size _cropBoxMaxSize = Size(0, 0); 166 | /// 裁剪框实际尺寸 167 | Size _cropBoxRealSize = Size(0, 0); 168 | /// 裁剪框实际坐标 169 | Rect _cropBoxRealRect = Rect.fromLTWH(0, 0, 0, 0); 170 | /// 裁剪比例 171 | Size _cropRatio = Size(16, 9); 172 | /// 中心点坐标 173 | Offset _originPos = Offset(0, 0); 174 | 175 | /// 裁剪结果数据 176 | /// 177 | /// LTRB值均为0到1的double值,代表在本轴上的百分比位置 178 | /// 179 | /// 包含了缩放尺寸,需要自行判断计算 180 | Rect resultRect = Rect.fromLTRB(0, 0, 1, 1); 181 | 182 | // 是否绘制完毕 183 | bool isReady = false; 184 | 185 | Future? _loading; 186 | 187 | @override 188 | void initState() { 189 | super.initState(); 190 | } 191 | 192 | /// 初始化裁剪 193 | /// 194 | /// 返回值 bool true表示初始化成功 false表示失败 195 | bool initCrop() { 196 | caculateCropBoxSize(); 197 | caculateInitClipSize(); 198 | caculateInitClipPosition(); 199 | return true; 200 | } 201 | 202 | /// 计算canvas绘制裁剪框的位置 203 | /// 204 | /// 计算裁剪框位置 205 | /// 206 | /// 计算中心点位置 207 | void caculateCropBoxSize() { 208 | // 中心坐标点用组件的中心点 209 | _originPos = Offset(_containerWidth / 2, (_containerHeight) / 2); 210 | // 计算裁剪框尺寸 211 | _cropBoxRealSize = canculateInnerBoxRealSize(_cropBoxMaxSize, _cropRatio); 212 | // 计算裁剪框坐标信息(坐标轴在 0, 0 处),用于裁剪框在canvas的坐标绘制 213 | _cropBoxRealRect = Rect.fromLTWH((_containerWidth - _cropBoxRealSize.width) / 2, (_containerHeight - _cropBoxRealSize.height) / 2, _cropBoxRealSize.width, _cropBoxRealSize.height); 214 | // print("caculateCropBoxSize Result \n _cropBoxRealSize: $_cropBoxRealSize \n _cropBoxRealRect: $_cropBoxRealRect \n _originPos: $_originPos"); 215 | } 216 | 217 | /// 计算初始素材尺寸 218 | /// 219 | /// 需要计算素材宽高比,判断横向还是纵向拉满 220 | void caculateInitClipSize() { 221 | double _realWidth = 0; 222 | double _realHeight = 0; 223 | 224 | double _cropAspectRatio = _cropBoxRealSize.width / _cropBoxRealSize.height; //裁剪框宽高比 225 | double _clipAspectRatio = _originClipSize.width / _originClipSize.height; //素材宽高比 226 | 227 | if (_cropAspectRatio > _clipAspectRatio) { 228 | _realWidth = _cropBoxRealSize.width; 229 | _realHeight = _realWidth / _clipAspectRatio; 230 | } else { 231 | _realHeight = _cropBoxRealSize.height; 232 | _realWidth = _realHeight * _clipAspectRatio; 233 | } 234 | _resizeClipSize = Size(_realWidth, _realHeight); 235 | 236 | print("_resizeClipSize: $_resizeClipSize"); 237 | } 238 | 239 | /// 计算初始素材摆放位置 240 | /// 241 | /// 根据初始素材尺寸以及scale确定初始位置 242 | void caculateInitClipPosition() { 243 | // 根据scale和传入的裁剪区域确定具体位置 244 | Rect? _clipRect; 245 | 246 | if(resultRect == Rect.fromLTRB(0, 0, 1, 1)) { 247 | // 如果没有传入初始裁剪区域,则默认居中裁剪对应比例的区域,那么scale必然为1 248 | _scale = 1.0; 249 | _deltaPoint = Offset(_originPos.dx - _resizeClipSize.width/2, _originPos.dy - _resizeClipSize.height/2); 250 | double _clipAspectRatio = _resizeClipSize.width / _resizeClipSize.height; //素材宽高比 251 | double _cropAspectRatio = _cropBoxRealSize.width / _cropBoxRealSize.height; //裁剪区域宽高比 252 | Rect _tempRect; 253 | if(_cropAspectRatio > _clipAspectRatio) { 254 | // 如果裁剪框宽高比大于素材宽高比 255 | _tempRect = Rect.fromLTWH(0, (_resizeClipSize.height - _cropBoxRealSize.height) / 2, _cropBoxRealSize.width, _cropBoxRealSize.height); 256 | }else{ 257 | _tempRect = Rect.fromLTWH((_resizeClipSize.width - _cropBoxRealSize.width) / 2, 0, _cropBoxRealSize.width, _cropBoxRealSize.height); 258 | } 259 | _clipRect = Rect.fromLTRB(_tempRect.left / _resizeClipSize.width, _tempRect.top / _resizeClipSize.height, _tempRect.right / _resizeClipSize.width, _tempRect.bottom / _resizeClipSize.height); 260 | }else{ 261 | double _clipAspectRatio = _resizeClipSize.width / _resizeClipSize.height; //素材宽高比 262 | double _cropAspectRatio = _cropBoxRealSize.width / _cropBoxRealSize.height; //裁剪区域宽高比 263 | if(_cropAspectRatio > _clipAspectRatio) { 264 | // 如果裁剪框宽高比大于素材宽高比 265 | _scale = 1 / resultRect.width; 266 | }else{ 267 | _scale = 1 / resultRect.height; 268 | } 269 | double _scaledWidth = _scale * _resizeClipSize.width; 270 | double _scaledHeight = _scale * _resizeClipSize.height; 271 | 272 | // 计算偏移和缩放后的位置 - 计算公式画图可得【公式一】 - 一定要注意_scale 273 | // 至于为啥是除以_scale还没整明白,猜测和缩放有关系,需要再研究 todo 274 | double _scaledLeft = _originPos.dx - (_cropBoxRealSize.width / 2 + _scaledWidth * resultRect.left) / _scale; 275 | double _scaledTop = _originPos.dy - (_cropBoxRealSize.height / 2 + _scaledHeight * resultRect.top) / _scale; 276 | _deltaPoint = Offset(_scaledLeft, _scaledTop); 277 | } 278 | 279 | print('_clipRect: $_clipRect _deltaPoint: $_deltaPoint'); 280 | } 281 | 282 | /// 判断是否超出界限 283 | /// 284 | /// 如果超出界限,则自动修正位置 285 | void resizeRange() { 286 | Rect _result = transPointToCropArea(); 287 | double left = _result.left; 288 | double right = _result.right; 289 | double top = _result.top; 290 | double bottom = _result.bottom; 291 | 292 | // print('resizeRange: ${_result.left} ${_result.top} ${_result.bottom} ${_result.right}'); 293 | 294 | bool _isOutRange = false; 295 | // 如果边过大,导致_scale < 1,则进行缩放计算,并且重置 _scale = 1 296 | if((right - left > 1) || (bottom - top > 1)) { 297 | double _max = max(right - left, bottom - top); 298 | left = left / _max; 299 | right = right / _max; 300 | top = top / _max; 301 | bottom = bottom / _max; 302 | 303 | _scale = 1; 304 | _isOutRange = true; 305 | } 306 | 307 | if(left < 0) { 308 | right = right - left; 309 | left = 0; 310 | _isOutRange = true; 311 | } 312 | 313 | if(right > 1) { 314 | left = 1 - (right - left); 315 | right = 1; 316 | _isOutRange = true; 317 | } 318 | 319 | if(top < 0) { 320 | bottom = bottom - top; 321 | top = 0; 322 | _isOutRange = true; 323 | } 324 | 325 | if(bottom > 1) { 326 | top = 1 - (bottom - top); 327 | bottom = 1; 328 | _isOutRange = true; 329 | } 330 | 331 | if(_isOutRange) { 332 | resultRect = Rect.fromLTRB(left, top, right, bottom); 333 | try { 334 | caculateInitClipPosition(); 335 | }catch(e) { 336 | print(e); 337 | } 338 | } 339 | } 340 | 341 | /// 根据当前的点,反向计算出渲染区域 342 | Rect transPointToCropArea() { 343 | double _scaledWidth = _scale * _resizeClipSize.width; 344 | double _scaledHeight = _scale * _resizeClipSize.height; 345 | // 由【公式一】反推 346 | double _left = ((_originPos.dx - _deltaPoint.dx) * _scale - _cropBoxRealSize.width / 2) / _scaledWidth; 347 | double _top = ((_originPos.dy - _deltaPoint.dy) * _scale - _cropBoxRealSize.height / 2) / _scaledHeight; 348 | 349 | double _clipAspectRatio = _resizeClipSize.width / _resizeClipSize.height; //素材宽高比 350 | double _cropAspectRatio = _cropBoxRealSize.width / _cropBoxRealSize.height; //裁剪区域宽高比 351 | if(_cropAspectRatio > _clipAspectRatio) { 352 | // 如果裁剪框宽高比大于素材宽高比 353 | // 根据left和top,以及裁剪比例和实际尺寸,计算出裁剪区域相对于素材的长宽百分比LTRB(这个百分比只是Left Top Right Bottom分别相对于原素材宽高的百分比,LTRB之间这个百分比值没有任何关系,LTRB的绝对值有比例关系,比例等于裁剪比例) 354 | double _width = _resizeClipSize.width / _scale; 355 | double _right = _left + 1 / _scale; 356 | double _bottom = _top + _width / _cropAspectRatio / _resizeClipSize.height; 357 | resultRect = Rect.fromLTRB(_left, _top, _right, _bottom); 358 | }else{ 359 | double _height = _resizeClipSize.height / _scale; 360 | double _bottom = _top + 1 / _scale; 361 | double _right = _left + _height * _cropAspectRatio / _resizeClipSize.width; 362 | _scale = 1 / resultRect.height; 363 | resultRect = Rect.fromLTRB(_left, _top, _right, _bottom); 364 | } 365 | 366 | return resultRect; 367 | } 368 | 369 | /// 根据填充物最大宽高和填充物比例,计算填充物实际宽高 370 | /// 371 | /// Size [_maxSize] 最大宽高 372 | /// 373 | /// Size [_ratioSize] 宽高比尺寸 374 | /// 375 | Size canculateInnerBoxRealSize(Size _maxSize, Size _ratioSize) { 376 | double _realWidth = 0; 377 | double _realHeight = 0; 378 | 379 | double _contentAspectRatio = _maxSize.width / _maxSize.height; //容器宽高比 380 | double _renderAspectRatio = _ratioSize.width / _ratioSize.height; //渲染区域宽高比 381 | 382 | if (_contentAspectRatio > _renderAspectRatio) { 383 | //容器宽高比大于渲染区域宽高比,则保证高度统一 384 | _realHeight = _maxSize.height; 385 | _realWidth = _realHeight * _renderAspectRatio; 386 | } else { 387 | _realWidth = _maxSize.width; 388 | _realHeight = _realWidth / _renderAspectRatio; 389 | } 390 | 391 | return Size(_realWidth, _realHeight); 392 | } 393 | 394 | @override 395 | void didUpdateWidget(covariant CropBox oldWidget) { 396 | if(widget.cropRatio != oldWidget.cropRatio) { 397 | setState(() { 398 | isReady = false; 399 | }); 400 | } 401 | 402 | super.didUpdateWidget(oldWidget); 403 | } 404 | 405 | 406 | @override 407 | Widget build(BuildContext context) { 408 | if(!isReady) { 409 | resultRect = widget.cropRect ?? Rect.fromLTRB(0, 0, 1, 1); 410 | assert(resultRect.left >= 0 && resultRect.left <=1); 411 | assert(resultRect.right >= 0 && resultRect.right <=1); 412 | assert(resultRect.top >= 0 && resultRect.top <=1); 413 | assert(resultRect.bottom >= 0 && resultRect.bottom <=1); 414 | 415 | _originClipSize = widget.clipSize; 416 | if(widget.cropBoxType == CropBoxType.Circle) { 417 | _cropRatio = Size(1, 1); 418 | }else{ 419 | _cropRatio = widget.cropRatio ?? Size(16, 9); 420 | } 421 | 422 | _loading = Future.delayed(Duration(milliseconds: 10)).then((value) { 423 | _containerWidth = context.size!.width; 424 | _containerHeight = context.size!.height; 425 | _containerPaddingTop = MediaQuery.of(context).padding.top * 2; 426 | _cropBoxMaxSize = widget.maxCropSize ?? Size(_containerWidth - _containerPaddingRL*2, _containerHeight - _containerPaddingTop - _containerPaddingBottom); 427 | print("build init data \n _containerWidth: $_containerWidth _containerHeight: $_containerHeight _containerPaddingTop: $_containerPaddingTop"); 428 | isReady = initCrop(); 429 | if(widget.cropRectUpdate != null) { 430 | resultRect = transPointToCropArea(); 431 | widget.cropRectUpdate!(resultRect); 432 | } 433 | setState(() {}); 434 | }); 435 | } 436 | 437 | return FutureBuilder( 438 | future: _loading, 439 | builder: (_, snapshot) { 440 | return ClipRect( 441 | child: Container( 442 | color: widget.backgroundColor ?? Color(0xff141414), 443 | child: GestureDetector( 444 | onScaleStart: _handleScaleStart, 445 | onScaleUpdate: (d) => _handleScaleUpdate(context.size!, d), 446 | onScaleEnd: _handleScaleEnd, 447 | child: AnimatedSwitcher( 448 | duration: Duration(milliseconds: 200), 449 | child: (isReady && snapshot.connectionState == ConnectionState.done) ? Stack( 450 | children: [ 451 | Transform( 452 | transform: Matrix4.identity() 453 | ..scale(max(_scale, 1.0), max(_scale, 1.0)) 454 | ..translate(_deltaPoint.dx, _deltaPoint.dy), 455 | origin: _originPos, 456 | // overflowBox解决容器尺寸问题,如果不用overflowBox,则子container过大时,会收到父级大小约束变形 457 | child: OverflowBox( 458 | alignment: Alignment.topLeft, 459 | maxWidth: double.infinity, 460 | maxHeight: double.infinity, 461 | child: Container( 462 | width: _resizeClipSize.width, 463 | height: _resizeClipSize.height, 464 | child: widget.child, 465 | ), 466 | ), 467 | ), 468 | CustomPaint( 469 | size: Size(double.infinity, double.infinity), 470 | painter: widget.cropBoxType == CropBoxType.Circle ? 471 | DrawCircleLight(clipRect: _cropBoxRealRect, centerPoint: _originPos, cropBoxBorder: widget.cropBoxBorder ?? CropBoxBorder(), maskColor: widget.maskColor) 472 | : DrawRectLight(clipRect: _cropBoxRealRect, needInnerBorder: widget.needInnerBorder, gridLine: widget.gridLine, cropBoxBorder: widget.cropBoxBorder, maskColor: widget.maskColor), 473 | ), 474 | ], 475 | ): Center( 476 | child: Container( 477 | child: Center(child: CupertinoActivityIndicator( 478 | radius: 12, 479 | )), 480 | ), 481 | ), 482 | ), 483 | ), 484 | ), 485 | ); 486 | } 487 | ); 488 | } 489 | 490 | void _handleScaleStart(ScaleStartDetails details) { 491 | _tmpScale = _scale; 492 | _lastFocalPoint = details.focalPoint; 493 | 494 | if(widget.cropRectUpdateStart != null) { 495 | widget.cropRectUpdateStart!(); 496 | } 497 | } 498 | 499 | void _handleScaleUpdate(Size size, ScaleUpdateDetails details) { 500 | setState(() { 501 | _scale = min(widget.maxScale, max(_tmpScale * details.scale, 1.0)); 502 | if (details.scale == 1) { 503 | _deltaPoint += (details.focalPoint - _lastFocalPoint); //偏移量 504 | _lastFocalPoint = details.focalPoint; //保存最有一个Point 505 | } 506 | resizeRange(); 507 | }); 508 | if(widget.cropRectUpdate != null) { 509 | widget.cropRectUpdate!(resultRect); 510 | } 511 | } 512 | 513 | void _handleScaleEnd(ScaleEndDetails details) { 514 | widget.cropRectUpdateEnd(resultRect); 515 | } 516 | } 517 | 518 | 519 | class DrawRectLight extends CustomPainter { 520 | final Rect clipRect; 521 | final bool needInnerBorder; 522 | final GridLine? gridLine; 523 | final CropBoxBorder? cropBoxBorder; 524 | final Color? maskColor; 525 | DrawRectLight({required this.clipRect, this.needInnerBorder = false, this.gridLine, this.cropBoxBorder, this.maskColor}); 526 | 527 | @override 528 | void paint(Canvas canvas, Size size) { 529 | var paint = Paint(); 530 | CropBoxBorder _cropBoxBorder = cropBoxBorder ?? CropBoxBorder(); 531 | double _storkeWidth = _cropBoxBorder.width; 532 | Radius _borderRadius = _cropBoxBorder.noNullRaidus; 533 | RRect _rrect = RRect.fromRectAndRadius(clipRect, _borderRadius); 534 | RRect _borderRRect = RRect.fromRectAndRadius(Rect.fromLTWH(clipRect.left, clipRect.top - _storkeWidth / 2, clipRect.width, clipRect.height + _storkeWidth), _borderRadius); 535 | 536 | paint 537 | ..style = PaintingStyle.fill 538 | ..color = maskColor ?? Color.fromRGBO(0, 0, 0, 0.5); 539 | canvas.save(); 540 | 541 | // 绘制一个圆形反选框和背景遮罩(透明部分) 542 | Path path = Path.combine( 543 | PathOperation.difference, 544 | Path()..addRect(Rect.fromLTRB(0, 0, size.width, size.height)), 545 | Path() 546 | ..addRRect(_rrect) 547 | ..close(), 548 | ); 549 | canvas.drawPath(path, paint); 550 | canvas.restore(); 551 | 552 | // 绘制主色调边框 553 | paint 554 | ..color = _cropBoxBorder.color 555 | ..style = PaintingStyle.stroke 556 | ..strokeWidth = _storkeWidth; 557 | 558 | canvas.drawRRect(_borderRRect, paint); 559 | 560 | if(gridLine != null) { 561 | canvas.save(); 562 | // 绘制主色调边框 563 | paint 564 | ..color = gridLine!.color 565 | ..style = PaintingStyle.stroke 566 | ..strokeWidth = gridLine!.width; 567 | Path gridLinePath = new Path(); 568 | 569 | EdgeInsets _padding = gridLine!.padding ?? EdgeInsets.all(0); 570 | 571 | for(int i = 1; i < 3; i ++) { 572 | // 绘制横线 573 | gridLinePath.moveTo(((clipRect.width / 3) * i + clipRect.left - gridLine!.width / 2), clipRect.top + _padding.top); 574 | gridLinePath.lineTo(((clipRect.width / 3) * i + clipRect.left - gridLine!.width / 2), clipRect.top + clipRect.height - _padding.bottom); 575 | 576 | // 绘制竖线 577 | gridLinePath.moveTo(clipRect.left + _padding.left, ((clipRect.height / 3) * i + clipRect.top - gridLine!.width / 2)); 578 | gridLinePath.lineTo(clipRect.left + clipRect.width - _padding.right, ((clipRect.height / 3) * i + clipRect.top - gridLine!.width / 2)); 579 | } 580 | canvas.drawPath(gridLinePath, paint); 581 | canvas.restore(); 582 | } 583 | 584 | if(needInnerBorder) { 585 | // 绘制边框内的样式 586 | paint.style = PaintingStyle.fill; 587 | canvas.drawRect(Rect.fromLTWH(clipRect.left - _storkeWidth / 2, clipRect.top - _storkeWidth, 45.44 / 2, 7.57 / 2), paint); 588 | canvas.drawRect(Rect.fromLTWH(clipRect.left - _storkeWidth / 2, clipRect.top - _storkeWidth, 7.57 / 2, 45.44 / 2), paint); 589 | canvas.drawRect(Rect.fromLTWH(clipRect.left + clipRect.width + _storkeWidth / 2, clipRect.top - _storkeWidth, -45.44 / 2, 7.57 / 2), paint); 590 | canvas.drawRect(Rect.fromLTWH(clipRect.left + clipRect.width + _storkeWidth / 2, clipRect.top - _storkeWidth, -7.57 / 2, 45.44 / 2), paint); 591 | canvas.drawRect(Rect.fromLTWH(clipRect.left - _storkeWidth / 2, clipRect.top + clipRect.height + _storkeWidth, 45.44 / 2, -7.57 / 2), paint); 592 | canvas.drawRect(Rect.fromLTWH(clipRect.left - _storkeWidth / 2, clipRect.top + clipRect.height + _storkeWidth, 7.57 / 2, -45.44 / 2), paint); 593 | canvas.drawRect(Rect.fromLTWH(clipRect.left + clipRect.width + _storkeWidth / 2, clipRect.top + clipRect.height + _storkeWidth, -45.44 / 2, -7.57 / 2), paint); 594 | canvas.drawRect(Rect.fromLTWH(clipRect.left + clipRect.width + _storkeWidth / 2, clipRect.top + clipRect.height + _storkeWidth, -7.57 / 2, -45.44 / 2), paint); 595 | } 596 | 597 | } 598 | 599 | @override 600 | bool shouldRepaint(CustomPainter oldDelegate) => true; 601 | } 602 | 603 | class DrawCircleLight extends CustomPainter { 604 | final Rect clipRect; 605 | final Offset centerPoint; 606 | final CropBoxBorder? cropBoxBorder; 607 | final Color? maskColor; 608 | DrawCircleLight({required this.clipRect, required this.centerPoint, this.cropBoxBorder, this.maskColor}); 609 | 610 | @override 611 | void paint(Canvas canvas, Size size) { 612 | CropBoxBorder _cropBoxBorder = cropBoxBorder ?? CropBoxBorder(); 613 | 614 | var paint = Paint(); 615 | double _storkeWidth = _cropBoxBorder.width; 616 | double _radius = clipRect.width / 2; 617 | paint 618 | ..style = PaintingStyle.fill 619 | ..color = maskColor ?? Color.fromRGBO(0, 0, 0, 0.5); 620 | canvas.save(); 621 | // 绘制一个圆形反选框和背景遮罩(透明部分) 622 | Path path = Path.combine( 623 | PathOperation.difference, 624 | Path()..addRect(Rect.fromLTRB(0, 0, size.width, size.height)), 625 | Path() 626 | ..addOval(Rect.fromCircle(center: centerPoint, radius: _radius)) 627 | ..close(), 628 | ); 629 | canvas.drawPath(path, paint); 630 | canvas.restore(); 631 | 632 | // 绘制主色调边框 633 | paint 634 | ..color = _cropBoxBorder.color 635 | ..style = PaintingStyle.stroke 636 | ..strokeWidth = _storkeWidth; 637 | canvas.drawCircle(centerPoint, _radius, paint); 638 | } 639 | 640 | @override 641 | bool shouldRepaint(CustomPainter oldDelegate) => true; 642 | } 643 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.1.1" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.6.1" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.2.0" 39 | clock: 40 | dependency: transitive 41 | description: 42 | name: clock 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.15.0" 53 | convert: 54 | dependency: transitive 55 | description: 56 | name: convert 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.0" 60 | exif: 61 | dependency: "direct main" 62 | description: 63 | name: exif 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.2.0" 67 | fake_async: 68 | dependency: transitive 69 | description: 70 | name: fake_async 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.2.0" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | hashcodes: 85 | dependency: transitive 86 | description: 87 | name: hashcodes 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "2.0.0" 91 | image_editor: 92 | dependency: "direct main" 93 | description: 94 | name: image_editor 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "1.0.0" 98 | image_size_getter: 99 | dependency: "direct main" 100 | description: 101 | name: image_size_getter 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.0.0" 105 | matcher: 106 | dependency: transitive 107 | description: 108 | name: matcher 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.12.10" 112 | meta: 113 | dependency: transitive 114 | description: 115 | name: meta 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.3.0" 119 | path: 120 | dependency: transitive 121 | description: 122 | name: path 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.8.0" 126 | sky_engine: 127 | dependency: transitive 128 | description: flutter 129 | source: sdk 130 | version: "0.0.99" 131 | source_span: 132 | dependency: transitive 133 | description: 134 | name: source_span 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.8.1" 138 | sprintf: 139 | dependency: transitive 140 | description: 141 | name: sprintf 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "6.0.0" 145 | stack_trace: 146 | dependency: transitive 147 | description: 148 | name: stack_trace 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.10.0" 152 | stream_channel: 153 | dependency: transitive 154 | description: 155 | name: stream_channel 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.1.0" 159 | string_scanner: 160 | dependency: transitive 161 | description: 162 | name: string_scanner 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.1.0" 166 | term_glyph: 167 | dependency: transitive 168 | description: 169 | name: term_glyph 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.2.0" 173 | test_api: 174 | dependency: transitive 175 | description: 176 | name: test_api 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.3.0" 180 | typed_data: 181 | dependency: transitive 182 | description: 183 | name: typed_data 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.3.0" 187 | vector_math: 188 | dependency: transitive 189 | description: 190 | name: vector_math 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "2.1.0" 194 | sdks: 195 | dart: ">=2.12.0 <3.0.0" 196 | flutter: ">=2.0.0" 197 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: crop_box 2 | description: "A component that can be tailored for a variety of materials.Widget for Clip." 3 | version: 1.0.2 4 | author: yangdz30@gmail.com 5 | homepage: https://github.com/godaangel/flutter_crop_box 6 | 7 | environment: 8 | sdk: ">=2.12.0 <3.0.0" 9 | flutter: ">=1.17.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | image_editor: ^1.0.0 15 | image_size_getter: ^1.0.0 16 | exif: ^2.1.0 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter. 25 | flutter: 26 | 27 | # To add assets to your package, add an assets section, like this: 28 | # assets: 29 | # - images/a_dot_burr.jpeg 30 | # - images/a_dot_ham.jpeg 31 | # 32 | # For details regarding assets in packages, see 33 | # https://flutter.dev/assets-and-images/#from-packages 34 | # 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.dev/assets-and-images/#resolution-aware. 37 | 38 | # To add custom fonts to your package, add a fonts section here, 39 | # in this "flutter" section. Each entry in this list should have a 40 | # "family" key with the font family name, and a "fonts" key with a 41 | # list giving the asset and other descriptors for the font. For 42 | # example: 43 | # fonts: 44 | # - family: Schyler 45 | # fonts: 46 | # - asset: fonts/Schyler-Regular.ttf 47 | # - asset: fonts/Schyler-Italic.ttf 48 | # style: italic 49 | # - family: Trajan Pro 50 | # fonts: 51 | # - asset: fonts/TrajanPro.ttf 52 | # - asset: fonts/TrajanPro_Bold.ttf 53 | # weight: 700 54 | # 55 | # For details regarding fonts in packages, see 56 | # https://flutter.dev/custom-fonts/#from-packages -------------------------------------------------------------------------------- /test/crop_box_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:crop_box/crop_box.dart'; 4 | 5 | void main() { 6 | test('adds one to input values', () { 7 | // final calculator = Calculator(); 8 | // expect(calculator.addOne(2), 3); 9 | // expect(calculator.addOne(-7), -6); 10 | // expect(calculator.addOne(0), 1); 11 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 12 | }); 13 | } 14 | --------------------------------------------------------------------------------