├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── com │ │ └── taijuan │ │ └── image_picker_flutter │ │ ├── BitmapUtils.kt │ │ ├── ImageDataLoader.kt │ │ ├── ImagePickerFlutterPlugin.kt │ │ ├── ImagePickerProvider.kt │ │ ├── PathUtils.java │ │ └── Utils.kt │ └── res │ └── xml │ └── provider_paths.xml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── 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 │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── flutter_export_environment.sh │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── 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 │ └── main.dart └── pubspec.yaml ├── fonts └── iconfont.ttf ├── image.gif ├── image_picker_flutter.iml ├── images └── placeholder.webp ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── ImagePickerFlutterPlugin.h │ ├── ImagePickerFlutterPlugin.m │ ├── LogUtils.swift │ └── SwiftImagePickerFlutterPlugin.swift └── image_picker_flutter.podspec ├── lib ├── image_picker_flutter.dart └── src │ ├── image │ └── asset_data_image.dart │ ├── image_picker.dart │ ├── model │ └── asset_data.dart │ ├── page │ ├── mul_image_picker_page.dart │ ├── single_image_picker_page.dart │ └── ui │ │ ├── dialog_loading.dart │ │ ├── drop_header_popup.dart │ │ └── image_picker_app_bar.dart │ └── utils.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | .svn/ 4 | pubspec.lock 5 | .packages 6 | .flutter-plugins 7 | android/local.properties 8 | android/.gradle/ 9 | android/gradlew 10 | android/gradlew.bat 11 | build/ 12 | example/pubspec.lock 13 | -------------------------------------------------------------------------------- /.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: 88fa7ea4031f5c86225573e58e5558dc4ea1c251 8 | channel: beta 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.4 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image_picker_flutter [![pub package](https://img.shields.io/pub/v/image_picker_flutter.svg)](https://pub.dartlang.org/packages/image_picker_flutter) 2 | ## 功能介绍 3 | 4 | - [x] 该插件目前支持IOS(8-13)、Android(16-29) 5 | - [x] 支持单选、多选 6 | - [x] 提供拍照、视频录制功能 7 | - [x] 支持多种图片格式PNG、JPG、GIF等,Flutter不支持的图片格式通过IOS、Android原生方法提供支持 8 | - [x] 支持多种视频格式,视频预览图通过IOS、Android原生方法提供支持 9 | - [x] 所用资源都提供File的绝对路径 10 | - [ ] 不支持原图预览 11 | - [ ] 不支持视频播放 12 | - [ ] 不支持IOS、Android动态权限、需要使用之前自行权限获取,建议使用[permission_handler](https://github.com/BaseflowIT/flutter-permission-handler) 13 | - [ ] ... 14 | 15 | ## Demo动图(有那么点不清晰) 16 | ![image](https://github.com/taijuan/image_picker_flutter/blob/master/image.gif) 17 | 18 | ## 使用说明 19 | 20 | ### Android 必须添加Kotlin依赖 21 | 22 | ### Ios 必须是Swift创建的项目 23 | 24 | ### dependencies in flutter 25 | 26 | ``` 27 | 28 | dependencies: 29 | image_picker_flutter: ^1.4.4 30 | 31 | ``` 32 | 33 | ### iOS 权限 34 | 35 | ``` 36 | 37 | NSPhotoLibraryUsageDescription 38 | 使用图片 39 | NSCameraUsageDescription 40 | 照相 41 | NSMicrophoneUsageDescription 42 | 录音 43 | 44 | ``` 45 | ### Android 权限 46 | 47 | ``` Android Permissions 48 | 49 | 50 | 51 | 52 | 53 | ``` 54 | 55 | 56 | ## Demo 应用 57 | 58 | ### [Android APP](https://fir.im/qfb8) 59 | 60 | ### IOS APP 因发布原因暂不提供、可以下载代码编译安装 61 | 62 | 63 | 64 | 65 | ## API 介绍 66 | - [ImagePicker](https://github.com/taijuan/image_picker_flutter/blob/master/lib/src/image_picker.dart) 67 | - (单选)singlePicker 68 | - (多选)mulPicker 69 | - (拍照)takePicture 70 | - (视频录制)takeVideo 71 | 72 | 73 | ## 序言 74 | - Flutter越做越强大!!! 75 | - image_picker_flutter功能越来越完善!!! 76 | - 开源加油!!! 77 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://www.dartlang.org/guides/language/analysis-options 2 | # Source of linter options: 3 | # http://dart-lang.github.io/linter/lints/options/options.html 4 | analyzer: 5 | errors: 6 | # treat missing required parameters as a warning (not a hint) 7 | missing_required_param: warning 8 | 9 | linter: 10 | rules: 11 | # these rules are documented on and in the same order as 12 | # the Dart Lint rules page to make maintenance easier 13 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 14 | # - always_declare_return_types 15 | # - always_specify_types 16 | # - annotate_overrides 17 | # - avoid_as 18 | - avoid_empty_else 19 | - avoid_init_to_null 20 | - avoid_return_types_on_setters 21 | - await_only_futures 22 | - camel_case_types 23 | - cancel_subscriptions 24 | - close_sinks 25 | # - comment_references # we do not presume as to what people want to reference in their dartdocs 26 | # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 27 | - control_flow_in_finally 28 | - empty_constructor_bodies 29 | - empty_statements 30 | - hash_and_equals 31 | - implementation_imports 32 | # - invariant_booleans 33 | # - iterable_contains_unrelated_type 34 | - library_names 35 | # - library_prefixes 36 | # - list_remove_unrelated_type 37 | # - literal_only_boolean_expressions 38 | - non_constant_identifier_names 39 | # - one_member_abstracts 40 | # - only_throw_errors 41 | # - overridden_fields 42 | - package_api_docs 43 | - package_names 44 | - package_prefixed_library_names 45 | - prefer_is_not_empty 46 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 47 | # - public_member_api_docs 48 | - slash_for_doc_comments 49 | # - sort_constructors_first 50 | # - sort_unnamed_constructors_first 51 | - test_types_in_equals 52 | - throw_in_finally 53 | # - type_annotate_public_apis # subset of always_specify_types 54 | - type_init_formals 55 | # - unawaited_futures 56 | - unnecessary_brace_in_string_interps 57 | - unnecessary_getters_setters 58 | - unnecessary_statements 59 | - unrelated_type_equality_checks 60 | - valid_regexps -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.taijuan.image_picker_flutter' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.3.61' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.5.3' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 29 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | } 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 43 | implementation 'androidx.core:core:1.2.0-rc01' 44 | implementation "androidx.exifinterface:exifinterface:1.1.0" 45 | } 46 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableJetifier=true 3 | android.useAndroidX=true -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'image_picker_flutter' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/taijuan/image_picker_flutter/BitmapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.taijuan.image_picker_flutter 2 | 3 | import android.graphics.Bitmap 4 | import java.io.ByteArrayOutputStream 5 | 6 | fun Bitmap.compress(minWidth: Int, minHeight: Int): ByteArray { 7 | val bos = ByteArrayOutputStream() 8 | val w = this.width 9 | val h = this.height 10 | val scaleW: Float = w * 1f / minWidth 11 | val scaleH: Float = h * 1f / minHeight 12 | val scale = 1f.coerceAtLeast(scaleW.coerceAtLeast(scaleH)) 13 | val destW = w / scale 14 | val destH = h / scale 15 | Bitmap.createScaledBitmap(this, destW.toInt(), destH.toInt(), true) 16 | .compress(Bitmap.CompressFormat.JPEG, 100, bos) 17 | return bos.toByteArray() 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/taijuan/image_picker_flutter/ImageDataLoader.kt: -------------------------------------------------------------------------------- 1 | package com.taijuan.image_picker_flutter 2 | 3 | import android.app.Activity 4 | import android.content.ContentUris 5 | import android.database.Cursor 6 | import android.graphics.BitmapFactory 7 | import android.media.MediaMetadataRetriever 8 | import android.net.Uri 9 | import android.provider.MediaStore 10 | import io.flutter.plugin.common.MethodChannel 11 | import java.io.File 12 | import java.util.concurrent.Executors 13 | import java.util.concurrent.Future 14 | import java.util.concurrent.ScheduledExecutorService 15 | 16 | 17 | private val IMAGE_PROJECTION = arrayOf(//查询图片需要的数据列 18 | MediaStore.Files.FileColumns._ID, 19 | MediaStore.MediaColumns.DISPLAY_NAME, //图片的真实路径 /storage/emulated/0/pp/downloader/wallpaper/aaa.jpg 20 | MediaStore.MediaColumns.MIME_TYPE, //图片的类型 image/jpeg 21 | MediaStore.MediaColumns.DATE_ADDED, 22 | MediaStore.MediaColumns.WIDTH, 23 | MediaStore.MediaColumns.HEIGHT) //图片被添加的时间,long型 1450518608 24 | 25 | internal const val IMAGE_SELECTION = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=${MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE} AND ${MediaStore.Files.FileColumns.SIZE}>0" 26 | internal const val VIDEO_SELECTION = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=${MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO} AND ${MediaStore.Files.FileColumns.SIZE}>0" 27 | 28 | internal val allImages = arrayListOf>() 29 | internal val allFolders = arrayListOf() 30 | internal fun Activity.getFolders(selection: String, result: MethodChannel.Result) { 31 | allImages.clear() 32 | allFolders.clear() 33 | allFolders.add("/All") 34 | runBackground { 35 | var cursor: Cursor? = null 36 | try { 37 | cursor = this.contentResolver.query(MediaStore.Files.getContentUri("external"), IMAGE_PROJECTION, selection, arrayOf(), IMAGE_PROJECTION[3] + " DESC") 38 | if (cursor != null) { 39 | while (cursor.moveToNext()) { 40 | val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)) 41 | val path = PathUtils.getPath(this, cursor.getUri()) 42 | val imageFile = File(path) 43 | if (!imageFile.exists() || imageFile.length() <= 0) { 44 | continue 45 | } 46 | val folder = imageFile.parentFile?.absolutePath ?: "/All" 47 | if (!allFolders.contains(folder)) { 48 | allFolders.add(folder) 49 | } 50 | val mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)) 51 | /** 52 | * @see:MediaStore.MediaColumns.DATE_ADDED 单位秒 53 | */ 54 | val time = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED))*1000 55 | val width = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)) 56 | val height = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)) 57 | val imageItem = HashMap().apply { 58 | put("id", path) 59 | put("name", name) 60 | put("path", path) 61 | put("folder", folder) 62 | put("mimeType", mimeType) 63 | put("time", time) 64 | put("width", width) 65 | put("height", height) 66 | } 67 | allImages.add(imageItem) 68 | } 69 | createFolder().listFiles()?.filter { 70 | when (selection) { 71 | IMAGE_SELECTION -> it.name.contains(".jpg") 72 | VIDEO_SELECTION -> it.name.contains(".mp4") 73 | else -> true 74 | } 75 | }?.forEach { 76 | val folder = it.parentFile?.absolutePath ?: "/All" 77 | if (!allFolders.contains(folder)) { 78 | allFolders.add(folder) 79 | } 80 | val imageItem = HashMap().apply { 81 | put("id", it.absolutePath) 82 | put("name", it.name) 83 | put("path", it.absolutePath) 84 | put("folder", folder) 85 | val isImage = it.name.contains(".jpg") 86 | put("mimeType", if (isImage) "image/jpg" else "video/mp4") 87 | put("time", it.lastModified()) 88 | val arr = size(it.absolutePath, isImage = isImage) 89 | arr.logE() 90 | put("width", arr[0]) 91 | put("height", arr[1]) 92 | } 93 | allImages.add(imageItem) 94 | } 95 | } 96 | } catch (e: Exception) { 97 | e.logT() 98 | } finally { 99 | cursor?.close() 100 | runOnUiThread { 101 | allFolders.logE() 102 | result.success(allFolders) 103 | } 104 | } 105 | } 106 | } 107 | 108 | internal fun Activity.getImages(folder: String, result: MethodChannel.Result) { 109 | if (folder == "/All") { 110 | runOnUiThread { 111 | result.success(allImages) 112 | } 113 | } else { 114 | val images = arrayListOf>() 115 | allImages.forEach { 116 | if (it["folder"] == folder) { 117 | images.add(it) 118 | } 119 | } 120 | runOnUiThread { 121 | result.success(images) 122 | } 123 | } 124 | } 125 | 126 | internal fun Activity.toUInt8List(res: List, result: MethodChannel.Result) { 127 | // data.clear() 128 | runBackground { 129 | try { 130 | val path = res[0] as String 131 | val isImage = res[1] as Boolean 132 | val width = res[2] as Int 133 | val height = res[3] as Int 134 | if (isImage) { 135 | val bytes = BitmapFactory.decodeFile(path).compress(width, height) 136 | runOnUiThread { 137 | result.success(bytes) 138 | } 139 | } else { 140 | val retriever = MediaMetadataRetriever() 141 | retriever.setDataSource(path) 142 | val bytes = retriever.frameAtTime.compress(width, height) 143 | runOnUiThread { 144 | result.success(bytes) 145 | } 146 | } 147 | } catch (e: Exception) { 148 | runOnUiThread { 149 | result.success(null) 150 | } 151 | } 152 | } 153 | } 154 | 155 | private class BackgroundTask { 156 | lateinit var future: Future<*> 157 | } 158 | 159 | private val background: ScheduledExecutorService by lazy { Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()) } 160 | private val backgroundMap: MutableList by lazy { 161 | mutableListOf() 162 | } 163 | 164 | private fun runBackground(body: () -> Unit) { 165 | val task = BackgroundTask() 166 | backgroundMap.add(task) 167 | task.future = background.submit { 168 | body.invoke() 169 | backgroundMap.remove(task) 170 | } 171 | 172 | } 173 | 174 | internal fun cancelBackground(result: MethodChannel.Result) { 175 | backgroundMap.filter { !it.future.isDone }.map { it.future.cancel(true) } 176 | backgroundMap.clear() 177 | result.success(true) 178 | } 179 | 180 | private fun Cursor.getUri(): Uri { 181 | val id = this.getLong(this.getColumnIndex(MediaStore.Files.FileColumns._ID)) 182 | val mimeType = this.getString(this.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)) 183 | val contentUri = when { 184 | mimeType.contains("image") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI 185 | mimeType.contains("video") -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI 186 | else -> MediaStore.Files.getContentUri("external") 187 | } 188 | return ContentUris.withAppendedId(contentUri, id) 189 | } 190 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/taijuan/image_picker_flutter/ImagePickerFlutterPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.taijuan.image_picker_flutter 2 | 3 | import android.annotation.SuppressLint 4 | import io.flutter.plugin.common.MethodCall 5 | import io.flutter.plugin.common.MethodChannel 6 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 7 | import io.flutter.plugin.common.MethodChannel.Result 8 | import io.flutter.plugin.common.PluginRegistry.Registrar 9 | 10 | @Suppress("UNCHECKED_CAST") 11 | class ImagePickerFlutterPlugin : MethodCallHandler { 12 | companion object { 13 | 14 | @SuppressLint("StaticFieldLeak") 15 | private lateinit var registrar: Registrar 16 | 17 | @JvmStatic 18 | fun registerWith(registrar: Registrar) { 19 | this.registrar = registrar 20 | val channel = MethodChannel(registrar.messenger(), "image_picker") 21 | channel.setMethodCallHandler(ImagePickerFlutterPlugin()) 22 | registrar.addActivityResultListener() 23 | } 24 | } 25 | 26 | override fun onMethodCall(call: MethodCall, result: Result) { 27 | when (call.method) { 28 | "getFolders" -> registrar.activity().getFolders(when (call.arguments as Int) { 29 | 1 -> IMAGE_SELECTION 30 | 2 -> VIDEO_SELECTION 31 | else -> "$IMAGE_SELECTION or $VIDEO_SELECTION" 32 | }, result) 33 | "getImages" -> registrar.activity().getImages(call.arguments as String, result) 34 | "toUInt8List" -> registrar.activity().toUInt8List(call.arguments as List, result) 35 | "cancelAll" -> cancelBackground(result) 36 | "takePicture" -> registrar.takePicture(result) 37 | "takeVideo" -> registrar.takeVideo(result) 38 | else -> result.notImplemented() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/taijuan/image_picker_flutter/ImagePickerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.taijuan.image_picker_flutter 2 | 3 | import androidx.core.content.FileProvider 4 | 5 | /** 6 | * 自定义一个Provider,以免和引入的项目的provider冲突 7 | * 8 | */ 9 | class ImagePickerProvider : FileProvider() 10 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/taijuan/image_picker_flutter/PathUtils.java: -------------------------------------------------------------------------------- 1 | package com.taijuan.image_picker_flutter; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.ContentUris; 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import android.os.Environment; 10 | import android.provider.DocumentsContract; 11 | import android.provider.MediaStore; 12 | 13 | /** 14 | * http://stackoverflow.com/a/27271131/4739220 15 | */ 16 | 17 | public class PathUtils { 18 | /** 19 | * Get a file path from a Uri. This will get the the path for Storage Access 20 | * Framework Documents, as well as the _data field for the MediaStore and 21 | * other file-based ContentProviders. 22 | * 23 | * @param context The context. 24 | * @param uri The Uri to query. 25 | * @author paulburke 26 | */ 27 | @TargetApi(Build.VERSION_CODES.KITKAT) 28 | public static String getPath(final Context context, final Uri uri) { 29 | // DocumentProvider 30 | if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { 31 | // ExternalStorageProvider 32 | if (isExternalStorageDocument(uri)) { 33 | final String docId = DocumentsContract.getDocumentId(uri); 34 | final String[] split = docId.split(":"); 35 | final String type = split[0]; 36 | 37 | if ("primary".equalsIgnoreCase(type)) { 38 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 39 | } 40 | 41 | // TODO handle non-primary volumes 42 | } else if (isDownloadsDocument(uri)) { // DownloadsProvider 43 | 44 | final String id = DocumentsContract.getDocumentId(uri); 45 | final Uri contentUri = ContentUris.withAppendedId( 46 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 47 | 48 | return getDataColumn(context, contentUri, null, null); 49 | } else if (isMediaDocument(uri)) { // MediaProvider 50 | final String docId = DocumentsContract.getDocumentId(uri); 51 | final String[] split = docId.split(":"); 52 | final String type = split[0]; 53 | 54 | Uri contentUri = null; 55 | if ("image".equals(type)) { 56 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 57 | } else if ("video".equals(type)) { 58 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 59 | } else if ("audio".equals(type)) { 60 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 61 | } 62 | 63 | final String selection = "_id=?"; 64 | final String[] selectionArgs = new String[]{ 65 | split[1] 66 | }; 67 | 68 | return getDataColumn(context, contentUri, selection, selectionArgs); 69 | } 70 | } else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general) 71 | return getDataColumn(context, uri, null, null); 72 | } else if ("file".equalsIgnoreCase(uri.getScheme())) { // File 73 | return uri.getPath(); 74 | } 75 | 76 | return ""; 77 | } 78 | 79 | /** 80 | * Get the value of the data column for this Uri. This is useful for 81 | * MediaStore Uris, and other file-based ContentProviders. 82 | * 83 | * @param context The context. 84 | * @param uri The Uri to query. 85 | * @param selection (Optional) Filter used in the query. 86 | * @param selectionArgs (Optional) Selection arguments used in the query. 87 | * @return The value of the _data column, which is typically a file path. 88 | */ 89 | private static String getDataColumn(Context context, Uri uri, String selection, 90 | String[] selectionArgs) { 91 | 92 | Cursor cursor = null; 93 | final String column = "_data"; 94 | final String[] projection = { 95 | column 96 | }; 97 | 98 | try { 99 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); 100 | if (cursor != null && cursor.moveToFirst()) { 101 | final int columnIndex = cursor.getColumnIndexOrThrow(column); 102 | return cursor.getString(columnIndex); 103 | } 104 | } finally { 105 | if (cursor != null) 106 | cursor.close(); 107 | } 108 | return null; 109 | } 110 | 111 | 112 | /** 113 | * @param uri The Uri to check. 114 | * @return Whether the Uri authority is ExternalStorageProvider. 115 | */ 116 | private static boolean isExternalStorageDocument(Uri uri) { 117 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 118 | } 119 | 120 | /** 121 | * @param uri The Uri to check. 122 | * @return Whether the Uri authority is DownloadsProvider. 123 | */ 124 | private static boolean isDownloadsDocument(Uri uri) { 125 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 126 | } 127 | 128 | /** 129 | * @param uri The Uri to check. 130 | * @return Whether the Uri authority is MediaProvider. 131 | */ 132 | private static boolean isMediaDocument(Uri uri) { 133 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 134 | } 135 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/com/taijuan/image_picker_flutter/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.taijuan.image_picker_flutter 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.BitmapFactory 7 | import android.media.MediaMetadataRetriever 8 | import android.net.Uri 9 | import android.os.Build 10 | import android.provider.MediaStore 11 | import android.util.Log 12 | import androidx.core.content.FileProvider 13 | import io.flutter.plugin.common.MethodChannel 14 | import io.flutter.plugin.common.PluginRegistry 15 | import java.io.File 16 | import java.text.SimpleDateFormat 17 | import java.util.* 18 | 19 | /** 20 | * 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); 21 | * 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片, 22 | * 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据, 23 | * 如果没有指定uri,则data就返回有数据! 24 | * 25 | * 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider 26 | */ 27 | internal fun PluginRegistry.Registrar.takePicture(result: MethodChannel.Result) { 28 | 29 | var takeImageFile = context().createFolder() 30 | takeImageFile = createFile(takeImageFile, "IMG-", ".jpg") 31 | val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) 32 | takePictureIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP 33 | if (takePictureIntent.resolveActivity(activity().packageManager) != null) { 34 | val uri = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { 35 | Uri.fromFile(takeImageFile) 36 | } else { 37 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 38 | FileProvider.getUriForFile(activity(), "${activity().packageName}.provider", takeImageFile) 39 | } 40 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri) 41 | activity().startActivityForResult(takePictureIntent, REQUEST_CAMERA_IMAGE) 42 | 43 | resultListener.onCompleted = { 44 | 45 | val imageItem = HashMap().apply { 46 | put("id", takeImageFile.absolutePath) 47 | put("name", takeImageFile.name) 48 | put("path", takeImageFile.absolutePath) 49 | put("mimeType", "image/jpg") 50 | put("time", System.currentTimeMillis()) 51 | val arr = size(takeImageFile.absolutePath) 52 | put("width", arr[0]) 53 | put("height", arr[1]) 54 | } 55 | 56 | this.activity().runOnUiThread { 57 | result.success(imageItem) 58 | } 59 | } 60 | } 61 | } 62 | 63 | fun Context.createFolder(): File { 64 | val folder = File(cacheDir, "image_picker_flutter") 65 | if (!folder.exists()) { 66 | folder.mkdirs() 67 | } 68 | if (folder.isFile) { 69 | folder.delete() 70 | folder.mkdirs() 71 | } 72 | return folder 73 | } 74 | 75 | /** 76 | * 默认情况下,即不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); 77 | * 照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图。如果想访问原始图片, 78 | * 可以通过dat extra能够得到原始图片位置。即,如果指定了目标uri,data就没有数据, 79 | * 如果没有指定uri,则data就返回有数据! 80 | * 81 | * 7.0 调用系统相机拍照不再允许使用Uri方式,应该替换为FileProvider 82 | */ 83 | internal fun PluginRegistry.Registrar.takeVideo(result: MethodChannel.Result) { 84 | var takeImageFile = context().createFolder() 85 | takeImageFile = createFile(takeImageFile, "VIDEO-", ".mp4") 86 | val takePictureIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) 87 | takePictureIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP 88 | if (takePictureIntent.resolveActivity(activity().packageManager) != null) { 89 | val uri = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { 90 | Uri.fromFile(takeImageFile) 91 | } else { 92 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 93 | FileProvider.getUriForFile(activity(), "${activity().packageName}.provider", takeImageFile) 94 | } 95 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri) 96 | activity().startActivityForResult(takePictureIntent, REQUEST_CAMERA_VIDEO) 97 | } 98 | resultListener.onCompleted = { 99 | val imageItem = HashMap().apply { 100 | put("id", takeImageFile.absolutePath) 101 | put("name", takeImageFile.name) 102 | put("path", takeImageFile.absolutePath) 103 | put("mimeType", "video/mp4") 104 | put("time", System.currentTimeMillis()) 105 | val arr = size(takeImageFile.absolutePath, isImage = false) 106 | arr.logE() 107 | put("width", arr[0]) 108 | put("height", arr[1]) 109 | } 110 | this.activity().runOnUiThread { 111 | result.success(imageItem) 112 | } 113 | 114 | } 115 | } 116 | 117 | /** 118 | * 根据系统时间、前缀、后缀产生一个文件 119 | */ 120 | private fun createFile(folder: File, prefix: String, suffix: String): File { 121 | if (!folder.exists() || !folder.isDirectory) folder.mkdirs() 122 | val dateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()) 123 | val filename = prefix + dateFormat.format(Date(System.currentTimeMillis())) + suffix 124 | folder.lastModified() 125 | return File(folder, filename) 126 | } 127 | 128 | private const val REQUEST_CAMERA_IMAGE = 0x23 129 | private const val REQUEST_CAMERA_VIDEO = 0x24 130 | 131 | internal val resultListener: ResultListener = ResultListener() 132 | 133 | internal fun PluginRegistry.Registrar.addActivityResultListener() { 134 | this.addActivityResultListener(resultListener) 135 | } 136 | 137 | internal class ResultListener : PluginRegistry.ActivityResultListener { 138 | internal var onCompleted: (() -> Unit)? = null 139 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { 140 | if (requestCode != REQUEST_CAMERA_IMAGE && requestCode != REQUEST_CAMERA_VIDEO) return true 141 | if (resultCode != Activity.RESULT_OK) return true 142 | onCompleted?.invoke() 143 | return true 144 | } 145 | 146 | } 147 | 148 | internal fun size(path: String, isImage: Boolean = true): Array { 149 | return if (isImage) { 150 | val options = BitmapFactory.Options().apply { 151 | inJustDecodeBounds = true 152 | } 153 | BitmapFactory.decodeFile(path, options) 154 | arrayOf(options.outWidth, options.outHeight) 155 | } else { 156 | val mmr = MediaMetadataRetriever() 157 | mmr.setDataSource(path) 158 | val width = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt() 159 | val height = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt() 160 | arrayOf(width, height) 161 | } 162 | } 163 | 164 | internal fun Any.logE() { 165 | if (BuildConfig.DEBUG) { 166 | Log.e("image_picker", this.toString()) 167 | } 168 | } 169 | 170 | internal fun Throwable.logT() { 171 | if (BuildConfig.DEBUG) { 172 | Log.e("image_picker", "", this) 173 | } 174 | } -------------------------------------------------------------------------------- /android/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | //context.getCacheDir() 4 | -------------------------------------------------------------------------------- /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 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /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: 88fa7ea4031f5c86225573e58e5558dc4ea1c251 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # image_picker_flutter_example 2 | 3 | Demonstrates how to use the image_picker_flutter plugin. 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.io/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.io/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 | -------------------------------------------------------------------------------- /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 28 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 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /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.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /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 flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /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 | 8.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/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/zhengtaijuan/Documents/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/zhengtaijuan/Documents/image_picker_flutter/example" 5 | export "FLUTTER_TARGET=/Users/zhengtaijuan/Documents/image_picker_flutter/example/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "FLUTTER_FRAMEWORK_DIR=/Users/zhengtaijuan/Documents/flutter/bin/cache/artifacts/engine/ios-release" 9 | export "FLUTTER_BUILD_NAME=1.0.0" 10 | export "FLUTTER_BUILD_NUMBER=1" 11 | export "TRACK_WIDGET_CREATION=true" 12 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 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 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 656C1929CC83CAF28C140412 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D413DBE0C119120FC15103C /* Pods_Runner.framework */; }; 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 31 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 40 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 41 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 42 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 43 | 5AA98CF7A4D4CD905F78A185 /* 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 = ""; }; 44 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 45 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 47 | 8D413DBE0C119120FC15103C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 49 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 50 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 51 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 9C616AB7537A16C3AC76E884 /* 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 = ""; }; 57 | E1D2D717890EEC59FC2BA84D /* 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 = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 66 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 67 | 656C1929CC83CAF28C140412 /* Pods_Runner.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 25CFB2BDDDF232049A69E90E /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 8D413DBE0C119120FC15103C /* Pods_Runner.framework */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | 6A9ADA79635FDB9AA04EC302 /* Pods */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 9C616AB7537A16C3AC76E884 /* Pods-Runner.debug.xcconfig */, 86 | E1D2D717890EEC59FC2BA84D /* Pods-Runner.release.xcconfig */, 87 | 5AA98CF7A4D4CD905F78A185 /* Pods-Runner.profile.xcconfig */, 88 | ); 89 | path = Pods; 90 | sourceTree = ""; 91 | }; 92 | 9740EEB11CF90186004384FC /* Flutter */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 3B80C3931E831B6300D905FE /* App.framework */, 96 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 97 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 98 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 99 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 100 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 101 | ); 102 | name = Flutter; 103 | sourceTree = ""; 104 | }; 105 | 97C146E51CF9000F007C117D = { 106 | isa = PBXGroup; 107 | children = ( 108 | 9740EEB11CF90186004384FC /* Flutter */, 109 | 97C146F01CF9000F007C117D /* Runner */, 110 | 97C146EF1CF9000F007C117D /* Products */, 111 | 6A9ADA79635FDB9AA04EC302 /* Pods */, 112 | 25CFB2BDDDF232049A69E90E /* Frameworks */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 97C146EF1CF9000F007C117D /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 97C146EE1CF9000F007C117D /* Runner.app */, 120 | ); 121 | name = Products; 122 | sourceTree = ""; 123 | }; 124 | 97C146F01CF9000F007C117D /* Runner */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 128 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 129 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 130 | 97C147021CF9000F007C117D /* Info.plist */, 131 | 97C146F11CF9000F007C117D /* Supporting Files */, 132 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 133 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 134 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 135 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 136 | ); 137 | path = Runner; 138 | sourceTree = ""; 139 | }; 140 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | ); 144 | name = "Supporting Files"; 145 | sourceTree = ""; 146 | }; 147 | /* End PBXGroup section */ 148 | 149 | /* Begin PBXNativeTarget section */ 150 | 97C146ED1CF9000F007C117D /* Runner */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 153 | buildPhases = ( 154 | 5F3123CCA76C8A509F777E6A /* [CP] Check Pods Manifest.lock */, 155 | 9740EEB61CF901F6004384FC /* Run Script */, 156 | 97C146EA1CF9000F007C117D /* Sources */, 157 | 97C146EB1CF9000F007C117D /* Frameworks */, 158 | 97C146EC1CF9000F007C117D /* Resources */, 159 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 160 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 161 | B224C32B6D58043E171E9DF3 /* [CP] Embed Pods Frameworks */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | ); 167 | name = Runner; 168 | productName = Runner; 169 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | 97C146E61CF9000F007C117D /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | LastUpgradeCheck = 1020; 179 | ORGANIZATIONNAME = "The Chromium Authors"; 180 | TargetAttributes = { 181 | 97C146ED1CF9000F007C117D = { 182 | CreatedOnToolsVersion = 7.3.1; 183 | DevelopmentTeam = Z6427YNZ85; 184 | LastSwiftMigration = 1100; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 189 | compatibilityVersion = "Xcode 3.2"; 190 | developmentRegion = en; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = 97C146E51CF9000F007C117D; 197 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 97C146ED1CF9000F007C117D /* Runner */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | 97C146EC1CF9000F007C117D /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 212 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 213 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 214 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXShellScriptBuildPhase section */ 221 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 222 | isa = PBXShellScriptBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | inputPaths = ( 227 | ); 228 | name = "Thin Binary"; 229 | outputPaths = ( 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | shellPath = /bin/sh; 233 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 234 | }; 235 | 5F3123CCA76C8A509F777E6A /* [CP] Check Pods Manifest.lock */ = { 236 | isa = PBXShellScriptBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | inputFileListPaths = ( 241 | ); 242 | inputPaths = ( 243 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 244 | "${PODS_ROOT}/Manifest.lock", 245 | ); 246 | name = "[CP] Check Pods Manifest.lock"; 247 | outputFileListPaths = ( 248 | ); 249 | outputPaths = ( 250 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | 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"; 255 | showEnvVarsInLog = 0; 256 | }; 257 | 9740EEB61CF901F6004384FC /* Run Script */ = { 258 | isa = PBXShellScriptBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | inputPaths = ( 263 | ); 264 | name = "Run Script"; 265 | outputPaths = ( 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | shellPath = /bin/sh; 269 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 270 | }; 271 | B224C32B6D58043E171E9DF3 /* [CP] Embed Pods Frameworks */ = { 272 | isa = PBXShellScriptBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | inputPaths = ( 277 | ); 278 | name = "[CP] Embed Pods Frameworks"; 279 | outputPaths = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 284 | showEnvVarsInLog = 0; 285 | }; 286 | /* End PBXShellScriptBuildPhase section */ 287 | 288 | /* Begin PBXSourcesBuildPhase section */ 289 | 97C146EA1CF9000F007C117D /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 294 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXVariantGroup section */ 301 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | 97C146FB1CF9000F007C117D /* Base */, 305 | ); 306 | name = Main.storyboard; 307 | sourceTree = ""; 308 | }; 309 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 97C147001CF9000F007C117D /* Base */, 313 | ); 314 | name = LaunchScreen.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 321 | isa = XCBuildConfiguration; 322 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_ANALYZER_NONNULL = YES; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_COMMA = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 336 | CLANG_WARN_EMPTY_BODY = YES; 337 | CLANG_WARN_ENUM_CONVERSION = YES; 338 | CLANG_WARN_INFINITE_RECURSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 352 | ENABLE_NS_ASSERTIONS = NO; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 363 | MTL_ENABLE_DEBUG_INFO = NO; 364 | SDKROOT = iphoneos; 365 | SUPPORTED_PLATFORMS = iphoneos; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Profile; 370 | }; 371 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | CLANG_ENABLE_MODULES = YES; 377 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 378 | DEVELOPMENT_TEAM = Z6427YNZ85; 379 | ENABLE_BITCODE = NO; 380 | FRAMEWORK_SEARCH_PATHS = ( 381 | "$(inherited)", 382 | "$(PROJECT_DIR)/Flutter", 383 | ); 384 | INFOPLIST_FILE = Runner/Info.plist; 385 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 386 | LIBRARY_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "$(PROJECT_DIR)/Flutter", 389 | ); 390 | PRODUCT_BUNDLE_IDENTIFIER = com.example.image.picker; 391 | PRODUCT_NAME = "$(TARGET_NAME)"; 392 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 393 | SWIFT_VERSION = 5.0; 394 | VERSIONING_SYSTEM = "apple-generic"; 395 | }; 396 | name = Profile; 397 | }; 398 | 97C147031CF9000F007C117D /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 401 | buildSettings = { 402 | ALWAYS_SEARCH_USER_PATHS = NO; 403 | CLANG_ANALYZER_NONNULL = YES; 404 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 405 | CLANG_CXX_LIBRARY = "libc++"; 406 | CLANG_ENABLE_MODULES = YES; 407 | CLANG_ENABLE_OBJC_ARC = YES; 408 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 409 | CLANG_WARN_BOOL_CONVERSION = YES; 410 | CLANG_WARN_COMMA = YES; 411 | CLANG_WARN_CONSTANT_CONVERSION = YES; 412 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_EMPTY_BODY = YES; 415 | CLANG_WARN_ENUM_CONVERSION = YES; 416 | CLANG_WARN_INFINITE_RECURSION = YES; 417 | CLANG_WARN_INT_CONVERSION = YES; 418 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 419 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 420 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNREACHABLE_CODE = YES; 426 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 427 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = dwarf; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | ENABLE_TESTABILITY = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu99; 433 | GCC_DYNAMIC_NO_PIC = NO; 434 | GCC_NO_COMMON_BLOCKS = YES; 435 | GCC_OPTIMIZATION_LEVEL = 0; 436 | GCC_PREPROCESSOR_DEFINITIONS = ( 437 | "DEBUG=1", 438 | "$(inherited)", 439 | ); 440 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 441 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 442 | GCC_WARN_UNDECLARED_SELECTOR = YES; 443 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 444 | GCC_WARN_UNUSED_FUNCTION = YES; 445 | GCC_WARN_UNUSED_VARIABLE = YES; 446 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 447 | MTL_ENABLE_DEBUG_INFO = YES; 448 | ONLY_ACTIVE_ARCH = YES; 449 | SDKROOT = iphoneos; 450 | TARGETED_DEVICE_FAMILY = "1,2"; 451 | }; 452 | name = Debug; 453 | }; 454 | 97C147041CF9000F007C117D /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 457 | buildSettings = { 458 | ALWAYS_SEARCH_USER_PATHS = NO; 459 | CLANG_ANALYZER_NONNULL = YES; 460 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 461 | CLANG_CXX_LIBRARY = "libc++"; 462 | CLANG_ENABLE_MODULES = YES; 463 | CLANG_ENABLE_OBJC_ARC = YES; 464 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 465 | CLANG_WARN_BOOL_CONVERSION = YES; 466 | CLANG_WARN_COMMA = YES; 467 | CLANG_WARN_CONSTANT_CONVERSION = YES; 468 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 469 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 470 | CLANG_WARN_EMPTY_BODY = YES; 471 | CLANG_WARN_ENUM_CONVERSION = YES; 472 | CLANG_WARN_INFINITE_RECURSION = YES; 473 | CLANG_WARN_INT_CONVERSION = YES; 474 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 475 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 476 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 477 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 478 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 479 | CLANG_WARN_STRICT_PROTOTYPES = YES; 480 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 481 | CLANG_WARN_UNREACHABLE_CODE = YES; 482 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 483 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 484 | COPY_PHASE_STRIP = NO; 485 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 486 | ENABLE_NS_ASSERTIONS = NO; 487 | ENABLE_STRICT_OBJC_MSGSEND = YES; 488 | GCC_C_LANGUAGE_STANDARD = gnu99; 489 | GCC_NO_COMMON_BLOCKS = YES; 490 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 491 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 492 | GCC_WARN_UNDECLARED_SELECTOR = YES; 493 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 494 | GCC_WARN_UNUSED_FUNCTION = YES; 495 | GCC_WARN_UNUSED_VARIABLE = YES; 496 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 497 | MTL_ENABLE_DEBUG_INFO = NO; 498 | SDKROOT = iphoneos; 499 | SUPPORTED_PLATFORMS = iphoneos; 500 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 501 | TARGETED_DEVICE_FAMILY = "1,2"; 502 | VALIDATE_PRODUCT = YES; 503 | }; 504 | name = Release; 505 | }; 506 | 97C147061CF9000F007C117D /* Debug */ = { 507 | isa = XCBuildConfiguration; 508 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 509 | buildSettings = { 510 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 511 | CLANG_ENABLE_MODULES = YES; 512 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 513 | DEVELOPMENT_TEAM = Z6427YNZ85; 514 | ENABLE_BITCODE = NO; 515 | FRAMEWORK_SEARCH_PATHS = ( 516 | "$(inherited)", 517 | "$(PROJECT_DIR)/Flutter", 518 | ); 519 | INFOPLIST_FILE = Runner/Info.plist; 520 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 521 | LIBRARY_SEARCH_PATHS = ( 522 | "$(inherited)", 523 | "$(PROJECT_DIR)/Flutter", 524 | ); 525 | PRODUCT_BUNDLE_IDENTIFIER = com.example.image.picker; 526 | PRODUCT_NAME = "$(TARGET_NAME)"; 527 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 528 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 529 | SWIFT_VERSION = 5.0; 530 | VERSIONING_SYSTEM = "apple-generic"; 531 | }; 532 | name = Debug; 533 | }; 534 | 97C147071CF9000F007C117D /* Release */ = { 535 | isa = XCBuildConfiguration; 536 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 537 | buildSettings = { 538 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 539 | CLANG_ENABLE_MODULES = YES; 540 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 541 | DEVELOPMENT_TEAM = Z6427YNZ85; 542 | ENABLE_BITCODE = NO; 543 | FRAMEWORK_SEARCH_PATHS = ( 544 | "$(inherited)", 545 | "$(PROJECT_DIR)/Flutter", 546 | ); 547 | INFOPLIST_FILE = Runner/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 549 | LIBRARY_SEARCH_PATHS = ( 550 | "$(inherited)", 551 | "$(PROJECT_DIR)/Flutter", 552 | ); 553 | PRODUCT_BUNDLE_IDENTIFIER = com.example.image.picker; 554 | PRODUCT_NAME = "$(TARGET_NAME)"; 555 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 556 | SWIFT_VERSION = 5.0; 557 | VERSIONING_SYSTEM = "apple-generic"; 558 | }; 559 | name = Release; 560 | }; 561 | /* End XCBuildConfiguration section */ 562 | 563 | /* Begin XCConfigurationList section */ 564 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 565 | isa = XCConfigurationList; 566 | buildConfigurations = ( 567 | 97C147031CF9000F007C117D /* Debug */, 568 | 97C147041CF9000F007C117D /* Release */, 569 | 249021D3217E4FDB00AE95B9 /* Profile */, 570 | ); 571 | defaultConfigurationIsVisible = 0; 572 | defaultConfigurationName = Release; 573 | }; 574 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 575 | isa = XCConfigurationList; 576 | buildConfigurations = ( 577 | 97C147061CF9000F007C117D /* Debug */, 578 | 97C147071CF9000F007C117D /* Release */, 579 | 249021D4217E4FDB00AE95B9 /* Profile */, 580 | ); 581 | defaultConfigurationIsVisible = 0; 582 | defaultConfigurationName = Release; 583 | }; 584 | /* End XCConfigurationList section */ 585 | }; 586 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 587 | } 588 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/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 | 照相 27 | NSMicrophoneUsageDescription 28 | 录音 29 | NSPhotoLibraryUsageDescription 30 | 使用图片 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" -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:image_picker_flutter/image_picker_flutter.dart'; 6 | import 'package:permission_handler/permission_handler.dart'; 7 | 8 | void main() async { 9 | const SystemUiOverlayStyle dark = SystemUiOverlayStyle( 10 | systemNavigationBarColor: Colors.transparent, 11 | systemNavigationBarDividerColor: Colors.transparent, 12 | statusBarColor: Colors.transparent, 13 | systemNavigationBarIconBrightness: Brightness.light, 14 | statusBarIconBrightness: Brightness.dark, 15 | statusBarBrightness: Brightness.light, 16 | ); 17 | SystemChrome.setSystemUIOverlayStyle(dark); 18 | runApp(MaterialApp(home: MyApp())); 19 | } 20 | 21 | class MyApp extends StatefulWidget { 22 | @override 23 | _MyAppState createState() => _MyAppState(); 24 | } 25 | 26 | class _MyAppState extends State { 27 | List _data = []; 28 | 29 | @override 30 | void initState() { 31 | if (Platform.isAndroid) { 32 | PermissionHandler().requestPermissions([ 33 | PermissionGroup.storage, 34 | PermissionGroup.camera, 35 | ]); 36 | } 37 | if (Platform.isIOS) { 38 | PermissionHandler().requestPermissions([ 39 | PermissionGroup.photos, 40 | PermissionGroup.camera, 41 | ]); 42 | } 43 | PaintingBinding.instance.imageCache 44 | ..maximumSize = 1000 45 | ..maximumSizeBytes = 500 << 20; 46 | super.initState(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Scaffold( 52 | appBar: AppBar( 53 | backgroundColor: Colors.red, 54 | title: Center( 55 | child: Text("Demo"), 56 | ), 57 | ), 58 | body: GridView.builder( 59 | padding: EdgeInsets.all(8), 60 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 61 | crossAxisCount: 3, 62 | mainAxisSpacing: 8, 63 | crossAxisSpacing: 8, 64 | ), 65 | itemBuilder: (context, index) { 66 | return Stack( 67 | alignment: AlignmentDirectional.center, 68 | children: [ 69 | Image( 70 | image: AssetDataImage( 71 | _data[index], 72 | targetWidth: Utils.width2px(context, ratio: 3), 73 | targetHeight: Utils.width2px(context, ratio: 3), 74 | ), 75 | fit: BoxFit.cover, 76 | width: double.infinity, 77 | height: double.infinity, 78 | ), 79 | iconVideo(_data[index]), 80 | ], 81 | ); 82 | }, 83 | itemCount: _data.length, 84 | ), 85 | bottomNavigationBar: Container( 86 | color: Colors.grey, 87 | height: MediaQuery.of(context).size.width / 4 + 88 | MediaQuery.of(context).padding.bottom, 89 | padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), 90 | alignment: AlignmentDirectional.center, 91 | child: GridView.count( 92 | crossAxisCount: 2, 93 | childAspectRatio: 4, 94 | shrinkWrap: true, 95 | physics: NeverScrollableScrollPhysics(), 96 | children: [ 97 | RawMaterialButton( 98 | onPressed: () { 99 | ImagePicker.mulPicker( 100 | context, 101 | data: _data, 102 | mulCallback: (data) { 103 | print(data.map((a){ 104 | return a.path; 105 | })); 106 | setState(() { 107 | _data = data; 108 | }); 109 | }, 110 | ); 111 | }, 112 | fillColor: Colors.blue, 113 | child: Text("MulImagePikcer"), 114 | ), 115 | RawMaterialButton( 116 | onPressed: () { 117 | ImagePicker.singlePicker(context, singleCallback: (data) { 118 | print(data.path); 119 | setState(() { 120 | _data 121 | ..removeWhere((a) => a == data) 122 | ..add(data); 123 | }); 124 | }); 125 | }, 126 | fillColor: Colors.blue, 127 | child: Text("SingleImagePikcer"), 128 | ), 129 | RawMaterialButton( 130 | onPressed: () { 131 | ImagePicker.takePicture((a) { 132 | print(a.path); 133 | setState(() { 134 | _data.add(a); 135 | }); 136 | }); 137 | }, 138 | fillColor: Colors.blue, 139 | child: Text("takePicture"), 140 | ), 141 | RawMaterialButton( 142 | onPressed: () { 143 | ImagePicker.takeVideo((a) { 144 | print(a.path); 145 | setState(() { 146 | _data.add(a); 147 | }); 148 | }); 149 | }, 150 | fillColor: Colors.blue, 151 | child: Text("takeVideo"), 152 | ), 153 | ], 154 | ), 155 | ), 156 | ); 157 | } 158 | 159 | Widget iconVideo(AssetData data) { 160 | if (data.isImage) { 161 | return Container( 162 | width: 0, 163 | height: 0, 164 | ); 165 | } 166 | return Icon( 167 | Utils.video, 168 | color: Colors.blue, 169 | ); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: image_picker_example 2 | description: Demonstrates how to use the image_picker plugin. 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.2.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | image_picker_flutter: 12 | path: ../ 13 | permission_handler: '^4.0.0' 14 | 15 | # For information on the generic Dart part of this file, see the 16 | # following page: https://www.dartlang.org/tools/pub/pubspec 17 | 18 | # The following section is specific to Flutter. 19 | flutter: 20 | 21 | # The following line ensures that the Material Icons font is 22 | # included with your application, so that you can use the icons in 23 | # the material Icons class. 24 | # uses-material-design: true 25 | # To add assets to your application, add an assets section, like this: 26 | # assets: 27 | # - images/a_dot_burr.jpeg 28 | # - images/a_dot_ham.jpeg 29 | 30 | # An image asset can refer to one or more resolution-specific "variants", see 31 | # https://flutter.dev/assets-and-images/#resolution-aware. 32 | 33 | # For details regarding adding assets from package dependencies, see 34 | # https://flutter.dev/assets-and-images/#from-packages 35 | 36 | # To add custom fonts to your application, add a fonts section here, 37 | # in this "flutter" section. Each entry in this list should have a 38 | # "family" key with the font family name, and a "fonts" key with a 39 | # list giving the asset and other descriptors for the font. For 40 | # example: 41 | # fonts: 42 | # - family: Schyler 43 | # fonts: 44 | # - asset: fonts/Schyler-Regular.ttf 45 | # - asset: fonts/Schyler-Italic.ttf 46 | # style: italic 47 | # - family: Trajan Pro 48 | # fonts: 49 | # - asset: fonts/TrajanPro.ttf 50 | # - asset: fonts/TrajanPro_Bold.ttf 51 | # weight: 700 52 | # 53 | # For details regarding fonts from package dependencies, 54 | # see https://flutter.dev/custom-fonts/#from-packages 55 | -------------------------------------------------------------------------------- /fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/fonts/iconfont.ttf -------------------------------------------------------------------------------- /image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/image.gif -------------------------------------------------------------------------------- /image_picker_flutter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /images/placeholder.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/images/placeholder.webp -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taijuan/image_picker_flutter/c5740b83efde2f08b021701703a04e5bdd6e98ad/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/ImagePickerFlutterPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ImagePickerFlutterPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/ImagePickerFlutterPlugin.m: -------------------------------------------------------------------------------- 1 | #import "ImagePickerFlutterPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "image_picker_flutter-Swift.h" 9 | #endif 10 | 11 | @implementation ImagePickerFlutterPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftImagePickerFlutterPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/LogUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogUtils.swift 3 | // image_picker_flutter 4 | // 5 | // Created by 郑泰捐 on 2019/11/5. 6 | // 7 | 8 | import Foundation 9 | 10 | func logE(_ log:Any){ 11 | #if DEBUG // 判断是否在测试环境下 12 | print("\(log)") 13 | #else 14 | #endif 15 | } 16 | -------------------------------------------------------------------------------- /ios/Classes/SwiftImagePickerFlutterPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import AVFoundation 4 | import Photos 5 | import MobileCoreServices 6 | 7 | public class SwiftImagePickerFlutterPlugin: NSObject, FlutterPlugin ,UINavigationControllerDelegate,UIImagePickerControllerDelegate{ 8 | let manager:PHCachingImageManager = PHCachingImageManager.default() as! PHCachingImageManager; 9 | var result: FlutterResult? = nil; 10 | var allImages = Array>(); 11 | var allFolders = Array(); 12 | var works = Array() 13 | public static func register(with registrar: FlutterPluginRegistrar) { 14 | let channel = FlutterMethodChannel(name: "image_picker", binaryMessenger: registrar.messenger()); 15 | let instance = SwiftImagePickerFlutterPlugin(); 16 | registrar.addMethodCallDelegate(instance, channel: channel); 17 | } 18 | 19 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 20 | if(call.method == "getFolders"){ 21 | getFolders(type: call.arguments as! Int, result: result) 22 | }else if(call.method == "getImages"){ 23 | getImages(folder: call.arguments as! String,result: result); 24 | }else if(call.method == "getPath"){ 25 | getPath(id: call.arguments as!String, result: result) 26 | }else if(call.method == "toUInt8List"){ 27 | let arr = call.arguments as! Array; 28 | toUInt8List(id: arr[0] as! String,width: arr[2] as! Int ,height: arr[3] as! Int, result: result) 29 | }else if(call.method == "cancelAll"){ 30 | cancelAll(result: result); 31 | }else if(call.method == "takePicture"){ 32 | takePicker(isVideo:false,result: result); 33 | }else if(call.method == "takeVideo"){ 34 | takePicker(isVideo:true,result: result) 35 | }else{ 36 | result(FlutterMethodNotImplemented) 37 | } 38 | } 39 | 40 | //MARK:获取对应相册数据源 41 | private func getFolders(type:Int,result:@escaping FlutterResult){ 42 | let old = Date().timeIntervalSince1970 43 | allImages.removeAll() 44 | allFolders.removeAll() 45 | allFolders.append("All") 46 | let work = DispatchWorkItem { 47 | let fetchOptions = PHFetchOptions() 48 | if(type == 1){ 49 | fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue) 50 | }else if(type == 2 ){ 51 | fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue) 52 | }else{} 53 | let all = PHAsset.fetchAssets(with: fetchOptions); 54 | 55 | //self.awaitAllPath(all: all) 该方法耗时太久 56 | //资源绝对路径改为flutter图片显示异步加载,见方法@see getPath(id:String,result:@escaping FlutterResult) 57 | for index in 0..(); 60 | d.updateValue(asset.localIdentifier, forKey: "id") 61 | d.updateValue("", forKey: "path") 62 | d.updateValue("", forKey: "name") 63 | if(asset.mediaType == PHAssetMediaType.image){ 64 | d.updateValue("image/",forKey: "mimeType"); 65 | }else{ 66 | d.updateValue("video/",forKey: "mimeType"); 67 | } 68 | d.updateValue(Int(asset.creationDate!.timeIntervalSince1970),forKey: "time"); 69 | d.updateValue(asset.pixelWidth,forKey: "width"); 70 | d.updateValue(asset.pixelHeight, forKey: "height"); 71 | d.updateValue("All", forKey: "folder") 72 | self.allImages.append(d); 73 | } 74 | 75 | let albums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil) 76 | for index in 0.. Bool in 103 | let _id:String? = d["id"] as? String 104 | return _id == id 105 | } 106 | if(index != nil){ 107 | var d = allImages[index!] 108 | allImages.remove(at: index!) 109 | let oldFolder:String = d["folder"] as? String ?? "" 110 | d.updateValue("\(oldFolder),\(folder)", forKey: "folder") 111 | allImages.append(d) 112 | } 113 | } 114 | //MARK:获取对应相册图片视频数据源 115 | private func getImages(folder:String,result:@escaping FlutterResult){ 116 | if(folder == "All"){ 117 | result(self.allImages) 118 | }else{ 119 | let images: Array> = self.allImages.filter { (item) -> Bool in 120 | let a:String = item["folder"] as? String ?? "" 121 | return a.contains(folder) 122 | } 123 | result(images) 124 | } 125 | } 126 | 127 | //Mark:获取文件名 128 | private func getName(path:String)->String{ 129 | var name = "" 130 | if(path.contains("/")){ 131 | name = "\(path.split(separator: "/").last ?? "")" 132 | }else{ 133 | name = path 134 | } 135 | logE(name) 136 | return name 137 | } 138 | //MARK:获取所用图片的路径、并且异步任务同时完成返回,一次性获取等待时间太久,该方法废弃 139 | private func awaitAllPath(all:PHFetchResult){ 140 | let old = Date().timeIntervalSince1970 141 | let queue = DispatchQueue.global() 142 | let group = DispatchGroup() 143 | let semaphore = DispatchSemaphore(value: 16) 144 | for index in 0..)->Void){ 163 | if(asset.mediaType == PHAssetMediaType.image){ 164 | getImagePath(asset: asset,folder: folder,result: result) 165 | }else{ 166 | getVideoPath(asset: asset,folder: folder,result: result) 167 | } 168 | } 169 | 170 | //MARK:通过LocalIdentifiers获取图片文件绝对路径 171 | private func getPath(id:String,result:@escaping FlutterResult){ 172 | let work = DispatchWorkItem { 173 | var path:String = "" 174 | let asset:PHAsset? = PHAsset.fetchAssets(withLocalIdentifiers: [id],options: nil).firstObject; 175 | if(asset != nil){ 176 | self.getPath(asset: asset!, folder: "") { (d) in 177 | path = d["path"] as! String 178 | DispatchQueue.main.async{ 179 | result(path) 180 | } 181 | } 182 | }else{ 183 | DispatchQueue.main.async{ 184 | result(path) 185 | } 186 | } 187 | } 188 | self.works.append(work) 189 | DispatchQueue.global(qos: .background).async(execute: work) 190 | } 191 | //MARK:获取图片绝对路径 192 | private func getImagePath(asset:PHAsset,folder:String,result:@escaping(Dictionary)->Void){ 193 | let options2 = PHContentEditingInputRequestOptions() 194 | options2.isNetworkAccessAllowed = true 195 | asset.requestContentEditingInput(with: options2){input, info in 196 | let path:String = input?.fullSizeImageURL?.path ?? "" 197 | var d = Dictionary(); 198 | d.updateValue(asset.localIdentifier, forKey: "id") 199 | let name = self.getName(path: path) 200 | d.updateValue(path, forKey: "path") 201 | d.updateValue(name, forKey: "name") 202 | d.updateValue("image/",forKey: "mimeType"); 203 | d.updateValue(Int(asset.creationDate!.timeIntervalSince1970),forKey: "time"); 204 | d.updateValue(asset.pixelWidth,forKey: "width"); 205 | d.updateValue(asset.pixelHeight, forKey: "height"); 206 | d.updateValue(folder, forKey: "folder") 207 | result(d) 208 | } 209 | } 210 | //MARK:获取视频绝对路径 211 | private func getVideoPath(asset:PHAsset,folder:String, result:@escaping(Dictionary)->Void){ 212 | self.manager.requestAVAsset(forVideo: asset, options: nil, resultHandler: {a,v,any in 213 | let url = (a as? AVURLAsset)?.url.absoluteString 214 | let path = url?.replacingOccurrences(of: "file://", with: "") ?? ""; 215 | var d = Dictionary(); 216 | d.updateValue(asset.localIdentifier, forKey: "id") 217 | let name = self.getName(path: path) 218 | d.updateValue(path, forKey: "path") 219 | d.updateValue(name, forKey: "name") 220 | d.updateValue("video/",forKey: "mimeType"); 221 | d.updateValue(Int(asset.creationDate!.timeIntervalSince1970),forKey: "time"); 222 | d.updateValue(asset.pixelWidth,forKey: "width"); 223 | d.updateValue(asset.pixelHeight, forKey: "height"); 224 | d.updateValue(folder, forKey: "folder") 225 | result(d) 226 | }); 227 | } 228 | 229 | //MARK:FLutter不支持图片视频缩略图获取方式 230 | private func toUInt8List(id:String,width:Int,height:Int,result:@escaping FlutterResult){ 231 | DispatchQueue.global(qos: .background).async { 232 | var data:Data? 233 | let asset:PHAsset? = PHAsset.fetchAssets(withLocalIdentifiers: [id],options: nil).firstObject; 234 | if(asset != nil){ 235 | self.manager.requestImage(for: asset!, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: nil, resultHandler: {(image,any) in 236 | data = image?.jpegData(compressionQuality: 100) ?? nil 237 | DispatchQueue.main.async{ 238 | result(data) 239 | } 240 | }); 241 | }else{ 242 | DispatchQueue.main.async{ 243 | result(data) 244 | } 245 | } 246 | } 247 | } 248 | 249 | //MARK:任务取消 250 | private func cancelAll(result:@escaping FlutterResult){ 251 | manager.stopCachingImagesForAllAssets(); 252 | for work in self.works { 253 | if(!work.isCancelled){ 254 | work.cancel() 255 | } 256 | } 257 | self.works.removeAll() 258 | result(true); 259 | } 260 | 261 | //MARK:拍照录制视频回调 262 | public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 263 | let mediaType = info[.mediaType] as! String; 264 | if(mediaType.contains("image")){ 265 | let image:UIImage? = info[.originalImage] as? UIImage; 266 | if(image != nil){ 267 | self.takePhotoData(image: image!, done: { 268 | picker.dismiss(animated: true, completion: nil); 269 | }) 270 | }else{ 271 | picker.dismiss(animated: true, completion: nil); 272 | } 273 | }else if(mediaType.contains("movie")){ 274 | let videoUrl:NSURL? = info[.mediaURL] as? NSURL; 275 | if(videoUrl != nil){ 276 | self.takeVideoData(videoUrl: videoUrl!, done: { 277 | picker.dismiss(animated: true, completion: nil); 278 | }) 279 | }else{ 280 | picker.dismiss(animated: true, completion: nil); 281 | } 282 | }else{ 283 | picker.dismiss(animated: true, completion: nil); 284 | } 285 | 286 | } 287 | 288 | // MARK: 录制视频获取 289 | private func takeVideoData(videoUrl:NSURL,done: @escaping @convention(block) () -> Void){ 290 | self.createAndGetAlbum { (album) in 291 | PHPhotoLibrary.shared().performChanges({ 292 | if album == nil { 293 | PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoUrl.absoluteURL!) 294 | }else{ 295 | let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoUrl.absoluteURL!) 296 | let assetPlaceholder = assetRequest?.placeholderForCreatedAsset! 297 | _=PHAssetCollectionChangeRequest.init(for: album!)?.addAssets([assetPlaceholder!] as NSArray) 298 | } 299 | }, completionHandler: {success, e in 300 | if(success){ 301 | let a = PHAsset.fetchAssets(with: .video, options: nil).lastObject; 302 | if(a != nil){ 303 | self.getVideoPath(asset: a!,folder: "",result: {a in 304 | self.result?(a); 305 | self.result = nil; 306 | done() 307 | }) 308 | }else{ 309 | done() 310 | } 311 | }else{ 312 | done() 313 | } 314 | }); 315 | } 316 | } 317 | 318 | // MARK: 拍照照片获取 319 | private func takePhotoData(image:UIImage,done: @escaping @convention(block) () -> Void){ 320 | self.createAndGetAlbum { (album) in 321 | PHPhotoLibrary.shared().performChanges({ 322 | if album == nil { 323 | PHAssetChangeRequest.creationRequestForAsset(from:image); 324 | }else{ 325 | let assetRequest = PHAssetChangeRequest.creationRequestForAsset(from: image) 326 | let assetPlaceholder = assetRequest.placeholderForCreatedAsset! 327 | _=PHAssetCollectionChangeRequest.init(for: album!)?.addAssets([assetPlaceholder] as NSArray) 328 | } 329 | }, completionHandler: {success, e in 330 | if(success){ 331 | let a = PHAsset.fetchAssets(with: .image, options: nil).lastObject; 332 | if(a != nil){ 333 | self.getImagePath(asset: a!,folder: "",result: {a in 334 | self.result?(a); 335 | self.result = nil; 336 | done() 337 | }) 338 | }else{ 339 | done() 340 | } 341 | }else{ 342 | done() 343 | } 344 | }); 345 | } 346 | } 347 | func createAndGetAlbum(_ done: @escaping @convention(block) (PHAssetCollection?) -> Void){ 348 | let albumName = "image_picker_flutter" 349 | let fetchOptions = PHFetchOptions() 350 | fetchOptions.predicate = NSPredicate.init(format: "title = %@", albumName) 351 | var albums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions) 352 | if let _ = albums.firstObject { 353 | done(albums.firstObject) 354 | }else{ 355 | PHPhotoLibrary.shared().performChanges({ 356 | PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumName) 357 | }) { (result, error) in 358 | albums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions) 359 | done(albums.firstObject) 360 | } 361 | } 362 | } 363 | // MARK: 拍照与录制视频 364 | private func takePicker(isVideo:Bool,result:@escaping FlutterResult){ 365 | self.result = result; 366 | if (UIImagePickerController.isSourceTypeAvailable(.camera)){ 367 | let cameraPicker = UIImagePickerController(); 368 | cameraPicker.delegate = self; 369 | cameraPicker.sourceType = .camera; 370 | cameraPicker.allowsEditing = false; 371 | if(isVideo){ 372 | cameraPicker.mediaTypes = [kUTTypeMovie as String]; 373 | cameraPicker.videoQuality = .typeIFrame1280x720; 374 | }else{ 375 | cameraPicker.mediaTypes = [kUTTypeImage as String]; 376 | } 377 | UIApplication.shared.windows.last?.rootViewController?.present( 378 | cameraPicker, 379 | animated: true, 380 | completion: nil 381 | ); 382 | }else{ 383 | logE("camera is nil"); 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /ios/image_picker_flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint image_picker_flutter.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'image_picker_flutter' 7 | s.version = '1.4.4' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'https://github.com/taijuan/image_picker_flutter.git' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'taijuan' => '673636090@qq.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/image_picker_flutter.dart: -------------------------------------------------------------------------------- 1 | library image_picker_flutter; 2 | 3 | export 'src/image/asset_data_image.dart'; 4 | export 'src/image_picker.dart'; 5 | export 'src/model/asset_data.dart'; 6 | export 'src/page/mul_image_picker_page.dart'; 7 | export 'src/page/single_image_picker_page.dart'; 8 | export 'src/page/ui/image_picker_app_bar.dart'; 9 | export 'src/utils.dart'; 10 | -------------------------------------------------------------------------------- /lib/src/image/asset_data_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | import 'dart:typed_data'; 4 | import 'dart:ui' as ui show Codec; 5 | 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | import 'package:image_picker_flutter/src/model/asset_data.dart'; 9 | import 'package:image_picker_flutter/src/utils.dart'; 10 | 11 | class AssetDataImage extends ImageProvider { 12 | const AssetDataImage( 13 | this.data, { 14 | this.targetWidth, 15 | this.targetHeight, 16 | this.scale = 1.0, 17 | }) : assert(data != null), 18 | assert(scale != null); 19 | 20 | final AssetData data; 21 | final int targetWidth, targetHeight; 22 | final double scale; 23 | 24 | @override 25 | Future obtainKey(ImageConfiguration configuration) { 26 | return SynchronousFuture(this); 27 | } 28 | 29 | @override 30 | ImageStreamCompleter load(AssetDataImage key, DecoderCallback decode) { 31 | return MultiFrameImageStreamCompleter( 32 | codec: _loadAsync(key, decode), 33 | scale: key.scale, 34 | informationCollector: () sync* { 35 | yield DiagnosticsProperty('Image provider', this); 36 | yield DiagnosticsProperty('Image key', key); 37 | }, 38 | ); 39 | } 40 | 41 | Future _loadAsync( 42 | AssetDataImage key, DecoderCallback decode) async { 43 | assert(key == this); 44 | 45 | await Utils.updateAndGetPath(data); 46 | Uint8List bytes; 47 | File file = File(data.path); 48 | 49 | ///判断文件是否存在 50 | if (!await file.exists()) { 51 | return null; 52 | } 53 | 54 | ///判断是否是支持的文件格式 55 | if (!Utils.isSupportImageFormatString(await file.openRead(0, 3).first)) { 56 | ///不支持走原生方式,获取的是jpg 57 | bytes = await Utils.channel.invokeMethod( 58 | 'toUInt8List', 59 | [ 60 | data.id, 61 | data.isImage, 62 | targetWidth, 63 | targetHeight, 64 | ], 65 | ); 66 | return await decode(bytes); 67 | } 68 | bytes = await file.readAsBytes(); 69 | if (bytes == null || bytes.lengthInBytes == 0) return null; 70 | if (targetWidth == null && targetHeight == null) { 71 | return await decode(bytes); 72 | } else if (targetWidth <= 0 && targetHeight == null) { 73 | return await decode(bytes); 74 | } else if (targetWidth > 0 && targetHeight == null) { 75 | return await decode( 76 | bytes, 77 | cacheWidth: targetWidth > data.width ? targetWidth : -1, 78 | ); 79 | } else if (targetWidth == null && targetHeight <= 0) { 80 | return await decode(bytes); 81 | } else if (targetWidth == null && targetHeight > 0) { 82 | return await decode( 83 | bytes, 84 | cacheHeight: targetHeight > data.height ? targetHeight : -1, 85 | ); 86 | } else { 87 | int w = data.width; 88 | int h = data.height; 89 | double wd = w / targetWidth.toDouble(); 90 | double hd = h / targetHeight.toDouble(); 91 | double be = max(1, max(wd, hd)); 92 | w = w ~/ be; 93 | h = h ~/ be; 94 | Utils.log("width:${data.width},height:${data.height}"); 95 | Utils.log("targetWidth:$targetWidth,targetHeight:$targetHeight"); 96 | Utils.log("w:$w,h:$h"); 97 | return await decode( 98 | bytes, 99 | cacheWidth: w, 100 | cacheHeight: h, 101 | ); 102 | } 103 | } 104 | 105 | @override 106 | bool operator ==(dynamic other) { 107 | if (other.runtimeType != runtimeType) return false; 108 | final AssetDataImage typedOther = other; 109 | return data == typedOther.data && 110 | scale == typedOther.scale && 111 | targetWidth == typedOther.targetWidth && 112 | targetHeight == typedOther.targetHeight; 113 | } 114 | 115 | @override 116 | int get hashCode => hashValues(data, targetWidth, targetHeight, scale); 117 | 118 | @override 119 | String toString() => 120 | '$runtimeType("$data", targetWidth: $targetWidth,targetHeight: $targetHeight,scale: $scale)'; 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/image_picker.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:image_picker_flutter/src/model/asset_data.dart'; 4 | import 'package:image_picker_flutter/src/page/mul_image_picker_page.dart'; 5 | import 'package:image_picker_flutter/src/page/single_image_picker_page.dart'; 6 | import 'package:image_picker_flutter/src/utils.dart'; 7 | 8 | typedef MulCallback = void Function(List); 9 | 10 | typedef SingleCallback = void Function(AssetData); 11 | 12 | typedef Callback = void Function(AssetData); 13 | 14 | class ImagePicker { 15 | ImagePicker._(); 16 | 17 | static debug(bool isDebug) { 18 | Utils.isDebug = isDebug; 19 | } 20 | 21 | ///单选图片 22 | static void singlePicker( 23 | BuildContext context, { 24 | ImagePickerType type = ImagePickerType.imageAndVideo, 25 | Language language, 26 | ImageProvider placeholder, 27 | Widget back, 28 | Decoration decoration, 29 | Color appBarColor = Colors.blue, 30 | Widget emptyView, 31 | SingleCallback singleCallback, 32 | }) { 33 | Navigator.of(context).push( 34 | MaterialPageRoute( 35 | builder: (context) => SingleImagePickerPage( 36 | type: type, 37 | language: language ?? Language(), 38 | placeholder: placeholder, 39 | decoration: decoration, 40 | appBarColor: appBarColor ?? Colors.blue, 41 | back: back, 42 | emptyView: emptyView, 43 | ), 44 | ), 45 | )..then((data) { 46 | if (data != null && singleCallback != null) { 47 | singleCallback(data); 48 | } 49 | }); 50 | } 51 | 52 | ///多选图片 53 | static void mulPicker( 54 | BuildContext context, { 55 | List data, 56 | ImagePickerType type = ImagePickerType.imageAndVideo, 57 | int limit = 9, 58 | Language language, 59 | ImageProvider placeholder, 60 | Widget back, 61 | Widget menu, 62 | Decoration decoration, 63 | Color appBarColor = Colors.blue, 64 | Widget emptyView, 65 | MulCallback mulCallback, 66 | }) { 67 | Navigator.of(context).push( 68 | MaterialPageRoute( 69 | builder: (context) => MulImagePickerPage( 70 | selectedData: data, 71 | type: type, 72 | limit: limit, 73 | appBarColor: appBarColor ?? Colors.blue, 74 | language: language ?? Language(), 75 | placeholder: placeholder, 76 | decoration: decoration, 77 | menu: menu, 78 | back: back, 79 | emptyView: emptyView, 80 | ), 81 | ), 82 | )..then((data) { 83 | if (data != null && mulCallback != null) { 84 | mulCallback(data); 85 | } 86 | }); 87 | } 88 | 89 | ///拍照返回图片路径 90 | static void takePicture(Callback callback) { 91 | Utils.takePicture().then((a) { 92 | callback(a); 93 | }); 94 | } 95 | 96 | ///录像返回图片路径 97 | static void takeVideo(Callback callback) { 98 | Utils.takeVideo().then((a) { 99 | callback(a); 100 | }); 101 | } 102 | } 103 | 104 | enum ImagePickerType { 105 | onlyImage, 106 | onlyVideo, 107 | imageAndVideo, 108 | } 109 | 110 | ///文字基类 111 | class Language { 112 | String get showToast => "Only ### images can be selected"; 113 | 114 | String get empty => "Empty"; 115 | } 116 | -------------------------------------------------------------------------------- /lib/src/model/asset_data.dart: -------------------------------------------------------------------------------- 1 | class AssetData { 2 | ///ios id与path不一致,Android id与path相同 3 | String id; 4 | String name; 5 | String path; 6 | String mimeType; 7 | int time; 8 | int width; 9 | int height; 10 | 11 | AssetData.fromJson(dynamic json) { 12 | id = json["id"]; 13 | name = json["name"]; 14 | path = json["path"]; 15 | mimeType = json["mimeType"]; 16 | time = json["time"]; 17 | width = json["width"]; 18 | height = json["height"]; 19 | } 20 | 21 | Map toJson() { 22 | return { 23 | "id": id, 24 | "name": name, 25 | "path": path, 26 | "mimeType": mimeType, 27 | "time": time, 28 | "width": width, 29 | "height": height, 30 | }; 31 | } 32 | 33 | bool get isImage => mimeType.contains("image"); 34 | 35 | @override 36 | bool operator ==(Object other) { 37 | if (other is AssetData && runtimeType == other.runtimeType) { 38 | return id == other.id; 39 | } else { 40 | return false; 41 | } 42 | } 43 | 44 | @override 45 | int get hashCode { 46 | return id.hashCode; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/page/mul_image_picker_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:image_picker_flutter/src/image/asset_data_image.dart'; 4 | import 'package:image_picker_flutter/src/image_picker.dart'; 5 | import 'package:image_picker_flutter/src/model/asset_data.dart'; 6 | import 'package:image_picker_flutter/src/page/ui/dialog_loading.dart'; 7 | import 'package:image_picker_flutter/src/page/ui/drop_header_popup.dart'; 8 | import 'package:image_picker_flutter/src/page/ui/image_picker_app_bar.dart'; 9 | import 'package:image_picker_flutter/src/utils.dart'; 10 | 11 | class MulImagePickerPage extends StatefulWidget { 12 | final int limit; 13 | final List selectedData; 14 | final ImagePickerType type; 15 | final Widget back, menu; 16 | final Decoration decoration; 17 | final Color appBarColor; 18 | final Language language; 19 | final ImageProvider placeholder; 20 | final Widget emptyView; 21 | 22 | const MulImagePickerPage({ 23 | Key key, 24 | this.limit = 9, 25 | this.selectedData, 26 | this.type = ImagePickerType.imageAndVideo, 27 | this.back, 28 | this.menu, 29 | this.decoration, 30 | this.appBarColor = Colors.blue, 31 | this.language, 32 | this.placeholder, 33 | this.emptyView, 34 | }) : super(key: key); 35 | 36 | @override 37 | State createState() { 38 | return MulImagePickerPageState(); 39 | } 40 | } 41 | 42 | class MulImagePickerPageState extends State { 43 | final List selectedData = []; 44 | final GlobalKey _scaffoldKey = GlobalKey(); 45 | final List data = []; 46 | bool isFirst = true; 47 | 48 | @override 49 | void dispose() { 50 | Utils.cancelAll(); 51 | super.dispose(); 52 | } 53 | 54 | @override 55 | void initState() { 56 | if (widget.selectedData != null) { 57 | selectedData.addAll(widget.selectedData); 58 | } 59 | Utils.log("initState"); 60 | super.initState(); 61 | } 62 | 63 | void getData(String folder) { 64 | Utils.getImages(folder) 65 | ..then((data) { 66 | this.data.clear(); 67 | this.data.addAll(data); 68 | this.isFirst = false; 69 | }) 70 | ..whenComplete(() { 71 | if (mounted) { 72 | setState(() {}); 73 | Utils.log("whenComplete"); 74 | } 75 | }); 76 | } 77 | 78 | @override 79 | Widget build(BuildContext context) { 80 | return Scaffold( 81 | key: _scaffoldKey, 82 | appBar: ImagePickerAppBar( 83 | context: context, 84 | center: DropHeader( 85 | type: widget.type, 86 | onSelect: (item) { 87 | getData(item); 88 | }, 89 | ), 90 | language: widget.language, 91 | back: widget.back ?? 92 | Icon( 93 | Utils.back, 94 | color: Colors.white, 95 | ), 96 | onBackCallback: () { 97 | Navigator.of(context).pop(); 98 | }, 99 | menu: widget.menu ?? 100 | Icon( 101 | Utils.save, 102 | color: Colors.white, 103 | ), 104 | onSaveCallback: () { 105 | LoadingDialog.showLoadingDialog(context); 106 | Utils.convertMulData(selectedData).whenComplete(() { 107 | Navigator.of(context)..pop()..pop(selectedData); 108 | }); 109 | }, 110 | decoration: widget.decoration, 111 | appBarColor: widget.appBarColor, 112 | ), 113 | body: body(), 114 | ); 115 | } 116 | 117 | Widget body() { 118 | if (isFirst) { 119 | return Center( 120 | child: CircularProgressIndicator(), 121 | ); 122 | } else if (data.isEmpty) { 123 | return Center(child: widget.emptyView ?? Text(widget.language.empty)); 124 | } else { 125 | return GridView.builder( 126 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 127 | crossAxisCount: 3, 128 | crossAxisSpacing: 8, 129 | mainAxisSpacing: 8, 130 | ), 131 | itemBuilder: (context, index) => _createItem(data[index]), 132 | itemCount: data.length, 133 | padding: EdgeInsets.fromLTRB( 134 | 8, 135 | 8, 136 | 8, 137 | 8 + MediaQuery.of(context).padding.bottom, 138 | ), 139 | ); 140 | } 141 | } 142 | 143 | Widget _createItem(AssetData data) { 144 | return Stack( 145 | alignment: AlignmentDirectional.bottomEnd, 146 | children: [ 147 | FadeInImage( 148 | placeholder: widget.placeholder ?? Utils.placeholder, 149 | image: AssetDataImage( 150 | data, 151 | targetWidth: Utils.width2px(context, ratio: 3), 152 | targetHeight: Utils.width2px(context, ratio: 3), 153 | ), 154 | fit: BoxFit.cover, 155 | width: double.infinity, 156 | height: double.infinity, 157 | ), 158 | RawMaterialButton( 159 | fillColor: 160 | selectedData.contains(data) ? Colors.white54 : Colors.transparent, 161 | constraints: BoxConstraints.expand(), 162 | highlightElevation: 0, 163 | elevation: 0, 164 | disabledElevation: 0, 165 | shape: CircleBorder( 166 | side: BorderSide( 167 | color: selectedData.contains(data) 168 | ? widget.appBarColor ?? Colors.blue 169 | : Colors.transparent, 170 | width: 4, 171 | ), 172 | ), 173 | onPressed: () { 174 | if (selectedData.contains(data)) { 175 | setState(() { 176 | selectedData.removeWhere((a) { 177 | return a == data; 178 | }); 179 | }); 180 | } else { 181 | if (selectedData.length < widget.limit) { 182 | setState(() { 183 | selectedData 184 | ..removeWhere((a) { 185 | return a == data; 186 | }) 187 | ..add(data); 188 | }); 189 | } else { 190 | _scaffoldKey.currentState.showSnackBar( 191 | SnackBar( 192 | content: Text( 193 | widget.language.showToast.replaceAll( 194 | "###", 195 | "${widget.limit}", 196 | ), 197 | ), 198 | ), 199 | ); 200 | } 201 | } 202 | }, 203 | child: Text( 204 | showNumberText(data), 205 | style: TextStyle( 206 | fontSize: 48, 207 | color: widget.appBarColor ?? Colors.blue, 208 | ), 209 | ), 210 | ), 211 | iconVideo(data), 212 | ], 213 | ); 214 | } 215 | 216 | Widget iconVideo(AssetData data) { 217 | if (data.isImage) { 218 | return Container( 219 | width: 0, 220 | height: 0, 221 | ); 222 | } 223 | return Icon( 224 | Utils.video, 225 | color: widget.appBarColor ?? Colors.blue, 226 | ); 227 | } 228 | 229 | showNumberText(AssetData data) { 230 | int num = selectedData.indexOf(data) + 1; 231 | if (num == 0) { 232 | return ""; 233 | } else { 234 | return "$num"; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /lib/src/page/single_image_picker_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:image_picker_flutter/src/image/asset_data_image.dart'; 4 | import 'package:image_picker_flutter/src/image_picker.dart'; 5 | import 'package:image_picker_flutter/src/model/asset_data.dart'; 6 | import 'package:image_picker_flutter/src/page/ui/dialog_loading.dart'; 7 | import 'package:image_picker_flutter/src/page/ui/image_picker_app_bar.dart'; 8 | import 'package:image_picker_flutter/src/utils.dart'; 9 | 10 | import 'ui/drop_header_popup.dart'; 11 | 12 | class SingleImagePickerPage extends StatefulWidget { 13 | final ImagePickerType type; 14 | final Widget back; 15 | final Decoration decoration; 16 | final Language language; 17 | final ImageProvider placeholder; 18 | final Color appBarColor; 19 | final Widget emptyView; 20 | 21 | const SingleImagePickerPage({ 22 | Key key, 23 | this.type = ImagePickerType.imageAndVideo, 24 | this.back, 25 | this.decoration, 26 | this.language, 27 | this.placeholder, 28 | this.appBarColor = Colors.blue, 29 | this.emptyView, 30 | }) : super(key: key); 31 | 32 | @override 33 | State createState() { 34 | return SingleImagePickerPageState(); 35 | } 36 | } 37 | 38 | class SingleImagePickerPageState extends State { 39 | final List data = []; 40 | bool isFirst = true; 41 | 42 | @override 43 | void dispose() { 44 | Utils.cancelAll(); 45 | super.dispose(); 46 | } 47 | 48 | void getData(String folder) { 49 | Utils.getImages(folder) 50 | ..then((data) { 51 | this.data.clear(); 52 | this.data.addAll(data); 53 | this.isFirst = false; 54 | }) 55 | ..whenComplete(() { 56 | if (mounted) { 57 | setState(() {}); 58 | Utils.log("whenComplete"); 59 | } 60 | }); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: ImagePickerAppBar( 67 | context: context, 68 | center: DropHeader( 69 | type: widget.type, 70 | onSelect: (item) { 71 | getData(item); 72 | }, 73 | ), 74 | language: widget.language, 75 | back: widget.back ?? 76 | Icon( 77 | Utils.back, 78 | color: Colors.white, 79 | ), 80 | onBackCallback: () { 81 | Navigator.of(context).pop(); 82 | }, 83 | decoration: widget.decoration, 84 | appBarColor: widget.appBarColor, 85 | ), 86 | body: body(), 87 | ); 88 | } 89 | 90 | Widget body() { 91 | if (isFirst) { 92 | return Center( 93 | child: CircularProgressIndicator(), 94 | ); 95 | } else if (data.isEmpty) { 96 | return Center(child: widget.emptyView ?? Text(widget.language.empty)); 97 | } else { 98 | return GridView.builder( 99 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 100 | crossAxisCount: 3, 101 | crossAxisSpacing: 8, 102 | mainAxisSpacing: 8, 103 | ), 104 | itemBuilder: (context, index) => _createItem(data[index]), 105 | itemCount: data.length, 106 | padding: EdgeInsets.fromLTRB( 107 | 8, 108 | 8, 109 | 8, 110 | 8 + MediaQuery.of(context).padding.bottom, 111 | ), 112 | ); 113 | } 114 | } 115 | 116 | Widget _createItem(AssetData data) { 117 | return Stack( 118 | alignment: AlignmentDirectional.bottomEnd, 119 | children: [ 120 | FadeInImage( 121 | placeholder: widget.placeholder ?? Utils.placeholder, 122 | image: AssetDataImage( 123 | data, 124 | targetWidth: Utils.width2px(context, ratio: 3), 125 | targetHeight: Utils.width2px(context, ratio: 3), 126 | ), 127 | fit: BoxFit.cover, 128 | width: double.infinity, 129 | height: double.infinity, 130 | ), 131 | RawMaterialButton( 132 | constraints: BoxConstraints.expand(), 133 | onPressed: () { 134 | LoadingDialog.showLoadingDialog(context); 135 | Utils.convertSingleData(data).whenComplete(() { 136 | Navigator.of(context)..pop()..pop(data); 137 | }); 138 | }, 139 | shape: CircleBorder(), 140 | ), 141 | iconVideo(data), 142 | ], 143 | ); 144 | } 145 | 146 | Widget iconVideo(AssetData data) { 147 | if (data.isImage) { 148 | return SizedBox.shrink(); 149 | } 150 | return Icon( 151 | Utils.video, 152 | color: widget.appBarColor ?? Colors.blue, 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/src/page/ui/dialog_loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingDialog { 4 | LoadingDialog._(); 5 | 6 | static showLoadingDialog(BuildContext context) { 7 | showDialog( 8 | context: context, 9 | barrierDismissible: false, 10 | builder: (context) { 11 | return Center( 12 | child: CircularProgressIndicator(), 13 | ); 14 | }, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/page/ui/drop_header_popup.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core' as prefix0; 2 | import 'dart:core'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:image_picker_flutter/image_picker_flutter.dart'; 7 | 8 | typedef OnSelected = void Function(T value); 9 | 10 | class DropHeader extends StatefulWidget { 11 | final ImagePickerType type; 12 | final Widget title; 13 | final OnSelected onSelect; 14 | 15 | const DropHeader({Key key, this.type, this.title, this.onSelect}) 16 | : super(key: key); 17 | 18 | @override 19 | State createState() { 20 | return DropHeaderState(); 21 | } 22 | } 23 | 24 | class DropHeaderState extends State { 25 | List _folders = []; 26 | String _folder = ""; 27 | 28 | @override 29 | void initState() { 30 | WidgetsBinding.instance 31 | .addPostFrameCallback((d) => Utils.getFolders(widget.type) 32 | ..then((folders) { 33 | setState(() { 34 | _folders.clear(); 35 | _folders.addAll(folders); 36 | _folder = _folders[0]; 37 | widget.onSelect(_folder); 38 | }); 39 | })); 40 | super.initState(); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | padding: EdgeInsets.symmetric(horizontal: 48), 47 | child: _folders.isEmpty 48 | ? SizedBox.shrink() 49 | : SizedBox.expand( 50 | child: FlatButton( 51 | onPressed: () { 52 | _showDropPopup(_folders, _folder).then((folder) { 53 | if (folder != null) { 54 | _folder = folder; 55 | widget.onSelect(_folder); 56 | } 57 | }); 58 | }, 59 | child: Text( 60 | _folder.split("/").last, 61 | maxLines: 1, 62 | textAlign: TextAlign.center, 63 | style: TextStyle(color: Colors.white, fontSize: 18), 64 | ), 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | Future _showDropPopup(List data, String select) async { 71 | return showGeneralDialog( 72 | context: context, 73 | barrierDismissible: true, 74 | barrierColor: Color(0x01000000), 75 | barrierLabel: "", 76 | transitionDuration: Duration(milliseconds: 150), 77 | transitionBuilder: (BuildContext context, Animation animation, 78 | Animation secondaryAnimation, Widget child) { 79 | return FadeTransition( 80 | opacity: CurvedAnimation( 81 | parent: animation, 82 | curve: Curves.easeOut, 83 | ), 84 | child: child, 85 | ); 86 | }, 87 | pageBuilder: (context, firstAnim, secondAnim) { 88 | double left = 48; 89 | double right = 48.0; 90 | double top = MediaQuery.of(context).padding.top + 48; 91 | double height = 48.0 * data.length; 92 | double bottom = MediaQuery.of(context).padding.bottom; 93 | double maxHeight = MediaQuery.of(context).size.height - top - bottom; 94 | height = height > maxHeight ? maxHeight : height; 95 | bottom = bottom + maxHeight - height; 96 | return Card( 97 | clipBehavior: Clip.antiAliasWithSaveLayer, 98 | margin: EdgeInsets.only( 99 | left: left, 100 | right: right, 101 | top: top, 102 | bottom: bottom, 103 | ), 104 | child: ListView.builder( 105 | physics: ClampingScrollPhysics(), 106 | padding: EdgeInsets.only(), 107 | itemBuilder: (context, index) { 108 | return RawMaterialButton( 109 | onPressed: () { 110 | Navigator.of(context).pop(data[index]); 111 | }, 112 | child: Text( 113 | data[index].split("/").last, 114 | style: TextStyle(color: Colors.black, fontSize: 16), 115 | ), 116 | ); 117 | }, 118 | itemCount: data.length, 119 | ), 120 | ); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/src/page/ui/image_picker_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:image_picker_flutter/src/image_picker.dart'; 4 | 5 | typedef OnBackCallback = Function(); 6 | typedef OnSaveCallback = Function(); 7 | 8 | class ImagePickerAppBar extends StatelessWidget implements PreferredSizeWidget { 9 | final BuildContext context; 10 | final Widget center, back, menu; 11 | final OnBackCallback onBackCallback; 12 | final OnSaveCallback onSaveCallback; 13 | final Decoration decoration; 14 | final Color appBarColor; 15 | final Language language; 16 | 17 | const ImagePickerAppBar({ 18 | Key key, 19 | @required this.context, 20 | @required this.center, 21 | this.back, 22 | this.menu, 23 | this.onBackCallback, 24 | this.onSaveCallback, 25 | this.decoration, 26 | this.language, 27 | this.appBarColor, 28 | }) : super(key: key); 29 | 30 | @override 31 | Size get preferredSize => Size.fromHeight( 32 | 48 + MediaQuery.of(context).padding.top, 33 | ); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Container( 38 | height: 48 + MediaQuery.of(context).padding.top, 39 | padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), 40 | decoration: decoration ?? BoxDecoration(color: appBarColor), 41 | child: Stack( 42 | children: [ 43 | center, 44 | Positioned( 45 | left: 0, 46 | top: 0, 47 | bottom: 0, 48 | child: RawMaterialButton( 49 | onPressed: onBackCallback, 50 | child: back, 51 | highlightElevation: 0, 52 | elevation: 0, 53 | disabledElevation: 0, 54 | constraints: BoxConstraints( 55 | minWidth: 48, 56 | minHeight: 48, 57 | maxHeight: 48, 58 | ), 59 | ), 60 | ), 61 | Positioned( 62 | top: 0, 63 | right: 0, 64 | bottom: 0, 65 | child: RawMaterialButton( 66 | onPressed: onSaveCallback, 67 | child: menu, 68 | highlightElevation: 0, 69 | elevation: 0, 70 | disabledElevation: 0, 71 | constraints: BoxConstraints( 72 | minWidth: 48, 73 | minHeight: 48, 74 | maxHeight: 48, 75 | ), 76 | ), 77 | ) 78 | ], 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | import 'package:image_picker_flutter/src/image_picker.dart'; 9 | import 'package:image_picker_flutter/src/model/asset_data.dart'; 10 | 11 | class Utils { 12 | Utils._(); 13 | 14 | ///插件包名,这个很重要 15 | static const String packageName = "image_picker_flutter"; 16 | 17 | ///插件通道 18 | static const MethodChannel channel = const MethodChannel('image_picker'); 19 | 20 | ///日志开关 21 | static bool isDebug = !kReleaseMode; 22 | 23 | static Future> getFolders(ImagePickerType type) async { 24 | final List a = await channel.invokeMethod( 25 | 'getFolders', 26 | getType(type), 27 | ); 28 | List folders = a.map((a) { 29 | String folder = a; 30 | return folder; 31 | }).toList(); 32 | return folders; 33 | } 34 | 35 | ///获取图片视频资源 36 | static Future> getImages(String folder) async { 37 | final List a = await channel.invokeMethod( 38 | 'getImages', 39 | folder, 40 | ); 41 | final List data = a.map( 42 | (a) { 43 | AssetData b = AssetData.fromJson(a); 44 | return b; 45 | }, 46 | ).toList(); 47 | 48 | data.sort((a, b) { 49 | return b.time.compareTo(a.time); 50 | }); 51 | return data; 52 | } 53 | 54 | ///获取资源绝对路径,并更新path、name、mimeType 55 | static Future updateAndGetPath(AssetData data) async { 56 | if (Platform.isIOS && (data.path == null || data.path.isEmpty)) { 57 | String path = await Utils.channel.invokeMethod("getPath", data.id); 58 | data.path = path; 59 | data.name = path.split("/").last; 60 | data.mimeType = 61 | "${data.mimeType}${data.path.split(".").last.toLowerCase()}"; 62 | } 63 | } 64 | 65 | ///取消任务 66 | static void cancelAll() async { 67 | await channel.invokeMethod("cancelAll"); 68 | } 69 | 70 | ///拍照 71 | static Future takePicture() async { 72 | dynamic a = await channel.invokeMethod("takePicture"); 73 | AssetData b = AssetData.fromJson(a); 74 | b = await convertSingleData(b); 75 | return b; 76 | } 77 | 78 | ///录制视频 79 | static Future takeVideo() async { 80 | dynamic a = await channel.invokeMethod("takeVideo"); 81 | AssetData b = AssetData.fromJson(a); 82 | b = await convertSingleData(b); 83 | return b; 84 | } 85 | 86 | ///多选数据组合 87 | static Future> convertMulData(List data) async { 88 | for (AssetData a in data) { 89 | await updateAndGetPath(a); 90 | } 91 | return data; 92 | } 93 | 94 | ///单选数据组合 95 | static Future convertSingleData(AssetData data) async { 96 | await updateAndGetPath(data); 97 | return data; 98 | } 99 | 100 | ///默认的图片加载loading 101 | static final AssetImage placeholder = AssetImage( 102 | "images/placeholder.webp", 103 | package: packageName, 104 | ); 105 | 106 | ///默认的返回键icon 107 | static final IconData back = IconData( 108 | 0xe62a, 109 | fontFamily: 'iconfont', 110 | fontPackage: packageName, 111 | ); 112 | 113 | ///默认的保存icon 114 | static final IconData save = IconData( 115 | 0xe601, 116 | fontFamily: 'iconfont', 117 | fontPackage: packageName, 118 | ); 119 | 120 | ///视频标签 121 | static final IconData video = IconData( 122 | 0xe641, 123 | fontFamily: 'iconfont', 124 | fontPackage: packageName, 125 | ); 126 | 127 | ///image_picker请求类型 128 | static int getType(ImagePickerType type) { 129 | switch (type) { 130 | case ImagePickerType.onlyImage: 131 | return 1; 132 | case ImagePickerType.onlyVideo: 133 | return 2; 134 | case ImagePickerType.imageAndVideo: 135 | return 3; 136 | } 137 | return 3; 138 | } 139 | 140 | ///屏幕宽度(单位pix) 141 | static int width2px(BuildContext context, {double ratio = 1}) { 142 | var m = MediaQuery.of(context); 143 | int a = m.size.width * m.devicePixelRatio ~/ ratio; 144 | return a; 145 | } 146 | 147 | ///图片文件直接使用判断,目前支持jpg,png,webp,gif 148 | static bool isSupportImageFormatString(Uint8List bytes) { 149 | String format = imageFormatString(bytes); 150 | log("==================================="); 151 | log(format); 152 | log('${bytes[0].toRadixString(16)} ${bytes[1].toRadixString(16)} ${bytes[2].toRadixString(16)}'); 153 | return format != "unknow"; 154 | } 155 | 156 | ///数据流判断文件类型 157 | static String imageFormatString(Uint8List bytes) { 158 | if (bytes == null || bytes.isEmpty) { 159 | return "unknow"; 160 | } 161 | int format = bytes[0]; 162 | if (format == 0xff) { 163 | return ".jpg"; 164 | } else if (format == 0x89) { 165 | return ".png"; 166 | } else if (format == 0x47) { 167 | return ".gif"; 168 | } else if (format == 0x52) { 169 | return ".webp"; 170 | } else { 171 | return "unknow"; 172 | } 173 | } 174 | 175 | ///日志输出 176 | static log(dynamic o) { 177 | if (isDebug) { 178 | print(o); 179 | } 180 | } 181 | 182 | static bool isEmpty(String s) { 183 | return s == null || s.isEmpty; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: image_picker_flutter 2 | description: A Flutter plugin for iOS and Android for picking images from the image library. 3 | version: 1.4.4 4 | author: taijuan <673636090@qq.com> 5 | homepage: https://github.com/taijuan/image_picker_flutter.git 6 | 7 | environment: 8 | sdk: ">=2.2.0 <3.0.0" 9 | 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | flutter: 16 | plugin: 17 | androidPackage: com.taijuan.image_picker_flutter 18 | pluginClass: ImagePickerFlutterPlugin 19 | 20 | #图片 21 | assets: 22 | - images/placeholder.webp 23 | 24 | #字体Icon 25 | fonts: 26 | - family: iconfont 27 | fonts: 28 | - asset: fonts/iconfont.ttf 29 | --------------------------------------------------------------------------------