├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── bk_flutter_image_android.iml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── bk │ │ └── flutter │ │ └── bk_texture_image │ │ ├── BkCallArgs.java │ │ ├── BkFlutterImagePlugin.java │ │ ├── BkLogger.java │ │ ├── BkLruCache.java │ │ ├── BkTextureRecord.java │ │ └── BkTextureRecordManager.java │ └── res │ └── drawable │ └── ic_launcher.png ├── bk_flutter_image.iml ├── example ├── .gitignore ├── .metadata ├── .vscode │ └── settings.json ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ ├── com │ │ │ │ │ └── example │ │ │ │ │ │ └── bk_flutter_image_example │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── io │ │ │ │ │ └── flutter │ │ │ │ │ └── plugins │ │ │ │ │ └── GeneratedPluginRegistrant.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── local.properties │ └── settings.gradle ├── images │ ├── 00.jpg │ ├── 01.jpg │ ├── 02.jpg │ ├── 03.jpg │ ├── 04.jpg │ ├── 05.jpg │ ├── 06.jpg │ ├── 07.jpg │ ├── 08.jpg │ └── 09.jpg ├── ios │ ├── Flutter │ │ ├── .last_build_id │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── beike.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ └── 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 │ │ ├── GeneratedPluginRegistrant.h │ │ ├── GeneratedPluginRegistrant.m │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── exts.dart │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── Assets │ ├── .gitkeep │ ├── glsl.fsh │ └── glsl.vsh ├── Classes │ ├── BkFlutterImagePlugin.h │ ├── BkFlutterImagePlugin.m │ ├── BkFlutterPixelBufferCache.h │ ├── BkFlutterPixelBufferCache.m │ ├── BkImageRenderWorker.h │ └── BkImageRenderWorker.m └── bk_flutter_image.podspec ├── lib ├── bk_flutter_image.dart ├── bk_flutter_image_native.dart └── bk_flutter_image_web.dart ├── pubspec.yaml └── test └── bk_flutter_image_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .idea/ 10 | pubspec.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright(c) 2017 Lianjia, Inc. All Rights Reserved 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bk_flutter_image——Flutter图片内存优化库 2 | 3 | ## 背景 4 | 随着移动端业务中更多Flutter应用, 多图、大图复杂页面使用Flutter的Image.network(..) , FadeInImage.network(..)易出现出现OOM问题 5 | * [实践](https://mp.weixin.qq.com/s/yUm4UFggYLgDbj4_JCjEdg) 6 | 7 | ## 指南 8 | 9 | ### Android依赖 Glide 4.11.0 10 | 11 | ```gradle 12 | dependencies { 13 | implementation 'com.github.bumptech.glide:glide:4.11.0' 14 | } 15 | ``` 16 | 17 | ### iOS 依赖 SDWebImage 5.12.6及以上 https://github.com/SDWebImage/SDWebImage/issues/3351 18 | pod 'SDWebImage','5.12.6' 19 | 20 | ### 使用方式 21 | 22 | ``` 23 | BkFlutterImage( 24 | url: imageUrl, 25 | width: width, 26 | height: height, 27 | autoResize: true, 28 | ... 29 | ) 30 | ``` 31 | 32 | ## License 33 | 34 | 详情参见 [LICENSE](./LICENSE)。 35 | 36 | ## 版本历史 37 | 具体版本历史请参看 [CHANGELOG.md](./CHANGELOG.md)。 38 | -------------------------------------------------------------------------------- /android/bk_flutter_image_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.ke.flutter.texture_image' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 21 29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 30 | } 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation 'com.github.bumptech.glide:glide:4.11.0' 42 | } 43 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | package android 2 | 3 | rootProject.name = 'bk_flutter_image' 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/bk/flutter/bk_texture_image/BkCallArgs.java: -------------------------------------------------------------------------------- 1 | package com.bk.flutter.bk_texture_image; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import io.flutter.plugin.common.MethodCall; 6 | 7 | public class BkCallArgs { 8 | 9 | MethodCall call; 10 | String url; 11 | int width; 12 | int height; 13 | boolean autoResize; 14 | 15 | long textureId; 16 | 17 | public BkCallArgs(@NonNull MethodCall call) { 18 | this.call = call; 19 | this.url = call.argument("url"); 20 | 21 | if (call.argument("width") != null) { 22 | double w = call.argument("width"); 23 | this.width = (int) w; 24 | } 25 | 26 | if (call.argument("height") != null) { 27 | double h = call.argument("height"); 28 | this.height = (int) h; 29 | } 30 | 31 | if (call.argument("autoResize") != null) { 32 | this.autoResize = call.argument("autoResize"); 33 | } 34 | 35 | } 36 | 37 | public long getTextureId() { 38 | return textureId; 39 | } 40 | 41 | public void setTextureId(long textureId) { 42 | this.textureId = textureId; 43 | } 44 | 45 | public String uniqueKey() { 46 | return String.valueOf(url); 47 | } 48 | 49 | public boolean hasReusableTexture(BkCallArgs targetArgs) { 50 | return (this.url.equals(targetArgs.url) && width >= targetArgs.width && height >= targetArgs.height); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "CallArgs{" + 56 | "url='" + url + '\'' + 57 | ", width=" + width + 58 | ", height=" + height + 59 | ", autoResize=" + autoResize + 60 | ", textureId=" + textureId + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /android/src/main/java/com/bk/flutter/bk_texture_image/BkFlutterImagePlugin.java: -------------------------------------------------------------------------------- 1 | package com.bk.flutter.bk_texture_image; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.PaintFlagsDrawFilter; 8 | import android.graphics.Rect; 9 | import android.graphics.SurfaceTexture; 10 | import android.os.Debug; 11 | import android.view.Surface; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | 16 | import com.bumptech.glide.Glide; 17 | import com.bumptech.glide.MemoryCategory; 18 | import com.bumptech.glide.load.DataSource; 19 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 20 | import com.bumptech.glide.load.engine.GlideException; 21 | import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; 22 | import com.bumptech.glide.load.engine.cache.MemoryCache; 23 | import com.bumptech.glide.load.resource.bitmap.BitmapEncoder; 24 | import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy; 25 | import com.bumptech.glide.request.RequestListener; 26 | import com.bumptech.glide.request.RequestOptions; 27 | import com.bumptech.glide.request.target.Target; 28 | import com.bumptech.glide.util.Executors; 29 | import com.bumptech.glide.util.Util; 30 | 31 | import io.flutter.embedding.engine.plugins.activity.ActivityAware; 32 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 33 | 34 | import javax.microedition.khronos.opengles.GL10; 35 | 36 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 37 | import io.flutter.plugin.common.MethodCall; 38 | import io.flutter.plugin.common.MethodChannel; 39 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 40 | import io.flutter.plugin.common.MethodChannel.Result; 41 | import io.flutter.plugin.common.PluginRegistry.Registrar; 42 | import io.flutter.view.TextureRegistry; 43 | 44 | /** 45 | * BkFlutterTextureImageViewPlugin 46 | */ 47 | public class BkFlutterImagePlugin implements FlutterPlugin, MethodCallHandler, 48 | ActivityAware { 49 | 50 | private static final String CHANNEL_NAME = "bk_flutter_image"; 51 | private static final String METHOD_CREATE = "create"; 52 | private static final String METHOD_DISPOSE = "dispose"; 53 | private static final String METHOD_CACHE = "setCacheSize"; 54 | private static final String METHOD_UPDATE = "updateUrl"; 55 | private static final String TAG = "BkFlutterImagePlugin"; 56 | 57 | // surfaceTexture 宽高限制 58 | private static final int MAX_PIXELS = Math.min(GL10.GL_MAX_VIEWPORT_DIMS, GL10.GL_MAX_TEXTURE_SIZE); 59 | private static final int LIMIT_NATIVE_HEAP_SIZE = 157286400; //[经验值] 内存大于150MB的时候清理bitmap内存 60 | private Context mContext; 61 | // private Activity mActivity; 62 | private TextureRegistry mTextureRegistry; 63 | private float density; 64 | private int widthPixels; 65 | private int heightPixels; 66 | 67 | private BkTextureRecordManager mRecordManager; 68 | 69 | public BkFlutterImagePlugin() { 70 | mRecordManager = new BkTextureRecordManager(); 71 | } 72 | 73 | @Override 74 | public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 75 | final MethodChannel channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), CHANNEL_NAME); 76 | channel.setMethodCallHandler(this); 77 | mContext = flutterPluginBinding.getApplicationContext(); 78 | /// 插件接口获取texture注册器 79 | mTextureRegistry = flutterPluginBinding.getTextureRegistry(); 80 | 81 | density = mContext.getResources().getDisplayMetrics().density; 82 | widthPixels = mContext.getResources().getDisplayMetrics().widthPixels; 83 | heightPixels = mContext.getResources().getDisplayMetrics().heightPixels; 84 | } 85 | 86 | public static void registerWith(Registrar registrar) { 87 | throw new IllegalArgumentException("Error, please use v2 FlutterActivity!!"); 88 | } 89 | 90 | @Override 91 | public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { 92 | try { 93 | if (METHOD_CREATE.equals(call.method)) { 94 | createTextureImage(call, result); 95 | } else if (METHOD_DISPOSE.equals(call.method)) { 96 | disposeTextureImage(call, result); 97 | } else if (METHOD_CACHE.equals(call.method)) { 98 | setMaxCacheSize(call, result); 99 | }else { 100 | result.notImplemented(); 101 | } 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } 105 | } 106 | 107 | private void setMaxCacheSize(MethodCall call, Result result) { 108 | double memoryMaxSize = call.argument("memoryMaxSize");; 109 | double diskMaxSize = call.argument("diskMaxSize"); 110 | mRecordManager.setCacheMaxSize(diskMaxSize, memoryMaxSize); 111 | } 112 | 113 | private void createTextureImage(MethodCall call, Result result) { 114 | BkCallArgs args = new BkCallArgs(call); 115 | 116 | BkTextureRecord textureRecord = mRecordManager.findReusableByCall(args); 117 | 118 | if (textureRecord != null && textureRecord.isValid()) { 119 | safeReply(result, textureRecord.response()); 120 | } else { 121 | BkTextureRecord record = new BkTextureRecord(args, mTextureRegistry.createSurfaceTexture()); 122 | mRecordManager.add(record); 123 | args.setTextureId(record.getTextureId()); 124 | loadTextureImage(args, result, record); 125 | } 126 | 127 | } 128 | 129 | private void disposeTextureImage(MethodCall call, Result result) { 130 | try { 131 | final int textureId = call.argument("textureId"); 132 | BkTextureRecord record = mRecordManager.maybeCacheAndReleaseOldest(textureId); 133 | if (record != null) { 134 | BkLogger.d("release texture memory : " + record); 135 | } 136 | } catch (Exception e) { 137 | e.printStackTrace(); 138 | } 139 | } 140 | 141 | private void loadTextureImage(final BkCallArgs callArgs, final MethodChannel.Result result, final BkTextureRecord textureRecord) { 142 | 143 | final String url = callArgs.url; 144 | 145 | Glide.get(mContext).setMemoryCategory(MemoryCategory.LOW); 146 | final boolean autoResize = callArgs.autoResize; 147 | textureRecord.setFullPixel(!autoResize); 148 | RequestOptions options = new RequestOptions() 149 | .diskCacheStrategy(DiskCacheStrategy.ALL) 150 | .downsample(DownsampleStrategy.DEFAULT); 151 | if (autoResize) { 152 | options = options.override(Math.min(callArgs.width, MAX_PIXELS), Math.min(callArgs.height, MAX_PIXELS)); 153 | } 154 | 155 | Glide.with(mContext).asBitmap().load(url).listener(new RequestListener() { 156 | @Override 157 | public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { 158 | BkLogger.d("glide onLoadFailed callArgs=" + callArgs.toString()); 159 | textureRecord.setTextureValid(false); 160 | textureRecord.setError(e != null ? e.getMessage() : ""); 161 | safeReply(result, textureRecord.response()); 162 | return true; 163 | } 164 | 165 | @Override 166 | public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { 167 | try { 168 | int targetWidth = Math.min(resource.getWidth(), MAX_PIXELS); 169 | int targetHeight = Math.min(resource.getHeight(), MAX_PIXELS); 170 | textureRecord.setTextureWH(targetWidth, targetHeight); 171 | 172 | BkLogger.d("glide onResourceReady w:h = " + targetWidth + ":" + targetHeight + ", fr:" + isFirstResource + ", ds:" +dataSource); 173 | // 画布显示区域 174 | Rect canvasRect = new Rect(0, 0, targetWidth, targetHeight); 175 | SurfaceTexture surfaceTexture = textureRecord.getTextureEntry().surfaceTexture(); 176 | Surface surface = new Surface(surfaceTexture); 177 | if (!surface.isValid()) { 178 | BkLogger.d("surface invalid"); 179 | return false; 180 | } 181 | surfaceTexture.setDefaultBufferSize(targetWidth, targetHeight); 182 | Canvas canvas = surface.lockCanvas(canvasRect); 183 | 184 | // 图片显示区域 185 | Rect dstRect = new Rect(0, 0, targetWidth, targetHeight); 186 | 187 | // 图片是否scale ? 188 | canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); 189 | canvas.drawBitmap(resource, null, dstRect, null); 190 | 191 | surface.unlockCanvasAndPost(canvas); 192 | surface.release(); 193 | 194 | safeReply(result, textureRecord.response()); 195 | 196 | } catch (Exception e) { 197 | e.printStackTrace(); 198 | BkLogger.d("glide onResourceReady Exception callArgs=" + callArgs.toString()); 199 | //result.success(-1); 200 | //标志纹理失败 201 | textureRecord.setError(e.getMessage()); 202 | textureRecord.setTextureValid(false); 203 | safeReply(result, textureRecord.response()); 204 | } finally { 205 | long nativeHeapAllocatedSize = Debug.getNativeHeapAllocatedSize(); 206 | if (nativeHeapAllocatedSize >= LIMIT_NATIVE_HEAP_SIZE) { 207 | BkLogger.d("NativeHeapAllocatedSize: " + (nativeHeapAllocatedSize >> 20) + "MB"); 208 | clearGlideMemory(); 209 | } 210 | } 211 | 212 | return false; 213 | } 214 | }).apply(options).preload(); 215 | 216 | 217 | } 218 | @Override 219 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 220 | mContext = null; 221 | mTextureRegistry = null; 222 | cleanResource(); 223 | } 224 | 225 | private void safeReply(final MethodChannel.Result result, final String response) { 226 | Executors.mainThreadExecutor().execute(new Runnable() { 227 | @Override 228 | public void run() { 229 | try { 230 | result.success(response); 231 | } catch (Exception e) { 232 | BkLogger.d("safeReply MainLooper Exception =" + e.getLocalizedMessage()); 233 | } 234 | } 235 | }); 236 | } 237 | 238 | private void cleanResource() { 239 | mRecordManager.clean(); 240 | } 241 | 242 | private void clearGlideMemory() { 243 | try { 244 | if (Util.isOnMainThread()) { 245 | Glide.get(mContext).clearMemory(); 246 | } 247 | } catch (Exception e) { 248 | BkLogger.d("clearGlideMemory : " + e.getCause()); 249 | } 250 | } 251 | 252 | @Override 253 | public void onAttachedToActivity(ActivityPluginBinding binding) { 254 | 255 | } 256 | 257 | @Override 258 | public void onDetachedFromActivityForConfigChanges() { 259 | clearGlideMemory(); 260 | } 261 | 262 | @Override 263 | public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { 264 | 265 | } 266 | 267 | @Override 268 | public void onDetachedFromActivity() { 269 | clearGlideMemory(); 270 | } 271 | } -------------------------------------------------------------------------------- /android/src/main/java/com/bk/flutter/bk_texture_image/BkLogger.java: -------------------------------------------------------------------------------- 1 | package com.bk.flutter.bk_texture_image; 2 | 3 | import io.flutter.Log; 4 | 5 | public final class BkLogger { 6 | private static final String TAG = "TextureImageLogger"; 7 | private static final boolean DEVELOP = BuildConfig.DEBUG; 8 | 9 | public static void i(Object msg) { 10 | if (DEVELOP) { 11 | android.util.Log.i(TAG, String.valueOf(msg)); 12 | } else { 13 | Log.i(TAG, String.valueOf(msg)); 14 | } 15 | } 16 | 17 | public static void d(Object msg) { 18 | if (DEVELOP) { 19 | android.util.Log.d(TAG, String.valueOf(msg)); 20 | } else { 21 | Log.d(TAG, String.valueOf(msg)); 22 | } 23 | } 24 | 25 | public static void v(Object msg) { 26 | if (DEVELOP) { 27 | android.util.Log.v(TAG, String.valueOf(msg)); 28 | } else { 29 | Log.v(TAG, String.valueOf(msg)); 30 | } 31 | } 32 | 33 | public static void w(Object msg) { 34 | if (DEVELOP) { 35 | android.util.Log.w(TAG, String.valueOf(msg)); 36 | } else { 37 | Log.w(TAG, String.valueOf(msg)); 38 | } 39 | } 40 | 41 | public static void e(Object msg) { 42 | if (DEVELOP) { 43 | android.util.Log.e(TAG, String.valueOf(msg)); 44 | } else { 45 | Log.e(TAG, String.valueOf(msg)); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /android/src/main/java/com/bk/flutter/bk_texture_image/BkLruCache.java: -------------------------------------------------------------------------------- 1 | package com.bk.flutter.bk_texture_image; 2 | 3 | import android.util.LruCache; 4 | 5 | public class BkLruCache extends LruCache { 6 | 7 | /** 8 | * @param maxSize for caches that do not override {@link #sizeOf}, this is 9 | * the maximum number of entries in the cache. For all other caches, 10 | * this is the maximum sum of the sizes of the entries in this cache. 11 | */ 12 | public BkLruCache(int maxSize) { 13 | super(maxSize); 14 | } 15 | 16 | @Override 17 | protected void entryRemoved(boolean evicted, BkCallArgs key, BkTextureRecord oldValue, BkTextureRecord newValue) { 18 | super.entryRemoved(evicted, key, oldValue, newValue); 19 | oldValue.tryRelease(); 20 | } 21 | 22 | @Override 23 | protected int sizeOf(BkCallArgs key, BkTextureRecord textureRecord) { 24 | return textureRecord.sizeOfMegaBytes(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/java/com/bk/flutter/bk_texture_image/BkTextureRecord.java: -------------------------------------------------------------------------------- 1 | package com.bk.flutter.bk_texture_image; 2 | 3 | 4 | import android.os.Build.VERSION; 5 | import android.os.Build.VERSION_CODES; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | import io.flutter.view.TextureRegistry; 14 | 15 | public class BkTextureRecord { 16 | // 同一个纹理多次使用计数,当最后一个dispose时才移除 17 | private AtomicInteger refCount = new AtomicInteger(1); 18 | // 纹理是否有效 19 | private final AtomicBoolean textureValid = new AtomicBoolean(true); 20 | 21 | private TextureRegistry.SurfaceTextureEntry textureEntry; 22 | 23 | private BkCallArgs call; 24 | private long textureId = 0L; 25 | private double textureWidth = 0L; 26 | private double textureHeight = 0L; 27 | private boolean isFullPixel = false; 28 | private String mError; 29 | 30 | public BkTextureRecord(BkCallArgs call, TextureRegistry.SurfaceTextureEntry textureEntry) { 31 | this.textureEntry = textureEntry; 32 | this.call = call; 33 | } 34 | 35 | public int refIncrementAndGet() { 36 | return refCount.incrementAndGet(); 37 | } 38 | 39 | public int refDecrementAndGet() { 40 | return refCount.decrementAndGet(); 41 | } 42 | 43 | public AtomicBoolean getTextureValid() { 44 | return textureValid; 45 | } 46 | 47 | public void setTextureValid(boolean valid) { 48 | this.textureValid.set(valid); 49 | } 50 | 51 | public boolean isValid() { 52 | return textureEntry != null && textureValid.get(); 53 | } 54 | 55 | public TextureRegistry.SurfaceTextureEntry getTextureEntry() { 56 | textureId = textureEntry.id(); 57 | return textureEntry; 58 | } 59 | 60 | public void setTextureEntry(TextureRegistry.SurfaceTextureEntry textureEntry) { 61 | this.textureEntry = textureEntry; 62 | } 63 | 64 | public BkCallArgs getCall() { 65 | return call; 66 | } 67 | 68 | public long getTextureId() { 69 | return textureId; 70 | } 71 | 72 | public void tryRelease() { 73 | if (textureEntry != null && textureEntry.surfaceTexture() != null) { 74 | if (VERSION.SDK_INT >= VERSION_CODES.O) { 75 | if (!textureEntry.surfaceTexture().isReleased()) { 76 | textureEntry.release(); 77 | } 78 | } else { 79 | textureEntry.release(); 80 | } 81 | } 82 | } 83 | 84 | public void setTextureWH(double w, double h) { 85 | textureWidth = w; 86 | textureHeight = h; 87 | } 88 | 89 | public double getTextureHeight() { 90 | return textureHeight; 91 | } 92 | 93 | public double getTextureWidth() { 94 | return textureWidth; 95 | } 96 | 97 | public void setFullPixel(boolean fullPixel) { 98 | isFullPixel = fullPixel; 99 | } 100 | 101 | public void setError(String error) { 102 | this.mError = error; 103 | } 104 | 105 | /** 106 | * 纹理占用内容(MB) 107 | */ 108 | public int sizeOfMegaBytes() { 109 | return (int) (textureWidth * textureHeight * 4) >> 20; 110 | } 111 | 112 | public String response() { 113 | JSONObject jsonObject = new JSONObject(); 114 | try { 115 | jsonObject.put("textureId", isValid() ? textureId : -1); 116 | jsonObject.put("textureWidth", textureWidth); 117 | jsonObject.put("textureHeight", textureHeight); 118 | jsonObject.put("isFullPixel", isFullPixel); 119 | jsonObject.put("error", mError); 120 | } catch (JSONException e) { 121 | e.printStackTrace(); 122 | } 123 | return jsonObject.toString(); 124 | } 125 | 126 | @Override 127 | public String toString() { 128 | return "TextureRecord{" + 129 | "refCount=" + refCount.get() + 130 | ", textureValid=" + textureValid.get() + 131 | ", textureId=" + textureEntry.id() + 132 | ", callArgs='" + call + '\'' + 133 | '}'; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /android/src/main/java/com/bk/flutter/bk_texture_image/BkTextureRecordManager.java: -------------------------------------------------------------------------------- 1 | package com.bk.flutter.bk_texture_image; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | 5 | 6 | public class BkTextureRecordManager { 7 | 8 | private static final int DEF_CACHE_SIZE = 268435456; // 256MB 9 | private static final int MAX_CACHE_SIZE = 1073741824; // 1024MB 10 | private final ConcurrentHashMap textureRecords = new ConcurrentHashMap<>(); 11 | private final BkLruCache textureCacheRecords = new BkLruCache(DEF_CACHE_SIZE); 12 | 13 | BkTextureRecordManager() {} 14 | 15 | public void add(BkTextureRecord record) { 16 | textureRecords.put(record.getCall(), record); 17 | } 18 | 19 | public BkTextureRecord remove(BkTextureRecord record) { 20 | return textureRecords.remove(record.getCall()); 21 | } 22 | 23 | public BkTextureRecord findReusableByCall(BkCallArgs key) { 24 | for (BkCallArgs bkCallArgs : textureRecords.keySet()) { 25 | if (bkCallArgs.hasReusableTexture(key)) { 26 | return textureRecords.get(bkCallArgs); 27 | } 28 | } 29 | for (BkCallArgs bkCallArgs : textureCacheRecords.snapshot().keySet()) { 30 | // 如果找到可复用的纹理直接返回 31 | if (bkCallArgs.hasReusableTexture(key)) { 32 | return textureCacheRecords.get(bkCallArgs); 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | /** 39 | * 通过纹理 id 找到 40 | * @param textureId 41 | * @return 42 | */ 43 | public BkCallArgs findCallById(long textureId) { 44 | for (BkCallArgs bkCallArgs : textureRecords.keySet()) { 45 | if (textureId == bkCallArgs.getTextureId()) { 46 | return bkCallArgs; 47 | } 48 | } 49 | for (BkCallArgs bkCallArgs : textureCacheRecords.snapshot().keySet()) { 50 | if (textureId == bkCallArgs.getTextureId()) { 51 | return bkCallArgs; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | /** 58 | * 59 | * @param textureId: 待释放纹理 id 60 | * @return BkTextureRecord: 从缓存中清理的记录 61 | */ 62 | public BkTextureRecord maybeCacheAndReleaseOldest(long textureId) { 63 | BkCallArgs bkCallArgs = findCallById(textureId); 64 | if (bkCallArgs == null) { 65 | return null; 66 | } 67 | BkTextureRecord bkTextureRecord = textureRecords.get(bkCallArgs); 68 | textureRecords.remove(bkCallArgs); 69 | if (bkTextureRecord != null) { 70 | return textureCacheRecords.put(bkCallArgs, bkTextureRecord); 71 | } 72 | return null; 73 | } 74 | 75 | public void clean() { 76 | textureRecords.clear(); 77 | } 78 | 79 | /** 80 | * diskCacheMaxSize 磁盘缓存Android暂不关注. 81 | * memoryCacheMaxSize 设置 LruCache MaxSize, 当纹理需要释放时我们优先将其缓存起来 82 | */ 83 | public void setCacheMaxSize(double diskCacheMaxSize, double memoryCacheMaxSize) { 84 | if (memoryCacheMaxSize > MAX_CACHE_SIZE) { 85 | BkLogger.w(String.format("memoryCacheMaxSize > %s, set to MAX_CACHE_SIZE: %s", MAX_CACHE_SIZE, MAX_CACHE_SIZE)); 86 | this.textureCacheRecords.resize(MAX_CACHE_SIZE); 87 | } else { 88 | this.textureCacheRecords.resize((int) memoryCacheMaxSize); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /android/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/android/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /bk_flutter_image.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | ios/Pods/* 33 | ios/Runner.xcworkspace/xcuserdata/* 34 | ios/.symlinks/ 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Exceptions to above rules. 39 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 40 | -------------------------------------------------------------------------------- /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: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # bk_flutter_image_example 2 | 3 | Demonstrates how to use the bk_flutter_image 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.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | 40 | defaultConfig { 41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 42 | applicationId "com.example.bk_flutter_image_example" 43 | minSdkVersion 21 44 | targetSdkVersion 28 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 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/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/bk_flutter_image_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.bk_flutter_image_example; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.NonNull; 6 | import io.flutter.embedding.android.FlutterActivity; 7 | import io.flutter.embedding.engine.FlutterEngine; 8 | import io.flutter.plugins.GeneratedPluginRegistrant; 9 | 10 | public class MainActivity extends FlutterActivity { 11 | @Override 12 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 13 | GeneratedPluginRegistrant.registerWith(flutterEngine); 14 | } 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | try { 20 | Class.forName("dalvik.system.CloseGuard") 21 | .getMethod("setEnabled", boolean.class) 22 | .invoke(null, true); 23 | } catch (ReflectiveOperationException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import androidx.annotation.Keep; 4 | import androidx.annotation.NonNull; 5 | import io.flutter.Log; 6 | 7 | import io.flutter.embedding.engine.FlutterEngine; 8 | 9 | /** 10 | * Generated file. Do not edit. 11 | * This file is generated by the Flutter tool based on the 12 | * plugins that support the Android platform. 13 | */ 14 | @Keep 15 | public final class GeneratedPluginRegistrant { 16 | private static final String TAG = "GeneratedPluginRegistrant"; 17 | public static void registerWith(@NonNull FlutterEngine flutterEngine) { 18 | try { 19 | flutterEngine.getPlugins().add(new com.bk.flutter.bk_texture_image.BkFlutterImagePlugin()); 20 | } catch(Exception e) { 21 | Log.e(TAG, "Error registering plugin bk_flutter_image, com.bk.flutter.bk_texture_image.BkFlutterImagePlugin", e); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | jcenter() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /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.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /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/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/local.properties: -------------------------------------------------------------------------------- 1 | sdk.dir=/Users/zhaohongwei/Library/Android/sdk 2 | flutter.sdk=/Users/zhaohongwei/.flutterw/lianjia/2.10.4.5 -------------------------------------------------------------------------------- /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/images/00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/00.jpg -------------------------------------------------------------------------------- /example/images/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/01.jpg -------------------------------------------------------------------------------- /example/images/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/02.jpg -------------------------------------------------------------------------------- /example/images/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/03.jpg -------------------------------------------------------------------------------- /example/images/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/04.jpg -------------------------------------------------------------------------------- /example/images/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/05.jpg -------------------------------------------------------------------------------- /example/images/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/06.jpg -------------------------------------------------------------------------------- /example/images/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/07.jpg -------------------------------------------------------------------------------- /example/images/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/08.jpg -------------------------------------------------------------------------------- /example/images/09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/images/09.jpg -------------------------------------------------------------------------------- /example/ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 4405250439844095dccd0762deff4833 -------------------------------------------------------------------------------- /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/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '10.0' 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_ios_podfile_setup 28 | 29 | target 'Runner' do 30 | pod 'SDWebImage', '5.12.6' 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /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 | 5715BEDF2832790100A48E87 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5715BEDE2832790100A48E87 /* CoreGraphics.framework */; }; 13 | 5715BEE228327A8500A48E87 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5715BEE128327A8500A48E87 /* CoreServices.framework */; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 18 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 19 | A9A3ADC9B5F84455289AC94B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B5759CD057BF385707014CC /* libPods-Runner.a */; }; 20 | E482E97D24EE7522009BF6BF /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 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 | ); 31 | name = "Embed Frameworks"; 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXCopyFilesBuildPhase section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 38 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 39 | 2CCFF51D683FE8585F6C845D /* 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 = ""; }; 40 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 41 | 3B5759CD057BF385707014CC /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 43 | 5715BEDE2832790100A48E87 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | 5715BEE128327A8500A48E87 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 45 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 46 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 48 | 7BAA500530288766DF596E24 /* 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 = ""; }; 49 | 7EF06221AEFF8DE7B023EAA7 /* 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 = ""; }; 50 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 51 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 52 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 5715BEDF2832790100A48E87 /* CoreGraphics.framework in Frameworks */, 66 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 67 | E482E97D24EE7522009BF6BF /* App.framework in Frameworks */, 68 | 5715BEE228327A8500A48E87 /* CoreServices.framework in Frameworks */, 69 | A9A3ADC9B5F84455289AC94B /* libPods-Runner.a in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 27CCEDA1863563A67524BED3 /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 5715BEE128327A8500A48E87 /* CoreServices.framework */, 80 | 5715BEDE2832790100A48E87 /* CoreGraphics.framework */, 81 | 3B5759CD057BF385707014CC /* libPods-Runner.a */, 82 | ); 83 | name = Frameworks; 84 | sourceTree = ""; 85 | }; 86 | 9740EEB11CF90186004384FC /* Flutter */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 3B80C3931E831B6300D905FE /* App.framework */, 90 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 91 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 92 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 93 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 94 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 95 | ); 96 | name = Flutter; 97 | sourceTree = ""; 98 | }; 99 | 97C146E51CF9000F007C117D = { 100 | isa = PBXGroup; 101 | children = ( 102 | 9740EEB11CF90186004384FC /* Flutter */, 103 | 97C146F01CF9000F007C117D /* Runner */, 104 | 97C146EF1CF9000F007C117D /* Products */, 105 | DD5A77578AE4E06F588FC829 /* Pods */, 106 | 27CCEDA1863563A67524BED3 /* Frameworks */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | 97C146EF1CF9000F007C117D /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 97C146EE1CF9000F007C117D /* Runner.app */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 97C146F01CF9000F007C117D /* Runner */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 122 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 123 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 124 | 97C147021CF9000F007C117D /* Info.plist */, 125 | 97C146F11CF9000F007C117D /* Supporting Files */, 126 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 127 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 128 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 129 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 130 | ); 131 | path = Runner; 132 | sourceTree = ""; 133 | }; 134 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | ); 138 | name = "Supporting Files"; 139 | sourceTree = ""; 140 | }; 141 | DD5A77578AE4E06F588FC829 /* Pods */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 7BAA500530288766DF596E24 /* Pods-Runner.debug.xcconfig */, 145 | 7EF06221AEFF8DE7B023EAA7 /* Pods-Runner.release.xcconfig */, 146 | 2CCFF51D683FE8585F6C845D /* Pods-Runner.profile.xcconfig */, 147 | ); 148 | path = Pods; 149 | sourceTree = ""; 150 | }; 151 | /* End PBXGroup section */ 152 | 153 | /* Begin PBXNativeTarget section */ 154 | 97C146ED1CF9000F007C117D /* Runner */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 157 | buildPhases = ( 158 | CACC213BD4D747A2309C8921 /* [CP] Check Pods Manifest.lock */, 159 | 9740EEB61CF901F6004384FC /* Run Script */, 160 | 97C146EA1CF9000F007C117D /* Sources */, 161 | 97C146EB1CF9000F007C117D /* Frameworks */, 162 | 97C146EC1CF9000F007C117D /* Resources */, 163 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 164 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 165 | EAB7C4205C42D36A094F8DDB /* [CP] Copy Pods Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = Runner; 172 | productName = Runner; 173 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 174 | productType = "com.apple.product-type.application"; 175 | }; 176 | /* End PBXNativeTarget section */ 177 | 178 | /* Begin PBXProject section */ 179 | 97C146E61CF9000F007C117D /* Project object */ = { 180 | isa = PBXProject; 181 | attributes = { 182 | LastUpgradeCheck = 1020; 183 | ORGANIZATIONNAME = "The Chromium Authors"; 184 | TargetAttributes = { 185 | 97C146ED1CF9000F007C117D = { 186 | CreatedOnToolsVersion = 7.3.1; 187 | LastSwiftMigration = 1100; 188 | ProvisioningStyle = Manual; 189 | }; 190 | }; 191 | }; 192 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 193 | compatibilityVersion = "Xcode 3.2"; 194 | developmentRegion = en; 195 | hasScannedForEncodings = 0; 196 | knownRegions = ( 197 | en, 198 | Base, 199 | ); 200 | mainGroup = 97C146E51CF9000F007C117D; 201 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 202 | projectDirPath = ""; 203 | projectRoot = ""; 204 | targets = ( 205 | 97C146ED1CF9000F007C117D /* Runner */, 206 | ); 207 | }; 208 | /* End PBXProject section */ 209 | 210 | /* Begin PBXResourcesBuildPhase section */ 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | inputPaths = ( 231 | ); 232 | name = "Thin Binary"; 233 | outputPaths = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | shellPath = /bin/sh; 237 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n\n"; 238 | }; 239 | 9740EEB61CF901F6004384FC /* Run Script */ = { 240 | isa = PBXShellScriptBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | inputPaths = ( 245 | ); 246 | name = "Run Script"; 247 | outputPaths = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; 252 | }; 253 | CACC213BD4D747A2309C8921 /* [CP] Check Pods Manifest.lock */ = { 254 | isa = PBXShellScriptBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | ); 258 | inputFileListPaths = ( 259 | ); 260 | inputPaths = ( 261 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 262 | "${PODS_ROOT}/Manifest.lock", 263 | ); 264 | name = "[CP] Check Pods Manifest.lock"; 265 | outputFileListPaths = ( 266 | ); 267 | outputPaths = ( 268 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | shellPath = /bin/sh; 272 | 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"; 273 | showEnvVarsInLog = 0; 274 | }; 275 | EAB7C4205C42D36A094F8DDB /* [CP] Copy Pods Resources */ = { 276 | isa = PBXShellScriptBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | inputPaths = ( 281 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", 282 | "${PODS_CONFIGURATION_BUILD_DIR}/bk_flutter_image/BkFlutterImage.bundle", 283 | ); 284 | name = "[CP] Copy Pods Resources"; 285 | outputPaths = ( 286 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BkFlutterImage.bundle", 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | shellPath = /bin/sh; 290 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 291 | showEnvVarsInLog = 0; 292 | }; 293 | /* End PBXShellScriptBuildPhase section */ 294 | 295 | /* Begin PBXSourcesBuildPhase section */ 296 | 97C146EA1CF9000F007C117D /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 301 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | /* End PBXSourcesBuildPhase section */ 306 | 307 | /* Begin PBXVariantGroup section */ 308 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 309 | isa = PBXVariantGroup; 310 | children = ( 311 | 97C146FB1CF9000F007C117D /* Base */, 312 | ); 313 | name = Main.storyboard; 314 | sourceTree = ""; 315 | }; 316 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 317 | isa = PBXVariantGroup; 318 | children = ( 319 | 97C147001CF9000F007C117D /* Base */, 320 | ); 321 | name = LaunchScreen.storyboard; 322 | sourceTree = ""; 323 | }; 324 | /* End PBXVariantGroup section */ 325 | 326 | /* Begin XCBuildConfiguration section */ 327 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 328 | isa = XCBuildConfiguration; 329 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_COMMA = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 343 | CLANG_WARN_EMPTY_BODY = YES; 344 | CLANG_WARN_ENUM_CONVERSION = YES; 345 | CLANG_WARN_INFINITE_RECURSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 348 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 359 | ENABLE_NS_ASSERTIONS = NO; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 370 | MTL_ENABLE_DEBUG_INFO = NO; 371 | SDKROOT = iphoneos; 372 | SUPPORTED_PLATFORMS = iphoneos; 373 | TARGETED_DEVICE_FAMILY = "1,2"; 374 | VALIDATE_PRODUCT = YES; 375 | }; 376 | name = Profile; 377 | }; 378 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 379 | isa = XCBuildConfiguration; 380 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 381 | buildSettings = { 382 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 383 | CLANG_ENABLE_MODULES = YES; 384 | CODE_SIGN_IDENTITY = "Apple Development"; 385 | CODE_SIGN_STYLE = Automatic; 386 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 387 | DEVELOPMENT_TEAM = W4PCLY57NK; 388 | ENABLE_BITCODE = NO; 389 | FRAMEWORK_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "$(PROJECT_DIR)/Flutter", 392 | ); 393 | INFOPLIST_FILE = Runner/Info.plist; 394 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 396 | LIBRARY_SEARCH_PATHS = ( 397 | "$(inherited)", 398 | "$(PROJECT_DIR)/Flutter", 399 | ); 400 | PRODUCT_BUNDLE_IDENTIFIER = com.zhw.test3; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | PROVISIONING_PROFILE_SPECIFIER = ""; 403 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 404 | SWIFT_VERSION = 5.0; 405 | VERSIONING_SYSTEM = "apple-generic"; 406 | }; 407 | name = Profile; 408 | }; 409 | 97C147031CF9000F007C117D /* Debug */ = { 410 | isa = XCBuildConfiguration; 411 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 412 | buildSettings = { 413 | ALWAYS_SEARCH_USER_PATHS = NO; 414 | CLANG_ANALYZER_NONNULL = YES; 415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 416 | CLANG_CXX_LIBRARY = "libc++"; 417 | CLANG_ENABLE_MODULES = YES; 418 | CLANG_ENABLE_OBJC_ARC = YES; 419 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_COMMA = YES; 422 | CLANG_WARN_CONSTANT_CONVERSION = YES; 423 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 424 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 425 | CLANG_WARN_EMPTY_BODY = YES; 426 | CLANG_WARN_ENUM_CONVERSION = YES; 427 | CLANG_WARN_INFINITE_RECURSION = YES; 428 | CLANG_WARN_INT_CONVERSION = YES; 429 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 430 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 431 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 433 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 434 | CLANG_WARN_STRICT_PROTOTYPES = YES; 435 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 436 | CLANG_WARN_UNREACHABLE_CODE = YES; 437 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 438 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 439 | COPY_PHASE_STRIP = NO; 440 | DEBUG_INFORMATION_FORMAT = dwarf; 441 | ENABLE_STRICT_OBJC_MSGSEND = YES; 442 | ENABLE_TESTABILITY = YES; 443 | GCC_C_LANGUAGE_STANDARD = gnu99; 444 | GCC_DYNAMIC_NO_PIC = NO; 445 | GCC_NO_COMMON_BLOCKS = YES; 446 | GCC_OPTIMIZATION_LEVEL = 0; 447 | GCC_PREPROCESSOR_DEFINITIONS = ( 448 | "DEBUG=1", 449 | "$(inherited)", 450 | ); 451 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 452 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 453 | GCC_WARN_UNDECLARED_SELECTOR = YES; 454 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 455 | GCC_WARN_UNUSED_FUNCTION = YES; 456 | GCC_WARN_UNUSED_VARIABLE = YES; 457 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 458 | MTL_ENABLE_DEBUG_INFO = YES; 459 | ONLY_ACTIVE_ARCH = YES; 460 | SDKROOT = iphoneos; 461 | TARGETED_DEVICE_FAMILY = "1,2"; 462 | }; 463 | name = Debug; 464 | }; 465 | 97C147041CF9000F007C117D /* Release */ = { 466 | isa = XCBuildConfiguration; 467 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 468 | buildSettings = { 469 | ALWAYS_SEARCH_USER_PATHS = NO; 470 | CLANG_ANALYZER_NONNULL = YES; 471 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 472 | CLANG_CXX_LIBRARY = "libc++"; 473 | CLANG_ENABLE_MODULES = YES; 474 | CLANG_ENABLE_OBJC_ARC = YES; 475 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 476 | CLANG_WARN_BOOL_CONVERSION = YES; 477 | CLANG_WARN_COMMA = YES; 478 | CLANG_WARN_CONSTANT_CONVERSION = YES; 479 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 480 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 481 | CLANG_WARN_EMPTY_BODY = YES; 482 | CLANG_WARN_ENUM_CONVERSION = YES; 483 | CLANG_WARN_INFINITE_RECURSION = YES; 484 | CLANG_WARN_INT_CONVERSION = YES; 485 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 486 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 487 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 488 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 489 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 490 | CLANG_WARN_STRICT_PROTOTYPES = YES; 491 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 492 | CLANG_WARN_UNREACHABLE_CODE = YES; 493 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 494 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 495 | COPY_PHASE_STRIP = NO; 496 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 497 | ENABLE_NS_ASSERTIONS = NO; 498 | ENABLE_STRICT_OBJC_MSGSEND = YES; 499 | GCC_C_LANGUAGE_STANDARD = gnu99; 500 | GCC_NO_COMMON_BLOCKS = YES; 501 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 502 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 503 | GCC_WARN_UNDECLARED_SELECTOR = YES; 504 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 505 | GCC_WARN_UNUSED_FUNCTION = YES; 506 | GCC_WARN_UNUSED_VARIABLE = YES; 507 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 508 | MTL_ENABLE_DEBUG_INFO = NO; 509 | SDKROOT = iphoneos; 510 | SUPPORTED_PLATFORMS = iphoneos; 511 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 512 | TARGETED_DEVICE_FAMILY = "1,2"; 513 | VALIDATE_PRODUCT = YES; 514 | }; 515 | name = Release; 516 | }; 517 | 97C147061CF9000F007C117D /* Debug */ = { 518 | isa = XCBuildConfiguration; 519 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 520 | buildSettings = { 521 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 522 | CLANG_ENABLE_MODULES = YES; 523 | CODE_SIGN_IDENTITY = "Apple Development"; 524 | CODE_SIGN_STYLE = Manual; 525 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 526 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 527 | DEVELOPMENT_TEAM = ""; 528 | ENABLE_BITCODE = NO; 529 | FRAMEWORK_SEARCH_PATHS = ( 530 | "$(inherited)", 531 | "$(PROJECT_DIR)/Flutter", 532 | ); 533 | INFOPLIST_FILE = Runner/Info.plist; 534 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 535 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 536 | LIBRARY_SEARCH_PATHS = ( 537 | "$(inherited)", 538 | "$(PROJECT_DIR)/Flutter", 539 | ); 540 | PRODUCT_BUNDLE_IDENTIFIER = com.zhw.test3; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | PROVISIONING_PROFILE_SPECIFIER = ""; 543 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 544 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 545 | SWIFT_VERSION = 5.0; 546 | VERSIONING_SYSTEM = "apple-generic"; 547 | }; 548 | name = Debug; 549 | }; 550 | 97C147071CF9000F007C117D /* Release */ = { 551 | isa = XCBuildConfiguration; 552 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 553 | buildSettings = { 554 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 555 | CLANG_ENABLE_MODULES = YES; 556 | CODE_SIGN_IDENTITY = "Apple Development"; 557 | CODE_SIGN_STYLE = Manual; 558 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 559 | DEVELOPMENT_TEAM = ""; 560 | ENABLE_BITCODE = NO; 561 | FRAMEWORK_SEARCH_PATHS = ( 562 | "$(inherited)", 563 | "$(PROJECT_DIR)/Flutter", 564 | ); 565 | INFOPLIST_FILE = Runner/Info.plist; 566 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 567 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 568 | LIBRARY_SEARCH_PATHS = ( 569 | "$(inherited)", 570 | "$(PROJECT_DIR)/Flutter", 571 | ); 572 | ONLY_ACTIVE_ARCH = YES; 573 | PRODUCT_BUNDLE_IDENTIFIER = com.zhw.test3; 574 | PRODUCT_NAME = "$(TARGET_NAME)"; 575 | PROVISIONING_PROFILE_SPECIFIER = ""; 576 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 577 | SWIFT_VERSION = 5.0; 578 | VERSIONING_SYSTEM = "apple-generic"; 579 | }; 580 | name = Release; 581 | }; 582 | /* End XCBuildConfiguration section */ 583 | 584 | /* Begin XCConfigurationList section */ 585 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | 97C147031CF9000F007C117D /* Debug */, 589 | 97C147041CF9000F007C117D /* Release */, 590 | 249021D3217E4FDB00AE95B9 /* Profile */, 591 | ); 592 | defaultConfigurationIsVisible = 0; 593 | defaultConfigurationName = Release; 594 | }; 595 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 596 | isa = XCConfigurationList; 597 | buildConfigurations = ( 598 | 97C147061CF9000F007C117D /* Debug */, 599 | 97C147071CF9000F007C117D /* Release */, 600 | 249021D4217E4FDB00AE95B9 /* Profile */, 601 | ); 602 | defaultConfigurationIsVisible = 0; 603 | defaultConfigurationName = Release; 604 | }; 605 | /* End XCConfigurationList section */ 606 | }; 607 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 608 | } 609 | -------------------------------------------------------------------------------- /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 | 37 | 38 | 39 | 40 | 41 | 42 | 53 | 55 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | 74 | 75 | 79 | 80 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcuserdata/beike.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/ios/Runner.xcworkspace/xcuserdata/beike.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcuserdata/beike.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 41 | 53 | 54 | 55 | 57 | 69 | 70 | 71 | 73 | 85 | 86 | 87 | 89 | 101 | 102 | 103 | 105 | 117 | 118 | 119 | 121 | 129 | 130 | 131 | 133 | 141 | 142 | 143 | 145 | 150 | 151 | 152 | 154 | 166 | 167 | 168 | 170 | 182 | 183 | 184 | 186 | 198 | 199 | 200 | 202 | 214 | 215 | 216 | 218 | 230 | 231 | 232 | 234 | 246 | 247 | 248 | 250 | 262 | 263 | 264 | 266 | 278 | 279 | 280 | 282 | 294 | 295 | 296 | 298 | 310 | 311 | 312 | 314 | 326 | 327 | 328 | 330 | 342 | 343 | 344 | 346 | 358 | 359 | 360 | 362 | 374 | 375 | 376 | 378 | 390 | 391 | 392 | 394 | 406 | 407 | 408 | 410 | 422 | 423 | 424 | 426 | 438 | 439 | 440 | 442 | 454 | 455 | 456 | 457 | 458 | -------------------------------------------------------------------------------- /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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/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/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GeneratedPluginRegistrant_h 8 | #define GeneratedPluginRegistrant_h 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface GeneratedPluginRegistrant : NSObject 15 | + (void)registerWithRegistry:(NSObject*)registry; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | #endif /* GeneratedPluginRegistrant_h */ 20 | -------------------------------------------------------------------------------- /example/ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #import "GeneratedPluginRegistrant.h" 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | @import bk_flutter_image; 13 | #endif 14 | 15 | @implementation GeneratedPluginRegistrant 16 | 17 | + (void)registerWithRegistry:(NSObject*)registry { 18 | [BkFlutterImagePlugin registerWithRegistrar:[registry registrarForPlugin:@"BkFlutterImagePlugin"]]; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /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 | bk_flutter_image_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/exts.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:bk_flutter_image/bk_flutter_image.dart'; 7 | 8 | enum GridDemoTileStyle { 9 | deleteMode, 10 | selectMode 11 | } 12 | 13 | typedef BannerTapCallback = void Function(Photo photo); 14 | 15 | const double _kMinFlingVelocity = 800.0; 16 | const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; 17 | 18 | class Photo { 19 | Photo({ 20 | this.url, 21 | this.assetPackage, 22 | this.title, 23 | this.caption, 24 | this.isFavorite = false, 25 | this.mode=BoxFit.fill, 26 | }); 27 | 28 | final String url; 29 | final String assetPackage; 30 | final String title; 31 | final String caption; 32 | final BoxFit mode; 33 | 34 | bool isFavorite; 35 | String get tag => url; // Assuming that all asset names are unique. 36 | 37 | bool get isValid => url != null && title != null && caption != null && isFavorite != null; 38 | } 39 | 40 | class GridPhotoViewer extends StatefulWidget { 41 | const GridPhotoViewer({ Key key, this.photo }) : super(key: key); 42 | 43 | final Photo photo; 44 | 45 | @override 46 | _GridPhotoViewerState createState() => _GridPhotoViewerState(); 47 | } 48 | 49 | class _GridTitleText extends StatelessWidget { 50 | const _GridTitleText(this.text); 51 | 52 | final String text; 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return FittedBox( 57 | fit: BoxFit.scaleDown, 58 | alignment: Alignment.centerLeft, 59 | child: Text(text), 60 | ); 61 | } 62 | } 63 | 64 | class _GridPhotoViewerState extends State with SingleTickerProviderStateMixin { 65 | AnimationController _controller; 66 | Animation _flingAnimation; 67 | Offset _offset = Offset.zero; 68 | double _scale = 1.0; 69 | Offset _normalizedOffset; 70 | double _previousScale; 71 | 72 | @override 73 | void initState() { 74 | super.initState(); 75 | _controller = AnimationController(vsync: this) 76 | ..addListener(_handleFlingAnimation); 77 | } 78 | 79 | @override 80 | void dispose() { 81 | _controller.dispose(); 82 | super.dispose(); 83 | } 84 | 85 | // The maximum offset value is 0,0. If the size of this renderer's box is w,h 86 | // then the minimum offset value is w - _scale * w, h - _scale * h. 87 | Offset _clampOffset(Offset offset) { 88 | final Size size = context.size; 89 | final Offset minOffset = Offset(size.width, size.height) * (1.0 - _scale); 90 | return Offset(offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0)); 91 | } 92 | 93 | void _handleFlingAnimation() { 94 | setState(() { 95 | _offset = _flingAnimation.value; 96 | }); 97 | } 98 | 99 | void _handleOnScaleStart(ScaleStartDetails details) { 100 | setState(() { 101 | _previousScale = _scale; 102 | _normalizedOffset = (details.focalPoint - _offset) / _scale; 103 | // The fling animation stops if an input gesture starts. 104 | _controller.stop(); 105 | }); 106 | } 107 | 108 | void _handleOnScaleUpdate(ScaleUpdateDetails details) { 109 | setState(() { 110 | _scale = (_previousScale * details.scale).clamp(1.0, 4.0); 111 | // Ensure that image location under the focal point stays in the same place despite scaling. 112 | _offset = _clampOffset(details.focalPoint - _normalizedOffset * _scale); 113 | }); 114 | } 115 | 116 | void _handleOnScaleEnd(ScaleEndDetails details) { 117 | final double magnitude = details.velocity.pixelsPerSecond.distance; 118 | if (magnitude < _kMinFlingVelocity) 119 | return; 120 | final Offset direction = details.velocity.pixelsPerSecond / magnitude; 121 | final double distance = (Offset.zero & context.size).shortestSide; 122 | _flingAnimation = _controller.drive(Tween( 123 | begin: _offset, 124 | end: _clampOffset(_offset + direction * distance), 125 | )); 126 | _controller 127 | ..value = 0.0 128 | ..fling(velocity: magnitude / 1000.0); 129 | } 130 | 131 | @override 132 | Widget build(BuildContext context) { 133 | return GestureDetector( 134 | onScaleStart: _handleOnScaleStart, 135 | onScaleUpdate: _handleOnScaleUpdate, 136 | onScaleEnd: _handleOnScaleEnd, 137 | child: ClipRect( 138 | child: Transform( 139 | transform: Matrix4.identity() 140 | ..translate(_offset.dx, _offset.dy) 141 | ..scale(_scale), 142 | child: 143 | // Image.network(widget.photo.url,), 144 | BkFlutterImage( 145 | url: widget.photo.url, 146 | width: 150, 147 | height: 150, 148 | //package: widget.photo.assetPackage, 149 | fit: BoxFit.contain, 150 | autoResize: false, 151 | ), 152 | ), 153 | ), 154 | ); 155 | } 156 | } 157 | 158 | class GridDemoPhotoItem extends StatelessWidget { 159 | GridDemoPhotoItem({ 160 | Key key, 161 | @required this.photo, 162 | @required this.tileStyle, 163 | @required this.onBannerTap, 164 | }) : assert(photo != null && photo.isValid), 165 | assert(tileStyle != null), 166 | assert(onBannerTap != null), 167 | super(key: key); 168 | 169 | final Photo photo; 170 | final GridDemoTileStyle tileStyle; 171 | final BannerTapCallback onBannerTap; // User taps on the photo's header or footer. 172 | 173 | void showPhoto(BuildContext context) { 174 | Navigator.push(context, MaterialPageRoute( 175 | builder: (BuildContext context) { 176 | return Scaffold( 177 | appBar: AppBar( 178 | title: Text(photo.title), 179 | ), 180 | body: SizedBox.expand( 181 | child: Hero( 182 | tag: photo.tag, 183 | child: GridPhotoViewer(photo: photo), 184 | ), 185 | ), 186 | ); 187 | } 188 | )); 189 | } 190 | 191 | @override 192 | Widget build(BuildContext context) { 193 | final Widget image = GestureDetector( 194 | onTap: () { showPhoto(context); }, 195 | child: BkFlutterImage( 196 | url: photo.url, 197 | fit: photo.mode, 198 | placeholder: 'images/01.jpg', 199 | autoResize: true, 200 | imageErrorBuilder: (BuildContext context,Object error,StackTrace stacktrace){ 201 | return Text(error); 202 | }, 203 | ), 204 | ); 205 | 206 | final IconData icon = photo.isFavorite ? Icons.star : Icons.star_border; 207 | 208 | switch (tileStyle) { 209 | case GridDemoTileStyle.deleteMode: 210 | return GridTile( 211 | header: GestureDetector( 212 | onTap: () { onBannerTap(photo); }, 213 | child: GridTileBar( 214 | title: _GridTitleText(photo.title), 215 | backgroundColor: Colors.black45, 216 | leading: Icon( 217 | Icons.delete_forever, 218 | color: Colors.white, 219 | ), 220 | ), 221 | ), 222 | child: image, 223 | ); 224 | 225 | case GridDemoTileStyle.selectMode: 226 | return GridTile( 227 | footer: GestureDetector( 228 | onTap: () { onBannerTap(photo); }, 229 | child: GridTileBar( 230 | backgroundColor: Colors.black45, 231 | title: _GridTitleText(photo.title), 232 | subtitle: _GridTitleText(photo.caption), 233 | trailing: Icon( 234 | icon, 235 | color: Colors.white, 236 | ), 237 | ), 238 | ), 239 | child: image, 240 | ); 241 | } 242 | assert(tileStyle != null); 243 | return null; 244 | } 245 | 246 | } 247 | 248 | class GridListDemo extends StatefulWidget { 249 | const GridListDemo({ Key key }) : super(key: key); 250 | 251 | @override 252 | GridListDemoState createState() => GridListDemoState(); 253 | 254 | } 255 | 256 | class GridListDemoState extends State { 257 | GridDemoTileStyle _tileStyle = GridDemoTileStyle.selectMode; 258 | 259 | @override 260 | void dispose() { 261 | super.dispose(); 262 | } 263 | 264 | @override 265 | @override 266 | void initState() { 267 | super.initState(); 268 | for(int i = 0; i < 10; i ++) { 269 | photos.addAll(allModephotos(i)); 270 | } 271 | } 272 | 273 | List allModephotos(int i){ 274 | List temp = [ 275 | Photo( 276 | url: photosUrls[i], 277 | assetPackage: _kGalleryAssetsPackage, 278 | title: 'BoxFit.fill', 279 | caption: 'Fisherman',mode: BoxFit.fill, 280 | ), 281 | Photo( 282 | url: photosUrls[i], 283 | assetPackage: _kGalleryAssetsPackage, 284 | title: 'BoxFit.cover', 285 | caption: 'Fisherman',mode: BoxFit.cover, 286 | ), 287 | Photo( 288 | url: photosUrls[i], 289 | assetPackage: _kGalleryAssetsPackage, 290 | title: 'BoxFit.contain', 291 | caption: 'Fisherman',mode: BoxFit.contain, 292 | ), 293 | Photo( 294 | url: photosUrls[i], 295 | assetPackage: _kGalleryAssetsPackage, 296 | title: 'BoxFit.fitWidth', 297 | caption: 'Fisherman',mode: BoxFit.fitWidth, 298 | ), 299 | Photo( 300 | url: photosUrls[i], 301 | assetPackage: _kGalleryAssetsPackage, 302 | title: 'BoxFit.fitHeight', 303 | caption: 'Fisherman', 304 | mode: BoxFit.fitHeight, 305 | ), 306 | Photo( 307 | url: photosUrls[i], 308 | assetPackage: _kGalleryAssetsPackage, 309 | title: 'BoxFit.scaleDown', 310 | caption: 'Fisherman', 311 | mode: BoxFit.scaleDown, 312 | ), 313 | ]; 314 | return temp; 315 | } 316 | 317 | List photos = [ 318 | ]; 319 | 320 | void changeTileStyle(GridDemoTileStyle value) { 321 | setState(() { 322 | _tileStyle = value; 323 | }); 324 | } 325 | 326 | @override 327 | Widget build(BuildContext context) { 328 | final Orientation orientation = MediaQuery.of(context).orientation; 329 | return Scaffold( 330 | appBar: AppBar( 331 | title: const Text('Grid list'), 332 | actions: [ 333 | //MaterialDemoDocumentationButton(GridListDemo.routeName), 334 | PopupMenuButton( 335 | onSelected: changeTileStyle, 336 | itemBuilder: (BuildContext context) => >[ 337 | const PopupMenuItem( 338 | value: GridDemoTileStyle.deleteMode, 339 | child: Text('Delete Mode'), 340 | ), 341 | const PopupMenuItem( 342 | value: GridDemoTileStyle.selectMode, 343 | child: Text('Select Mode'), 344 | ), 345 | ], 346 | ), 347 | ], 348 | ), 349 | body: 350 | Column( children: [ 351 | Expanded( child: SafeArea( 352 | top: false, 353 | bottom: false, 354 | child: GridView.count( 355 | crossAxisCount: (orientation == Orientation.portrait) ? 2 : 3, 356 | mainAxisSpacing: 4.0, 357 | crossAxisSpacing: 4.0, 358 | padding: const EdgeInsets.all(4.0), 359 | childAspectRatio: (orientation == Orientation.portrait) ? 1.0 : 1.3, 360 | children: photos.map((Photo photo) { 361 | return GridDemoPhotoItem( 362 | photo: photo, 363 | tileStyle: _tileStyle, 364 | onBannerTap: (Photo photo) { 365 | setState(() { 366 | if (GridDemoTileStyle.deleteMode == _tileStyle) { 367 | photos.remove(photo); 368 | } else { 369 | photo.isFavorite = !photo.isFavorite; 370 | } 371 | }); 372 | }, 373 | ); 374 | }).toList(), 375 | ), 376 | ), 377 | ), 378 | ], 379 | ), 380 | ); 381 | } 382 | } 383 | 384 | List photosUrls = [ 385 | 'https://images.pexels.com/photos/6383210/pexels-photo-6383210.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 386 | 'https://images.pexels.com/photos/1751279/pexels-photo-1751279.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 387 | 'https://images.pexels.com/photos/2434269/pexels-photo-2434269.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 388 | 'https://images.pexels.com/photos/7307618/pexels-photo-7307618.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 389 | 'https://images.pexels.com/photos/3932944/pexels-photo-3932944.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 390 | 'https://images.pexels.com/photos/4578770/pexels-photo-4578770.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 391 | 'https://images.pexels.com/photos/9060607/pexels-photo-9060607.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 392 | 'https://images.pexels.com/photos/1915715/pexels-photo-1915715.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 393 | 'https://images.pexels.com/photos/4683675/pexels-photo-4683675.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 394 | 'https://images.pexels.com/photos/9166412/pexels-photo-9166412.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 395 | 'https://images.pexels.com/photos/2681319/pexels-photo-2681319.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 396 | 'https://images.pexels.com/photos/6243804/pexels-photo-6243804.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500', 397 | 'https://image2.ljcdn.com/utopia-file/p1/215a16c18b2c6ad44a61ff077f12b64fd937398c-3840-2560!m_fit,w_2560,o_auto,f_jpg', 398 | 'https://image2.ljcdn.com/utopia-file/p1/215a16c18b2c6ad44a61ff077f12b64fd937398c-3840-2560!m_fit,w_2560,o_auto,f_jpg', 399 | 'https://image2.ljcdn.com/utopia-file/p1/d7884f6c4cf7e43e826018a8576e6bf53d07fe40-5424-3632!m_fit,w_2560,o_auto,f_jpg', 400 | 'https://image2.ljcdn.com/utopia-file/p1/e279f611cea722a7225ef581bace829d68dd3463-5328-4000!m_fit,w_2560,o_auto,f_jpg', 401 | 'https://image2.ljcdn.com/utopia-file/p1/8834c7631bfddee25391079b6951e1d6c10c40b8-2560-1920!m_fit,w_2560,o_auto,f_jpg', 402 | 'https://image2.ljcdn.com/utopia-file/p1/90aacb7b156c0670dd8fe203a6cf372d5b4bc1ca-5376-3314!m_fit,w_2560,o_auto,f_jpg', 403 | 'https://image2.ljcdn.com/utopia-file/p1/d57bad88338fcdba2405a02dda5415b73e7cbb4c-6480-4320!m_fit,w_2560,o_auto,f_jpg', 404 | 'https://image2.ljcdn.com/utopia-file/p1/b7e71ed8ec092e6b25ca4315d43b11aee85a76b7-2560-1707!m_fit,w_2560,o_auto,f_jpg', 405 | 'https://image2.ljcdn.com/utopia-file/p1/a2909d3b7d35165daf0cea9697df470e3e61d747-5973-4480!m_fit,w_2560,o_auto,f_jpg', 406 | 'https://image2.ljcdn.com/utopia-file/p1/d5202b01af7a978a9f4332208780265bf1ac3617-5760-3840!m_fit,w_2560,o_auto,f_jpg', 407 | 'https://image2.ljcdn.com/utopia-file/p1/51392b46baa507e7b97ad9fadbdb0bf6bcf0bc06-2667-2000!m_fit,w_2560,o_auto,f_jpg', 408 | 'https://image2.ljcdn.com/utopia-file/p1/861520ecf875fc750de9565fb01548e7e94fa95a-5994-3996!m_fit,w_2560,o_auto,f_jpg', 409 | 'https://image2.ljcdn.com/utopia-file/p1/3e1ebe4a9bb66b1322bd90e5b185b8e0d9661dc1-5472-3648!m_fit,w_2560,o_auto,f_jpg', 410 | 'https://image2.ljcdn.com/utopia-file/p1/5e1d544c33118451ebaca9aae349a9c704e5cb2c-8448-6336!m_fit,w_2560,o_auto,f_jpg', 411 | 'https://image2.ljcdn.com/utopia-file/p1/e7eeeb5b4a079a0e4f51c7c955da85464f2e93a5-2400-1800!m_fit,w_2560,o_auto,f_jpg', 412 | "https://image2.ljcdn.com/utopia-file/p1/215a16c18b2c6ad44a61ff077f12b64fd937398c-3840-2560!m_fit,w_2560,o_auto,f_jpg", 413 | "https://image2.ljcdn.com/utopia-file/p1/d7884f6c4cf7e43e826018a8576e6bf53d07fe40-5424-3632!m_fit,w_2560,o_auto,f_jpg", 414 | "https://image2.ljcdn.com/utopia-file/p1/e279f611cea722a7225ef581bace829d68dd3463-5328-4000!m_fit,w_2560,o_auto,f_jpg", 415 | "https://image2.ljcdn.com/utopia-file/p1/90aacb7b156c0670dd8fe203a6cf372d5b4bc1ca-5376-3314!m_fit,w_2560,o_auto,f_jpg", 416 | ]; -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:bk_flutter_image/bk_flutter_image.dart'; 3 | 4 | import 'exts.dart'; 5 | 6 | void main() => runApp(MyApp()); 7 | 8 | class MyApp extends StatefulWidget { 9 | @override 10 | _MyAppState createState() => _MyAppState(); 11 | } 12 | 13 | class _MyAppState extends State { 14 | @override 15 | void initState() { 16 | super.initState(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp( 22 | home: MainPage(), 23 | ); 24 | } 25 | 26 | } 27 | 28 | class MainPage extends StatefulWidget { 29 | @override 30 | _MainPageState createState() => _MainPageState(); 31 | } 32 | 33 | class _MainPageState extends State { 34 | 35 | double width = 300; 36 | double height = 300; 37 | BoxFit fit = BoxFit.cover; 38 | bool useFlutterImage = true; 39 | String imageUrl ='https://image2.ljcdn.com/utopia-file/p1/215a16c18b2c6ad44a61ff077f12b64fd937398c-3840-2560!m_fit,w_2560,o_auto,f_jpg'; 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Scaffold( 44 | appBar: AppBar(title:Text('flutter 使用原生image view'),actions: [ 45 | IconButton(icon: Icon(Icons.list), onPressed: (){ 46 | Navigator.push(context, DialogRoute(context: context, builder: (c) { 47 | return GridListDemo(); 48 | })); 49 | // Navigator.push(context, MaterialPageRoute(builder: (c) { 50 | // return GridListDemo(); 51 | // })); 52 | }) 53 | ],), 54 | body: Stack( 55 | children: [ 56 | SafeArea( 57 | child: Column( 58 | children: [ 59 | Expanded( 60 | flex: 1, 61 | child: Container(), 62 | ), 63 | Text('flutter这边的图片container参数设置'), 64 | Row( 65 | children: [ 66 | Text('flutter上的宽度'), 67 | Slider( 68 | value: width, 69 | min: 100, 70 | max: 500, 71 | onChanged: (value) { 72 | setState(() { 73 | width = value.roundToDouble(); 74 | }); 75 | }), 76 | ], 77 | ), 78 | Row( 79 | children: [ 80 | Text('flutter上的高度'), 81 | Slider( 82 | value: height, 83 | min: 100, 84 | max: 500, 85 | onChanged: (value) { 86 | setState(() { 87 | height = value.roundToDouble(); 88 | }); 89 | }), 90 | ], 91 | ), 92 | Row( 93 | children: [ 94 | Text('使用Texture加载图片: $useFlutterImage'), 95 | Checkbox( 96 | value: useFlutterImage, 97 | onChanged: (value) { 98 | setState(() { 99 | useFlutterImage = value; 100 | }); 101 | }, 102 | ), 103 | Text('Cover:'), 104 | Checkbox( 105 | value: fit == BoxFit.cover, 106 | onChanged: (value) { 107 | setState(() { 108 | fit = value ? BoxFit.cover : BoxFit.none; 109 | }); 110 | }, 111 | ), 112 | Text('Contain:'), 113 | Checkbox( 114 | value: fit == BoxFit.contain, 115 | onChanged: (value) { 116 | setState(() { 117 | fit = value ? BoxFit.contain : BoxFit.none; 118 | }); 119 | }, 120 | ), 121 | Text('fill:'), 122 | Checkbox( 123 | value: fit == BoxFit.fill, 124 | onChanged: (value) { 125 | setState(() { 126 | fit = value ? BoxFit.fill : BoxFit.none; 127 | }); 128 | }, 129 | ), 130 | Text('fitWidth:'), 131 | Checkbox( 132 | value: fit == BoxFit.fitWidth, 133 | onChanged: (value) { 134 | setState(() { 135 | fit = value ? BoxFit.fitWidth : BoxFit.none; 136 | }); 137 | }, 138 | ), 139 | Text('fitHeight:'), 140 | Checkbox( 141 | value: fit == BoxFit.fitHeight, 142 | onChanged: (value) { 143 | setState(() { 144 | fit = value ? BoxFit.fitHeight : BoxFit.none; 145 | }); 146 | }, 147 | ), 148 | Text('scale:'), 149 | Checkbox( 150 | value: fit == BoxFit.scaleDown, 151 | onChanged: (value) { 152 | setState(() { 153 | fit = value ? BoxFit.scaleDown : BoxFit.none; 154 | }); 155 | }, 156 | ), 157 | ], 158 | ), 159 | ], 160 | ), 161 | ), 162 | Center( 163 | child: SafeArea( 164 | child:Column( 165 | children:[ 166 | Container(// width: 400, 167 | // height: 100, 168 | child: useFlutterImage 169 | ? BkFlutterImage( 170 | url: imageUrl, 171 | height: height, 172 | width: width, 173 | fit: fit, 174 | ) 175 | : Image.network( 176 | imageUrl, 177 | fit: fit, 178 | width: width, 179 | height: height, 180 | ), 181 | 182 | ), ]), 183 | 184 | ), 185 | ) 186 | ], 187 | ), 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: bk_flutter_image_example 2 | description: Demonstrates how to use the bk_flutter_image plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | bk_flutter_image: 21 | path: ../ 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | 29 | # The following line ensures that the Material Icons font is 30 | # included with your application, so that you can use the icons in 31 | # the material Icons class. 32 | uses-material-design: true 33 | 34 | # To add assets to your application, add an assets section, like this: 35 | assets: 36 | - images/01.jpg 37 | # An image asset can refer to one or more resolution-specific "variants", see 38 | # https://flutter.dev/assets-and-images/#resolution-aware. 39 | 40 | # For details regarding adding assets from package dependencies, see 41 | # https://flutter.dev/assets-and-images/#from-packages 42 | 43 | # To add custom fonts to your application, add a fonts section here, 44 | # in this "flutter" section. Each entry in this list should have a 45 | # "family" key with the font family name, and a "fonts" key with a 46 | # list giving the asset and other descriptors for the font. For 47 | # example: 48 | # fonts: 49 | # - family: Schyler 50 | # fonts: 51 | # - asset: fonts/Schyler-Regular.ttf 52 | # - asset: fonts/Schyler-Italic.ttf 53 | # style: italic 54 | # - family: Trajan Pro 55 | # fonts: 56 | # - asset: fonts/TrajanPro.ttf 57 | # - asset: fonts/TrajanPro_Bold.ttf 58 | # weight: 700 59 | # 60 | # For details regarding fonts from package dependencies, 61 | # see https://flutter.dev/custom-fonts/#from-packages 62 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:bk_flutter_image_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianjiaTech/bk_flutter_image/752408f944aad295668fec068658a7be8ecf8dd0/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Assets/glsl.fsh: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D Texture; 4 | varying vec2 TextureCoordsVarying; 5 | 6 | void main (void) { 7 | vec4 mask = texture2D(Texture, TextureCoordsVarying); 8 | gl_FragColor = vec4(mask.rgb, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /ios/Assets/glsl.vsh: -------------------------------------------------------------------------------- 1 | attribute vec4 Position; 2 | attribute vec2 TextureCoords; 3 | varying vec2 TextureCoordsVarying; 4 | 5 | void main (void) { 6 | gl_Position = Position; 7 | TextureCoordsVarying = TextureCoords; 8 | } 9 | -------------------------------------------------------------------------------- /ios/Classes/BkFlutterImagePlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface BkFlutterImagePlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/BkFlutterImagePlugin.m: -------------------------------------------------------------------------------- 1 | #import "BkFlutterImagePlugin.h" 2 | #import "BkImageRenderWorker.h" 3 | #import "BkFlutterPixelBufferCache.h" 4 | 5 | 6 | @interface BkFlutterImagePlugin () 7 | 8 | @property (nonatomic, strong) NSMutableDictionary *workers; 9 | @property (nonatomic, weak) NSObject *textures; 10 | 11 | @end 12 | 13 | @implementation BkFlutterImagePlugin 14 | 15 | + (void)registerWithRegistrar:(NSObject*)registrar{ 16 | FlutterMethodChannel *channel = [FlutterMethodChannel 17 | methodChannelWithName:@"bk_flutter_image" 18 | binaryMessenger:[registrar messenger]]; 19 | BkFlutterImagePlugin *instance = [[BkFlutterImagePlugin alloc] initWithTextures:[registrar textures]]; 20 | [registrar addMethodCallDelegate:instance channel:channel]; 21 | } 22 | 23 | - (instancetype)initWithTextures:(NSObject *)textures { 24 | self = [super init]; 25 | if (self) { 26 | _workers = [[NSMutableDictionary alloc] init]; 27 | _textures = textures; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { 33 | if ([@"create" isEqualToString:call.method]) { 34 | NSInteger __block textureId = -1; 35 | id __weak registry = self.textures; 36 | BkImageRenderWorker *worker = [[BkImageRenderWorker alloc] initWithParams:[self convertToNoNullDic:call.arguments] onNewFrame:^(NSError * error, CGSize size, BOOL isFullPixel) { 37 | NSMutableDictionary *textureInfo = [NSMutableDictionary dictionary]; 38 | [textureInfo setValue:error.userInfo[NSLocalizedDescriptionKey] ?: @"" forKey:@"error"]; 39 | [textureInfo setValue:@(error ? -1 : textureId) forKey:@"textureId"]; 40 | [textureInfo setValue:@(size.width) forKey:@"textureWidth"]; 41 | [textureInfo setValue:@(size.height) forKey:@"textureHeight"]; 42 | [textureInfo setValue:@(isFullPixel) forKey:@"isFullPixel"]; 43 | result([[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:textureInfo options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding]); 44 | [registry textureFrameAvailable:textureId]; 45 | }]; 46 | textureId = (NSInteger) [self.textures registerTexture:worker]; 47 | [worker startWork]; 48 | } else if ([@"dispose" isEqualToString:call.method]) { 49 | NSNumber *textureId = call.arguments[@"textureId"]; 50 | if (![textureId isKindOfClass:[NSNull class]] && textureId.integerValue != -1) { 51 | [self.textures unregisterTexture:[textureId integerValue]]; 52 | } 53 | result(nil); 54 | } else if ([@"setCacheSize" isEqualToString:call.method]) { 55 | NSDictionary *cacheData = [self convertToNoNullDic:call.arguments]; 56 | [[BkFlutterPixelBufferCache sharedInstance] setDiskCacheMaxSize:[cacheData[@"diskMaxSize"] unsignedIntValue]]; 57 | [[BkFlutterPixelBufferCache sharedInstance] setMemoryCacheMaxSize:[cacheData[@"memoryMaxSize"] unsignedIntValue]]; 58 | result(nil); 59 | } else { 60 | result(FlutterMethodNotImplemented); 61 | } 62 | } 63 | 64 | - (NSString *)stringFromDictionary:(NSDictionary *)dict { 65 | 66 | NSString *jsonString = nil; 67 | if ([NSJSONSerialization isValidJSONObject:dict]) { 68 | NSError *error; 69 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error]; 70 | jsonString =[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 71 | if (error) { 72 | //return [self errorString:error]; 73 | } 74 | } 75 | return jsonString; 76 | } 77 | 78 | - (NSDictionary *)convertToNoNullDic:(NSDictionary *)dic { 79 | NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithCapacity:dic.count]; 80 | for (id key in dic.allKeys) { 81 | if (![dic[key] isEqual:[NSNull null]]){ 82 | mDic[key] = dic[key]; 83 | } 84 | } 85 | return mDic; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /ios/Classes/BkFlutterPixelBufferCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // BkFlutterPixelBufferCache.h 3 | // bk_flutter_image 4 | // 5 | // Created by zhao hongwei on 2022/5/13. 6 | // 7 | 8 | #import 9 | #import "SDMemoryCache.h" 10 | #import "SDImageCacheConfig.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface BkFlutterPixelBufferRef : NSObject 15 | 16 | @property (nonatomic, unsafe_unretained) CVPixelBufferRef ref; 17 | 18 | + (BkFlutterPixelBufferRef *)pixelBufferRefFromData:(NSData *)data size:(CGSize)size; 19 | + (BkFlutterPixelBufferRef *)pixelBufferRefWithImage:(UIImage *)sImage; 20 | 21 | + (NSData *)dataFromPixelBufferRef:(BkFlutterPixelBufferRef *)pixelBufferRef; 22 | 23 | - (id)initWithRef:(CVPixelBufferRef)ref; 24 | 25 | @end 26 | 27 | @interface BkFlutterPixelBufferCache : NSObject 28 | 29 | + (instancetype)sharedInstance; 30 | 31 | - (void)loadImageAndPixelBuffer:(NSString *)url size:(CGSize)size completion:(void (^)(BkFlutterPixelBufferRef *buffer, NSError* error, BOOL isFullPixel))completion; 32 | 33 | - (void)setMemoryCacheMaxSize:(NSUInteger)maxSize; 34 | 35 | - (void)setDiskCacheMaxSize:(NSUInteger)maxSize; 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /ios/Classes/BkFlutterPixelBufferCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // BkFlutterPixelBufferCache.m 3 | // bk_flutter_image 4 | // 5 | // Created by zhao hongwei on 2022/5/13. 6 | // 7 | 8 | #import "BkFlutterPixelBufferCache.h" 9 | 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | 17 | API_AVAILABLE(ios(10.0)) 18 | @interface BkFlutterPixelBufferCache () 19 | 20 | @property (nonatomic, strong) SDMemoryCache *cache; 21 | @property (nonatomic, strong) SDDiskCache *diskCache; 22 | @property (nonatomic, strong) dispatch_queue_t queue; 23 | 24 | @end 25 | 26 | @implementation BkFlutterPixelBufferCache 27 | 28 | #pragma init 29 | + (BkFlutterPixelBufferCache *)sharedInstance { 30 | static BkFlutterPixelBufferCache *_sharedInstance = nil; 31 | static dispatch_once_t oncePredicate; 32 | dispatch_once(&oncePredicate, ^{ 33 | _sharedInstance = [[self alloc] init]; 34 | }); 35 | return _sharedInstance; 36 | } 37 | 38 | 39 | + (void)swizzlingOriginClass:(Class)originClass originMethodName:(NSString *)originMethodName 40 | currentClass:(Class)currentClass currentMethodName:(NSString *)currentMethodName 41 | { 42 | SEL originSelector = NSSelectorFromString(originMethodName); 43 | SEL currentSelector = NSSelectorFromString(currentMethodName); 44 | Method originMethod = class_getInstanceMethod(originClass, originSelector); 45 | Method currentMethod = class_getInstanceMethod(currentClass, currentSelector); 46 | BOOL isSwizzed = class_addMethod(originClass, originSelector, method_getImplementation(currentMethod), method_getTypeEncoding(currentMethod)); 47 | if (isSwizzed) { 48 | class_replaceMethod(originClass, currentSelector,method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); 49 | } else { 50 | method_exchangeImplementations(originMethod, currentMethod); 51 | } 52 | } 53 | 54 | - (id)init { 55 | if (self = [super init]) { 56 | SDImageCacheConfig *config = [SDImageCacheConfig new]; 57 | self.diskCache = [[SDDiskCache alloc] initWithCachePath: [[SDImageCache defaultDiskCacheDirectory] stringByAppendingPathComponent:@"bkFlutterImage"] config:config]; 58 | self.cache = [[SDMemoryCache alloc] initWithConfig:config]; 59 | self.queue = dispatch_queue_create("com.BK.flutterImage", DISPATCH_QUEUE_SERIAL); 60 | } 61 | return self; 62 | } 63 | 64 | - (void)setMemoryCacheMaxSize:(NSUInteger)maxSize { 65 | self.cache.config.maxMemoryCost = maxSize * 1024 * 1024; 66 | } 67 | 68 | - (void)setDiskCacheMaxSize:(NSUInteger)maxSize { 69 | self.cache.config.maxDiskSize = maxSize * 1024 * 1024; 70 | } 71 | 72 | #pragma load 73 | - (void)loadImageAndPixelBuffer:(NSString *)url size:(CGSize)size completion:(void (^)(BkFlutterPixelBufferRef *buffer, NSError* error, BOOL isFullPixel))completion { 74 | 75 | __weak typeof(self) weakSelf = self; 76 | dispatch_async(self.queue, ^{ 77 | BOOL isFullPixel; 78 | __strong typeof(weakSelf) strongSelf = weakSelf; 79 | BkFlutterPixelBufferRef *bufferRef = [strongSelf pixelBufferRefWithUrl:url size:size isFullPixelImage:&isFullPixel]; 80 | if (!bufferRef) { 81 | NSDictionary *context = CGSizeEqualToSize(size, CGSizeMake(-1, -1)) ? nil : @{SDWebImageContextImageThumbnailPixelSize:@(size)}; 82 | 83 | //先检查缓存中的Image 84 | UIImage *memoryImage = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:url]; 85 | NSData *data; 86 | BOOL isFullPixel; 87 | if (memoryImage) { 88 | data = [[SDImageCache sharedImageCache] diskImageDataForKey:url]; 89 | isFullPixel = [strongSelf isFullPixelImage:memoryImage data:data]; 90 | if (isFullPixel || [strongSelf sizeMatch:memoryImage.size toSize:size]) { 91 | [strongSelf handleResult:nil image:memoryImage url:url isFullPixel:isFullPixel error:nil completion:completion]; 92 | return; 93 | } 94 | } 95 | 96 | //检查diskImage 97 | UIImage *diskImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:url options:0 context:context]; 98 | if (diskImage) { 99 | if (context){ 100 | data = data ?: [[SDImageCache sharedImageCache] diskImageDataForKey:url]; 101 | isFullPixel = [strongSelf isFullPixelImage:diskImage data:data]; 102 | } else { 103 | isFullPixel = YES; 104 | } 105 | [strongSelf handleResult:nil image:diskImage url:url isFullPixel:isFullPixel error:nil completion:completion]; 106 | return; 107 | } 108 | 109 | __weak typeof(self) weakSelf = strongSelf; 110 | [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[[NSURL alloc] initWithString:url] options:SDWebImageDownloaderScaleDownLargeImages | SDWebImageDownloaderUseNSURLCache | SDWebImageDownloaderAvoidDecodeImage context:context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { 111 | dispatch_async(strongSelf.queue, ^{ 112 | __strong typeof(weakSelf) strongSelf = weakSelf; 113 | if (!error && image) { 114 | [strongSelf handleResult:nil image:image url:url isFullPixel:[strongSelf isFullPixelImage:image data:data] error:error completion:completion]; 115 | [[SDImageCache sharedImageCache] storeImageDataToDisk:data forKey:url]; 116 | } else { 117 | [strongSelf handleResult:nil image:nil url:url isFullPixel:NO error:error completion:completion]; 118 | } 119 | }); 120 | }]; 121 | } else if (completion) { 122 | [strongSelf handleResult:bufferRef image:nil url:url isFullPixel:isFullPixel error:nil completion:completion]; 123 | } 124 | }); 125 | } 126 | 127 | 128 | - (void)handleResult:(BkFlutterPixelBufferRef *)bufferRef image:(UIImage *)image url:(NSString *)url isFullPixel:(BOOL)isFullPixel error:(NSError *)error completion:(void (^)( BkFlutterPixelBufferRef *buffer, NSError* error, BOOL isFullPixel))completion { 129 | bufferRef = bufferRef ?: [BkFlutterPixelBufferRef pixelBufferRefWithImage:image]; 130 | [self storePixelBufferRef:bufferRef url:url isFullPixelImage:isFullPixel]; 131 | if (completion) 132 | completion(bufferRef, error, isFullPixel); 133 | } 134 | 135 | #pragma memory op 136 | 137 | - (BkFlutterPixelBufferRef *)pixelBufferRefWithUrl:(NSString *)url size:(CGSize)size isFullPixelImage:(BOOL *)isFullPixelImage{ 138 | //get memory 139 | NSString *fullSizeCacheKey = [url stringByAppendingString:@"&BkFlutterFullSize"]; 140 | BkFlutterPixelBufferRef *fullRef = [self.cache objectForKey:fullSizeCacheKey]; 141 | BkFlutterPixelBufferRef *thumbRef = [self.cache objectForKey:url]; 142 | if (fullRef || thumbRef) { //check memory 143 | CGSize pixelSize = fullRef ? CGSizeMake(INT_MAX, INT_MAX) : CGSizeMake(CVPixelBufferGetWidth(thumbRef.ref), CVPixelBufferGetHeight(thumbRef.ref)); 144 | 145 | if ([self sizeMatch:pixelSize toSize:size]) { 146 | CGSize logSize = fullRef ? CGSizeMake(CVPixelBufferGetWidth(fullRef.ref), CVPixelBufferGetHeight(fullRef.ref)) : CGSizeMake(CVPixelBufferGetWidth(thumbRef.ref), CVPixelBufferGetHeight(thumbRef.ref)); 147 | *isFullPixelImage = fullRef != nil; 148 | return fullRef ?:thumbRef; 149 | } 150 | } else { //check disk 151 | BkFlutterPixelBufferRef *diskRef = [self diskPixelBufferRefWithUrl:url size:size isFullPixelImage:isFullPixelImage]; 152 | //store in menory cache 153 | [self storePixelBufferRef:diskRef url:url isFullPixelImage:*isFullPixelImage]; 154 | return diskRef; 155 | } 156 | return nil; 157 | } 158 | 159 | - (void)storePixelBufferRef:(BkFlutterPixelBufferRef *)buffer url:(NSString *)url isFullPixelImage:(BOOL)isFullPixelImage { 160 | if (buffer == nil) 161 | return; 162 | CGSize targetSize = CGSizeMake(CVPixelBufferGetWidth(buffer.ref), CVPixelBufferGetHeight(buffer.ref)); 163 | 164 | NSString *fullSizeCacheKey = [url stringByAppendingString:@"&BkFlutterFullSize"]; 165 | NSString *targetCacheKey = isFullPixelImage ? fullSizeCacheKey : url; 166 | 167 | BkFlutterPixelBufferRef *fullRef = [self.cache objectForKey:fullSizeCacheKey]; 168 | BkFlutterPixelBufferRef *thumbRef = [self.cache objectForKey:targetCacheKey]; 169 | 170 | if (fullRef == buffer || thumbRef == buffer) 171 | return; 172 | 173 | if (!fullRef) { 174 | if (isFullPixelImage || !thumbRef) { 175 | [self.cache setObject:buffer forKey:targetCacheKey cost:targetSize.width * targetSize.height * 4]; 176 | [self storeDiskPixelBufferRef:buffer url:url isFullPixelImage:isFullPixelImage]; 177 | } else { 178 | CGSize thumbSize = CGSizeMake(CVPixelBufferGetWidth(thumbRef.ref), CVPixelBufferGetHeight(thumbRef.ref)); 179 | if ([self sizeMatch:targetSize toSize:thumbSize]) { 180 | [self.cache setObject:buffer forKey:targetCacheKey cost:targetSize.width * targetSize.height * 4]; 181 | [self storeDiskPixelBufferRef:buffer url:url isFullPixelImage:isFullPixelImage]; 182 | } 183 | } 184 | } 185 | } 186 | 187 | 188 | #pragma disk op 189 | - (BkFlutterPixelBufferRef *)diskPixelBufferRefWithUrl:(NSString *)url size:(CGSize)size isFullPixelImage:(BOOL *)isFullPixelImage { 190 | 191 | BkFlutterPixelBufferRef *ref = nil; 192 | BOOL tempIsFullPixelImage = NO; 193 | if ([self.diskCache containsDataForKey:url]) { 194 | NSData *data = [self.diskCache extendedDataForKey:url]; 195 | NSError *error = nil; 196 | NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 197 | NSDictionary *sizeDic = [unarchiver decodeObjectForKey:@"sizeInfo"]; 198 | if (!error && sizeDic && sizeDic[@"PixelBufferSize"]) { 199 | CGSize pixelSize = [sizeDic[@"IsFullPixelImage"] boolValue] ? CGSizeMake(INT_MAX, INT_MAX) : [sizeDic[@"PixelBufferSize"] CGSizeValue]; 200 | if ([self sizeMatch:pixelSize toSize:size]) { 201 | ref = [BkFlutterPixelBufferRef pixelBufferRefFromData:[self.diskCache dataForKey:url] size:[sizeDic[@"PixelBufferSize"] CGSizeValue]]; 202 | tempIsFullPixelImage = [sizeDic[@"IsFullPixelImage"] boolValue]; 203 | } 204 | } 205 | } 206 | 207 | *isFullPixelImage = tempIsFullPixelImage; 208 | return ref; 209 | } 210 | 211 | - (void)storeDiskPixelBufferRef:(BkFlutterPixelBufferRef *)buffer url:(NSString *)url isFullPixelImage:(BOOL)isFullPixelImage { 212 | dispatch_async(self.queue, ^{ 213 | if (![self.diskCache containsDataForKey:url]) { 214 | //sizekey 215 | CGSize targetSize = CGSizeMake(CVPixelBufferGetWidth(buffer.ref), CVPixelBufferGetHeight(buffer.ref)); 216 | NSMutableData *data = [[NSMutableData alloc]init]; 217 | NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data]; 218 | [archiver encodeObject:@{@"PixelBufferSize":@(targetSize),@"IsFullPixelImage":@(isFullPixelImage)} 219 | forKey:@"sizeInfo"]; 220 | [archiver finishEncoding]; 221 | 222 | [self.diskCache setData:[BkFlutterPixelBufferRef dataFromPixelBufferRef:buffer] forKey:url]; 223 | [self.diskCache setExtendedData:data forKey:url]; 224 | } 225 | }); 226 | } 227 | 228 | #pragma tool 229 | - (BOOL)sizeMatch:(CGSize)size toSize:(CGSize)toSize { 230 | //前置处理 231 | size = [self convertSize:size]; 232 | toSize = [self convertSize:toSize]; 233 | 234 | CGFloat pixelRatio = size.width / size.height; 235 | CGFloat thumbnailRatio = toSize.width / toSize.height; 236 | 237 | if ((pixelRatio >= thumbnailRatio && size.width >= toSize.width) 238 | || (pixelRatio < thumbnailRatio && size.height >= toSize.height)) { 239 | return YES; 240 | } 241 | return NO; 242 | } 243 | 244 | - (CGSize)convertSize:(CGSize)size { 245 | CGFloat width = size.width; 246 | CGFloat height = size.height; 247 | 248 | if (width < 0 || height < 0) { 249 | width = INT_MAX; 250 | height = INT_MAX; 251 | } 252 | 253 | width = width == 0 ? 1 : width; 254 | height = height == 0 ? 1 : height; 255 | return CGSizeMake(width, height); 256 | } 257 | 258 | - (BOOL)isFullPixelImage:(UIImage *)image data:(NSData *)imageData { 259 | CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL); 260 | 261 | NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL); 262 | double pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue]; 263 | double pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue]; 264 | CFRelease(source); 265 | return CGSizeEqualToSize(image.size, CGSizeMake(pixelWidth, pixelHeight)); 266 | } 267 | 268 | //fix SDWebImage Bug 269 | + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options { 270 | // Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :) 271 | // Parse the image properties 272 | NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options); 273 | double pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue]; 274 | double pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue]; 275 | CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue]; 276 | if (!exifOrientation) { 277 | exifOrientation = kCGImagePropertyOrientationUp; 278 | } 279 | 280 | CFStringRef uttype = CGImageSourceGetType(source); 281 | // Check vector format 282 | BOOL isVector = NO; 283 | if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) { 284 | isVector = YES; 285 | } 286 | 287 | NSMutableDictionary *decodingOptions; 288 | if (options) { 289 | decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options]; 290 | } else { 291 | decodingOptions = [NSMutableDictionary dictionary]; 292 | } 293 | CGImageRef imageRef; 294 | BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height); 295 | if (createFullImage) { 296 | if (isVector) { 297 | if (thumbnailSize.width == 0 || thumbnailSize.height == 0) { 298 | // Provide the default pixel count for vector images, simply just use the screen size 299 | #if SD_WATCH 300 | thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size; 301 | #elif SD_UIKIT 302 | thumbnailSize = UIScreen.mainScreen.bounds.size; 303 | #elif SD_MAC 304 | thumbnailSize = NSScreen.mainScreen.frame.size; 305 | #endif 306 | } 307 | CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); 308 | NSUInteger DPIPerPixel = 2; 309 | NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel; 310 | decodingOptions[@"kCGImageSourceRasterizationDPI"] = @(rasterizationDPI); 311 | } 312 | imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]); 313 | } else { 314 | decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio); 315 | CGFloat maxPixelSize; 316 | if (preserveAspectRatio) { 317 | CGFloat pixelRatio = pixelWidth / pixelHeight; 318 | CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height; 319 | if (pixelRatio > thumbnailRatio) { 320 | maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.width / pixelRatio); 321 | } else { 322 | maxPixelSize = MAX(thumbnailSize.height * pixelRatio, thumbnailSize.height); 323 | } 324 | } else { 325 | maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); 326 | } 327 | decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize); 328 | decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES); 329 | imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]); 330 | } 331 | if (!imageRef) { 332 | return nil; 333 | } 334 | // Thumbnail image post-process 335 | if (!createFullImage) { 336 | if (preserveAspectRatio) { 337 | // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice 338 | exifOrientation = kCGImagePropertyOrientationUp; 339 | } else { 340 | // `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size 341 | CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize]; 342 | CGImageRelease(imageRef); 343 | imageRef = scaledImageRef; 344 | } 345 | } 346 | 347 | #if SD_UIKIT || SD_WATCH 348 | UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation]; 349 | UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation]; 350 | #else 351 | UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation]; 352 | #endif 353 | CGImageRelease(imageRef); 354 | return image; 355 | } 356 | 357 | 358 | @end 359 | 360 | @implementation BkFlutterPixelBufferRef 361 | 362 | + (BkFlutterPixelBufferRef *)pixelBufferRefFromData:(NSData *)data size:(CGSize)size { 363 | NSDictionary *pixelAttributes = @{(NSString*)kCVPixelBufferIOSurfacePropertiesKey:@{}}; 364 | 365 | CVPixelBufferRef pixelBuffer = NULL; 366 | 367 | CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault, 368 | size.width, 369 | size.height, 370 | kCVPixelFormatType_32BGRA, 371 | (__bridge CFDictionaryRef)(pixelAttributes), 372 | &pixelBuffer);//kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, 373 | 374 | CVPixelBufferLockBaseAddress(pixelBuffer,0); 375 | unsigned char *plane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); 376 | unsigned char *bytes = (unsigned char *)data.bytes; 377 | memcpy(plane, bytes, size.width * size.height * 4); 378 | 379 | if (result != kCVReturnSuccess) { 380 | return nil; 381 | } 382 | BkFlutterPixelBufferRef *buffer = [[BkFlutterPixelBufferRef alloc] initWithRef:pixelBuffer]; 383 | CVPixelBufferRelease(pixelBuffer); 384 | return buffer; 385 | } 386 | 387 | + (NSData *)dataFromPixelBufferRef:(BkFlutterPixelBufferRef *)pixelBufferRef { 388 | CVPixelBufferLockBaseAddress(pixelBufferRef.ref, 0); 389 | unsigned char* address = (unsigned char*)CVPixelBufferGetBaseAddressOfPlane(pixelBufferRef.ref, 0); 390 | 391 | int32_t width = (int32_t)CVPixelBufferGetWidth(pixelBufferRef.ref); 392 | int32_t height = (int32_t)CVPixelBufferGetHeight(pixelBufferRef.ref); 393 | 394 | NSMutableData* data = [NSMutableData dataWithBytes:address length:height * width * 4]; 395 | CVPixelBufferUnlockBaseAddress(pixelBufferRef.ref, 0); 396 | return data; 397 | } 398 | 399 | + (BkFlutterPixelBufferRef *)pixelBufferRefWithImage:(UIImage *)sImage { 400 | if (sImage) { 401 | CGImageRef image = [sImage CGImage]; 402 | GLuint width = (GLuint)CGImageGetWidth(image); 403 | GLuint height = (GLuint)CGImageGetHeight(image); 404 | CGSize size = CGSizeMake(width, height); 405 | BOOL hasAlpha = bkCGImageRefContainsAlpha(image); 406 | 407 | CFDictionaryRef empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 408 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 409 | @(YES), kCVPixelBufferCGImageCompatibilityKey, 410 | @(YES), kCVPixelBufferCGBitmapContextCompatibilityKey, 411 | empty, kCVPixelBufferIOSurfacePropertiesKey, nil]; 412 | 413 | CVPixelBufferRef pixelBuffer = NULL; 414 | CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef) options, &pixelBuffer); 415 | NSParameterAssert(status == kCVReturnSuccess && pixelBuffer != NULL); 416 | if (status != kCVReturnSuccess) 417 | return nil; 418 | 419 | CVPixelBufferLockBaseAddress(pixelBuffer, 0); 420 | void *pxdata = CVPixelBufferGetBaseAddress(pixelBuffer); 421 | NSParameterAssert(pxdata != NULL); 422 | 423 | CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); 424 | uint32_t bitmapInfo = bkBitmapInfoWithPixelFormatType(kCVPixelFormatType_32BGRA, (bool)hasAlpha); 425 | CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, CVPixelBufferGetBytesPerRow(pixelBuffer), rgbColorSpace, bitmapInfo); 426 | 427 | NSParameterAssert(context); 428 | CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); 429 | CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 430 | CFRelease(empty); 431 | CGColorSpaceRelease(rgbColorSpace); 432 | CGContextRelease(context); 433 | 434 | BkFlutterPixelBufferRef *buffer = [[BkFlutterPixelBufferRef alloc] initWithRef:pixelBuffer]; 435 | CVPixelBufferRelease(pixelBuffer); 436 | return buffer; 437 | } else { 438 | return nil; 439 | } 440 | } 441 | 442 | - (id)initWithRef:(CVPixelBufferRef)ref { 443 | if (self == [super init]) { 444 | self.ref = ref; 445 | CVPixelBufferRetain(ref); 446 | 447 | } 448 | return self; 449 | } 450 | 451 | - (void)dealloc { 452 | CVPixelBufferRelease(self.ref); 453 | } 454 | 455 | 456 | static uint32_t bkBitmapInfoWithPixelFormatType(OSType inputPixelFormat, bool hasAlpha) { 457 | if (inputPixelFormat == kCVPixelFormatType_32BGRA) { 458 | uint32_t bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; 459 | if (!hasAlpha) 460 | bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; 461 | return bitmapInfo; 462 | } else if (inputPixelFormat == kCVPixelFormatType_32ARGB) { 463 | return kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big; 464 | } else 465 | return 0; 466 | } 467 | 468 | static BOOL bkCGImageRefContainsAlpha(CGImageRef imageRef) { 469 | if (!imageRef) { 470 | return NO; 471 | } 472 | CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef); 473 | BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst ||alphaInfo == kCGImageAlphaNoneSkipLast); 474 | return hasAlpha; 475 | } 476 | 477 | @end 478 | 479 | 480 | -------------------------------------------------------------------------------- /ios/Classes/BkImageRenderWorker.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface BkImageRenderWorker : NSObject 4 | 5 | @property (nonatomic, assign) BOOL glInitDone; 6 | @property (nonatomic, assign) BOOL disposed; 7 | 8 | - (instancetype)initWithParams:(NSDictionary *)params onNewFrame:(void (^)(NSError* error, CGSize size, BOOL isFullPixel))onNewFrame; 9 | - (void)startWork; 10 | @end 11 | -------------------------------------------------------------------------------- /ios/Classes/BkImageRenderWorker.m: -------------------------------------------------------------------------------- 1 | 2 | #import "BkImageRenderWorker.h" 3 | 4 | #import 5 | 6 | #import "UIImageView+WebCache.h" 7 | #import "SDWebImageDownloader.h" 8 | #import "SDImageCache.h" 9 | #import "BkFlutterPixelBufferCache.h" 10 | 11 | @interface BkImageRenderWorker () 12 | 13 | @property (nonatomic, copy) NSString *url; 14 | @property (nonatomic, assign) CGFloat aspectRatio; 15 | @property (nonatomic, assign) CGFloat width; 16 | @property (nonatomic, assign) CGFloat height; 17 | @property (nonatomic, assign) CGFloat widthPixel; 18 | @property (nonatomic, assign) CGFloat heightPixel; 19 | @property (nonatomic, assign) BOOL autoResize; 20 | @property (nonatomic, copy) void (^onNewFrame)(NSError* error, CGSize size, BOOL isFullPixel); 21 | @property (nonatomic, strong) BkFlutterPixelBufferRef *pixelBufferRef; 22 | 23 | @end 24 | 25 | @implementation BkImageRenderWorker 26 | 27 | - (instancetype)initWithParams:(NSDictionary *)params onNewFrame:(void (^)(NSError* error, CGSize size, BOOL isFullPixel))onNewFrame { 28 | self = [super init]; 29 | 30 | if (self) { 31 | _onNewFrame = onNewFrame; 32 | [self initParams:params]; 33 | } 34 | 35 | return self; 36 | } 37 | 38 | - (void)initParams:(NSDictionary *)params { 39 | _url = params[@"url"]; 40 | _aspectRatio = [[UIScreen mainScreen] scale]; 41 | if (params[@"autoResize"]) 42 | _autoResize = [params[@"autoResize"] boolValue]; 43 | 44 | if (params[@"width"] && params[@"height"]) { 45 | //图片像素值没有小数 46 | _width = floor([params[@"width"] floatValue] * _aspectRatio); 47 | _height = floor([params[@"height"] floatValue] * _aspectRatio); 48 | } else { 49 | _width = -1; 50 | _height = -1; 51 | } 52 | 53 | } 54 | 55 | - (void)startWork{ 56 | __weak typeof(self) weakSelf = self; 57 | [[BkFlutterPixelBufferCache sharedInstance] loadImageAndPixelBuffer:self.url size:_autoResize ? CGSizeMake(_width, _height) : CGSizeMake(-1, -1) completion:^(BkFlutterPixelBufferRef * _Nonnull buffer, NSError* error, BOOL isFullPixel) { 58 | __strong typeof(weakSelf) strongSelf = weakSelf; 59 | [strongSelf callBufferRef:buffer isFullPixel:isFullPixel error:error]; 60 | }]; 61 | } 62 | 63 | - (void)callBufferRef:(BkFlutterPixelBufferRef *)buffer isFullPixel:(BOOL)isFullPixel error:(NSError *)error{ 64 | if (buffer && !error) { 65 | self.pixelBufferRef = buffer; 66 | CGSize size = CGSizeMake(CVPixelBufferGetWidth(buffer.ref) / _aspectRatio, CVPixelBufferGetHeight(buffer.ref) / _aspectRatio); 67 | dispatch_async(dispatch_get_main_queue(), ^{ 68 | self.onNewFrame(nil, size,isFullPixel); 69 | }); 70 | } else { 71 | dispatch_async(dispatch_get_main_queue(), ^{ 72 | self.onNewFrame(error, CGSizeZero, isFullPixel); 73 | }); 74 | } 75 | } 76 | 77 | 78 | #pragma mark - FlutterTexture 79 | 80 | - (CVPixelBufferRef)copyPixelBuffer{ 81 | if (self.pixelBufferRef) { 82 | CVPixelBufferRetain(self.pixelBufferRef.ref); 83 | return self.pixelBufferRef.ref; 84 | } else { 85 | return NULL; 86 | } 87 | } 88 | 89 | - (void)onTextureUnregistered:(NSObject*)texture { 90 | _pixelBufferRef = nil; 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /ios/bk_flutter_image.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint bk_flutter_image.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'bk_flutter_image' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.resource_bundles = { 18 | 'BkFlutterImage' => ['Assets/**/*'] 19 | } 20 | 21 | 22 | s.dependency 'Flutter' 23 | s.dependency 'SDWebImage', '>= 5.12.6' 24 | 25 | s.ios.deployment_target = '10.0' 26 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 27 | # s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 28 | end 29 | 30 | -------------------------------------------------------------------------------- /lib/bk_flutter_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import 'package:bk_flutter_image/bk_flutter_image_native.dart' 4 | if (dart.library.html) 'package:bk_flutter_image/bk_flutter_image_web.dart'; 5 | 6 | class BkFlutterImage extends BkFlutterImageImpl { 7 | const BkFlutterImage({ 8 | Key? key, 9 | required this.url, 10 | this.placeholder, 11 | this.width, 12 | this.height, 13 | this.fit = BoxFit.none, 14 | this.autoResize = true, 15 | this.cacheWidth, 16 | this.cacheHeight, 17 | this.imageErrorBuilder, 18 | }) : super( 19 | key: key, 20 | url: url, 21 | placeholder: placeholder, 22 | width: width, 23 | height: height, 24 | fit: BoxFit.none, 25 | autoResize: true, 26 | cacheWidth: cacheWidth, 27 | cacheHeight: cacheHeight, 28 | imageErrorBuilder: imageErrorBuilder); 29 | 30 | final String url; // 图片web地址 31 | final double? width; // 组件宽度 32 | final double? height; // 组件高度 33 | final String? placeholder; 34 | final BoxFit fit; // 图片显示模式 35 | final bool autoResize; // 是否下采样图片大小 36 | final double? cacheWidth; // 下采样的宽度 37 | final double? cacheHeight; // 下采样的高度 38 | final ImageErrorWidgetBuilder? imageErrorBuilder; 39 | 40 | static void setCacheMaxSize(double diskMaxSize, double memoryMaxSize) { 41 | BkFlutterImageImpl.setCacheMaxSize(diskMaxSize, memoryMaxSize); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/bk_flutter_image_native.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/rendering.dart'; 7 | import 'package:flutter/services.dart'; 8 | 9 | typedef ImageCallback = void Function(ImageViewController controller); 10 | typedef SizeCallBack = void Function(Size size); 11 | 12 | class BkFlutterImageImpl extends StatefulWidget { 13 | const BkFlutterImageImpl({ 14 | Key? key, 15 | required this.url, 16 | this.placeholder, 17 | this.width, 18 | this.height, 19 | this.fit = BoxFit.none, 20 | this.autoResize = true, 21 | this.cacheWidth, 22 | this.cacheHeight, 23 | this.imageErrorBuilder, 24 | this.color 25 | }) : super(key: key); 26 | 27 | static void registerWith(dynamic registrar) {} 28 | 29 | final String url; // 图片web地址 30 | final double? width; // 组件宽度 31 | final double? height; // 组件高度 32 | final String? placeholder; 33 | final BoxFit fit; // 图片显示模式 34 | final bool autoResize; // 是否下采样图片大小 35 | final double? cacheWidth; // 下采样的宽度 36 | final double? cacheHeight; // 下采样的高度 37 | final ImageErrorWidgetBuilder? imageErrorBuilder; 38 | final Color? color; 39 | 40 | @override 41 | State createState() => _BkFlutterImageState(); 42 | 43 | static void setCacheMaxSize(double diskMaxSize, double memoryMaxSize) { 44 | ImageViewController.setCacheMaxSize(diskMaxSize, memoryMaxSize); 45 | } 46 | } 47 | 48 | class _BkFlutterImageState extends State { 49 | ImageViewController _controller = ImageViewController(); 50 | int textureId = -1; 51 | Object? error; 52 | 53 | bool isFullPixel = false; 54 | double textureWidth = -1; 55 | double textureHeight = -1; 56 | String textureUrl = ''; 57 | bool textureAutoResize = true; 58 | 59 | Size? realSize; 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | List idList = List.empty(growable: true); 64 | idList.add( LayoutId( id: _LayoutType.None, 65 | child: Container(color: widget.color ?? Color.fromARGB(0, 0, 0, 0)))); 66 | _LayoutType layoutType = _LayoutType.None; 67 | if (textureId != -1) { 68 | idList.add(LayoutId(id: _LayoutType.Image, child: Texture(textureId: textureId))); 69 | layoutType = _LayoutType.Image; 70 | } else if (error != null && widget.imageErrorBuilder != null) { 71 | idList.add(LayoutId(id: _LayoutType.Error, child: widget.imageErrorBuilder!(context, error!, null))); 72 | layoutType = _LayoutType.Error; 73 | } else if (error == null && widget.placeholder != null) { 74 | idList.add(LayoutId( 75 | id: _LayoutType.PlaceHolder, 76 | child: Image.asset( 77 | widget.placeholder!, 78 | fit: widget.fit, 79 | width: widget.width, 80 | height: widget.height, 81 | ))); 82 | layoutType = _LayoutType.PlaceHolder; 83 | } 84 | return _BkFlutterImageRenderObjectWidget( 85 | layoutType: layoutType, 86 | width: widget.width, 87 | height: widget.height, 88 | fit: widget.fit, 89 | callBack: sizeChange, 90 | imageSize: Size(textureWidth,textureHeight), 91 | children: idList,); 92 | } 93 | 94 | @override 95 | void initState() { 96 | super.initState(); 97 | if (widget.cacheWidth != null && widget.cacheHeight != null) { 98 | updateTexture(); 99 | } 100 | } 101 | 102 | @override 103 | void didUpdateWidget(covariant BkFlutterImageImpl oldWidget) { 104 | super.didUpdateWidget(oldWidget); 105 | if (maybeUpdate(widget, null)) { 106 | updateTexture(); 107 | } 108 | } 109 | 110 | void sizeChange(Size size) { 111 | Size? tempRealSize = size == Size(double.infinity,double.infinity) ? 112 | null : Size(size.width == double.infinity ? size.height : size.width, size.height == double.infinity ? size.width : size.height); 113 | if (tempRealSize == realSize) { 114 | return; 115 | } 116 | realSize = tempRealSize; 117 | if (maybeUpdate(widget, size)) { 118 | updateTexture(); 119 | } 120 | } 121 | 122 | bool maybeUpdate(BkFlutterImageImpl newImage, Size? realSize) { 123 | if (textureUrl != newImage.url || textureId == -1) { 124 | return true; 125 | } 126 | //计算现有size 127 | Size oldSize = isFullPixel 128 | ? Size(double.maxFinite, double.maxFinite) 129 | : Size(textureWidth, textureHeight); 130 | 131 | Size requestSize = 132 | (newImage.cacheWidth != null && newImage.cacheHeight != null) 133 | ? Size(newImage.cacheWidth!, newImage.cacheHeight!) 134 | : realSize != null 135 | ? realSize 136 | : Size(0.1, 0.1); 137 | Size newSize = newImage.autoResize 138 | ? requestSize 139 | : Size(double.maxFinite, double.maxFinite); 140 | 141 | if (sizeMatchSize(oldSize, newSize)) { 142 | return false; 143 | } 144 | return true; 145 | } 146 | 147 | bool sizeMatchSize(Size fromSize, Size toSize) { 148 | double oldImageRatio = fromSize.width / fromSize.height; 149 | double newImageRatio = toSize.width / toSize.height; 150 | 151 | if (oldImageRatio >= newImageRatio && fromSize.width >= toSize.width || 152 | oldImageRatio < newImageRatio && fromSize.height >= toSize.height) { 153 | return true; 154 | } 155 | 156 | return false; 157 | } 158 | 159 | void updateTexture() { 160 | print("updateTexture"); 161 | 162 | _controller.dispose(); 163 | _controller = ImageViewController(); 164 | _controller 165 | .initialize(widget.url, 166 | width: widget.cacheWidth ?? realSize?.width, 167 | height: widget.cacheHeight ?? realSize?.height, 168 | autoResize: widget.autoResize) 169 | .then((value) { 170 | if (mounted) { 171 | setState(() { 172 | if (value != null) { 173 | textureId = value['textureId']; 174 | textureWidth = 175 | textureId == -1 ? -1 : value['textureWidth'].roundToDouble(); 176 | textureHeight = 177 | textureId == -1 ? -1 : value['textureHeight'].roundToDouble(); 178 | isFullPixel = value['isFullPixel']; 179 | error = value['error'].toString().isNotEmpty 180 | ? value['error'].toString() 181 | : (textureId == -1 ? '注册纹理失败' : null); 182 | textureAutoResize = widget.autoResize; 183 | textureUrl = widget.url; 184 | } 185 | }); 186 | } 187 | }); 188 | } 189 | 190 | @override 191 | void dispose() { 192 | //不加上这个会内存泄漏 193 | _controller.dispose(); 194 | print("_controller.dispose()"); 195 | super.dispose(); 196 | } 197 | } 198 | 199 | class ImageViewController { 200 | static MethodChannel _channel = MethodChannel('bk_flutter_image'); 201 | bool disposed = false; 202 | int textureId = -1; 203 | 204 | Future initialize(String url, 205 | {double? width, double? height, bool autoResize = true}) async { 206 | String mapString = await _channel.invokeMethod('create', { 207 | 'width': width, 208 | 'height': height, 209 | 'url': url, 210 | 'autoResize': autoResize, 211 | }); 212 | Map textureInfo = json.decode(mapString); 213 | textureId = textureInfo['textureId']; 214 | return Future.value(textureInfo); 215 | } 216 | 217 | void dispose() { 218 | logger('dispose $textureId'); 219 | _channel.invokeMethod('dispose', {'textureId': textureId}); 220 | disposed = true; 221 | } 222 | 223 | static void setCacheMaxSize(double diskMaxSize, double memoryMaxSize) { 224 | _channel.invokeMethod('setCacheSize', 225 | {'memoryMaxSize': memoryMaxSize, 'diskMaxSize': diskMaxSize}); 226 | } 227 | } 228 | 229 | 230 | enum _LayoutType { None, PlaceHolder, Image, Error} 231 | 232 | class _BkFlutterImageRenderObject extends RenderBox 233 | with 234 | ContainerRenderObjectMixin, 235 | RenderBoxContainerDefaultsMixin { 236 | 237 | SizeCallBack callBack; 238 | BoxFit _fit; 239 | Size _imageSize; 240 | double? _width; 241 | double? _height; 242 | Offset? imageOffset; 243 | 244 | _LayoutType layoutType = _LayoutType.None; 245 | 246 | 247 | _BkFlutterImageRenderObject({required this.layoutType, 248 | required Size imageSize, 249 | required this.callBack, 250 | required BoxFit fit, 251 | double? width, 252 | double? height, 253 | children,}): _imageSize = imageSize , 254 | _fit = fit, 255 | _width = width, 256 | _height = height{ 257 | addAll(children); 258 | } 259 | 260 | 261 | set fit(BoxFit fit) { 262 | if ( fit == _fit) 263 | return; 264 | _fit = fit; 265 | markNeedsLayout(); 266 | } 267 | 268 | set imageSize(Size imageSize) { 269 | if ( imageSize == _imageSize) 270 | return; 271 | _imageSize = imageSize; 272 | markNeedsLayout(); 273 | } 274 | 275 | set width(double? width) { 276 | if ( width == _width) 277 | return; 278 | _width = width; 279 | markNeedsLayout(); 280 | } 281 | 282 | set height(double? height) { 283 | if ( height == _height) 284 | return; 285 | _height = height; 286 | markNeedsLayout(); 287 | } 288 | 289 | Size get imageSize => _imageSize; 290 | BoxFit get fit => _fit; 291 | double? get height => _height; 292 | double? get width => _width; 293 | 294 | @override 295 | void setupParentData(RenderBox child) { 296 | if (child.parentData is! MultiChildLayoutParentData) 297 | child.parentData = MultiChildLayoutParentData(); 298 | } 299 | 300 | @override 301 | void paint(PaintingContext context, Offset offset) { 302 | context.pushClipRect( 303 | needsCompositing, 304 | offset, 305 | Offset.zero & size, 306 | defaultPaint, 307 | ); 308 | } 309 | 310 | Size calculateImageContainerSize(BoxConstraints c) { 311 | if (imageSize.width > 0 && imageSize.height > 0) { 312 | //1.计算逻辑,确定宽高是否都是范围值 313 | double? realWidth = c.minWidth == c.maxWidth ? c.maxWidth : null; 314 | double? realHeight = c.minHeight == c.maxHeight ? c.maxHeight : null; 315 | if (realHeight != null || realWidth != null) { 316 | //2.宽不是范围值,就采用宽,如果是则通过高计算,同理高也一样。 317 | realWidth = 318 | realWidth ?? imageSize.width / imageSize.height * realHeight!; 319 | realHeight = 320 | realHeight ?? imageSize.height / imageSize.width * realWidth; 321 | } else if (constraints.isSatisfiedBy(imageSize)) { 322 | //3.宽和搞都是范围值,并且image的大小都在这个范围内,那就直接采用image的大小 323 | realWidth = imageSize.width; 324 | realHeight = imageSize.height; 325 | } else if (c.minWidth < imageSize.width && 326 | imageSize.width <= c.maxWidth) { 327 | //4.宽和搞都是范围值,但是高不在范围内,那么就把高直接换算成范围边界的就近值,然后宽按照比例计算宽。 328 | realHeight = c.constrainHeight(imageSize.height); 329 | realWidth = imageSize.width / imageSize.height * realHeight; 330 | } else if (c.minHeight < imageSize.height && 331 | imageSize.height <= c.maxHeight) { 332 | //4.宽和搞都是范围值,但是宽不在范围内,那么就把宽直接换算成范围边界的就近值,然后高按照比例计算宽。 333 | realWidth = c.constrainWidth(imageSize.width); 334 | realHeight = imageSize.height / imageSize.width * realWidth; 335 | } else { 336 | //5.如果都不满足,那么就取值各自范围的Max,如果是无穷大,则取最小值。 337 | realWidth = c.maxWidth == double.infinity ? c.minWidth : c.maxWidth; 338 | realHeight = c.maxHeight == double.infinity ? c.minHeight : c.maxHeight; 339 | } 340 | return Size(realWidth,realHeight); 341 | } 342 | return Size.zero; 343 | } 344 | 345 | Size calculateImageContentSize(Size size){ 346 | if (imageSize.width > 0 && 347 | imageSize.height > 0) { 348 | Size realSize = imageSize; 349 | double wRatio = size.width / imageSize.width; 350 | double hRatio = size.height / imageSize.height; 351 | switch (fit) { 352 | case BoxFit.contain: 353 | if (wRatio <= hRatio) { 354 | realSize = Size(size.width, imageSize.height * wRatio); 355 | } else { 356 | realSize = Size(imageSize.width * hRatio, size.height); 357 | } 358 | break; 359 | case BoxFit.cover: 360 | if (wRatio >= hRatio) { 361 | realSize = Size(size.width, imageSize.height * wRatio); 362 | } else { 363 | realSize = Size(imageSize.width * hRatio, size.height); 364 | } 365 | break; 366 | case BoxFit.fitWidth: 367 | realSize = Size(size.width, imageSize.height * wRatio); 368 | break; 369 | case BoxFit.fitHeight: 370 | realSize = Size(imageSize.width * hRatio, size.height); 371 | break; 372 | case BoxFit.scaleDown: 373 | if (wRatio < 1 || hRatio < 1) { 374 | if (wRatio <= hRatio) { 375 | realSize = Size(size.width, imageSize.height * wRatio); 376 | } else { 377 | realSize = Size(imageSize.width * hRatio, size.height); 378 | } 379 | } else { 380 | realSize = imageSize; 381 | } 382 | break; 383 | case BoxFit.fill: 384 | realSize = size; 385 | break; 386 | case BoxFit.none: 387 | realSize = imageSize; 388 | break; 389 | } 390 | return realSize; 391 | } 392 | return Size.zero; 393 | } 394 | 395 | @override 396 | void performLayout() { 397 | Map idToChild = {}; 398 | RenderBox? child = firstChild; 399 | while (child != null) { 400 | final MultiChildLayoutParentData childParentData = child.parentData! as MultiChildLayoutParentData; 401 | idToChild[childParentData.id!] = child; 402 | child = childParentData.nextSibling; 403 | } 404 | 405 | //根据限定大小,计算出新的约束 406 | BoxConstraints c = constraints.tighten(width: width, height: height); 407 | 408 | //根据约束情况返回请求信息 409 | if (layoutType != _LayoutType.Error) { 410 | callBack(c.biggest); 411 | } 412 | //开始实际布局, 413 | Size tempSize = Size.zero; 414 | imageOffset = null; 415 | switch (layoutType) { 416 | case _LayoutType.PlaceHolder: 417 | { //布局完成placeholder 418 | RenderBox? content = idToChild[_LayoutType.PlaceHolder]; 419 | if (content != null) { 420 | content.layout(c, parentUsesSize: true); 421 | tempSize = content.size; 422 | } 423 | } 424 | break; 425 | case _LayoutType.Error: 426 | { 427 | RenderBox? content = idToChild[_LayoutType.Error]; 428 | if (content != null) { 429 | content.layout(c, parentUsesSize: true); 430 | tempSize = content.size; 431 | } 432 | } 433 | break; 434 | case _LayoutType.Image: 435 | { 436 | RenderBox? content = idToChild[_LayoutType.Image]; 437 | if (content != null) { 438 | tempSize = calculateImageContainerSize(c); 439 | Size imageContentSize = calculateImageContentSize(tempSize); 440 | content.layout(BoxConstraints.tight(imageContentSize)); 441 | final MultiChildLayoutParentData childParentData = content 442 | .parentData! as MultiChildLayoutParentData; 443 | childParentData.offset = Offset((tempSize.width - imageContentSize.width) / 2, 444 | (tempSize.height - imageContentSize.height) / 2); 445 | imageOffset = childParentData.offset; 446 | } 447 | } 448 | } 449 | 450 | tempSize = Size (tempSize.width == double.infinity ? 0 : tempSize.width, tempSize.height == double.infinity ? 0 : tempSize.height); 451 | RenderBox? content = idToChild[_LayoutType.None]; 452 | if (content != null) { 453 | content.layout(c,parentUsesSize: false); 454 | } 455 | size = tempSize; 456 | } 457 | 458 | 459 | @override 460 | double? computeDistanceToActualBaseline(TextBaseline baseline) { 461 | return defaultComputeDistanceToHighestActualBaseline(baseline); 462 | } 463 | 464 | @override 465 | bool hitTestChildren(HitTestResult result, {required Offset position}) { 466 | return defaultHitTestChildren(result as BoxHitTestResult, position: position); 467 | } 468 | } 469 | 470 | class _BkFlutterImageRenderObjectWidget extends MultiChildRenderObjectWidget { 471 | 472 | final SizeCallBack callBack; 473 | final BoxFit fit; 474 | final Size imageSize; 475 | final double? width; 476 | final double? height; 477 | final _LayoutType layoutType; 478 | 479 | _BkFlutterImageRenderObjectWidget({ 480 | Key? key, 481 | required this.layoutType, 482 | required this.callBack, 483 | required this.fit, 484 | required this.imageSize, 485 | this.width, 486 | this.height, 487 | List children = const [], 488 | }) : super(key: key, children: children); 489 | 490 | @override 491 | RenderObject createRenderObject(BuildContext context) { 492 | // TODO: implement createRenderObject 493 | return _BkFlutterImageRenderObject(layoutType: layoutType,imageSize: imageSize, width: width, height: height, fit: fit,callBack: callBack,); 494 | } 495 | 496 | @override 497 | void updateRenderObject(BuildContext context, _BkFlutterImageRenderObject renderObject) { 498 | // TODO: implement updateRenderObject 499 | renderObject 500 | ..layoutType = layoutType 501 | ..fit = fit 502 | ..imageSize = imageSize 503 | ..callBack = callBack 504 | ..width = width 505 | ..height = height; 506 | } 507 | } 508 | 509 | logger(dynamic msgs) { 510 | assert(() { 511 | debugPrint('[DEBUG] # $msgs'); 512 | return true; 513 | }()); 514 | } 515 | -------------------------------------------------------------------------------- /lib/bk_flutter_image_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | class BkFlutterImageImpl extends StatefulWidget { 8 | const BkFlutterImageImpl({ 9 | Key? key, 10 | required this.url, 11 | this.placeholder, 12 | this.width, 13 | this.height, 14 | this.fit = BoxFit.none, 15 | this.autoResize = true, 16 | this.cacheWidth, 17 | this.cacheHeight, 18 | this.imageErrorBuilder, 19 | }) : super(key: key); 20 | 21 | static void registerWith(dynamic registrar) {} 22 | 23 | final String url; // 图片web地址 24 | final double? width; // 组件宽度 25 | final double? height; // 组件高度 26 | final String? placeholder; 27 | final BoxFit fit; // 图片显示模式 28 | final bool autoResize; // 是否下采样图片大小 29 | final double? cacheWidth; // 下采样的宽度 30 | final double? cacheHeight; // 下采样的高度 31 | final ImageErrorWidgetBuilder? imageErrorBuilder; 32 | 33 | @override 34 | State createState() => _BkFlutterImageState(); 35 | 36 | static void setCacheMaxSize(double diskMaxSize, double memoryMaxSize) {} 37 | } 38 | 39 | class _BkFlutterImageState extends State { 40 | @override 41 | Widget build(BuildContext context) { 42 | // String? placeholder = widget.placeholder; 43 | 44 | // if (placeholder == null || placeholder == '') { 45 | return Image.network( 46 | widget.url, 47 | width: widget.width, 48 | height: widget.height, 49 | fit: widget.fit, 50 | cacheWidth: widget.cacheWidth?.toInt(), 51 | cacheHeight: widget.cacheHeight?.toInt(), 52 | errorBuilder: widget.imageErrorBuilder, 53 | ); 54 | // } else {//web添加placeholder,动画有bug 55 | // return FadeInImage.assetNetwork( 56 | // placeholder: placeholder, 57 | // image: widget.url, 58 | // width: widget.width, 59 | // height: widget.height, 60 | // fit: widget.fit, 61 | // imageCacheHeight: widget.cacheHeight?.toInt(), 62 | // imageCacheWidth: widget.cacheWidth?.toInt(), 63 | // imageErrorBuilder: widget.imageErrorBuilder); 64 | // } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: bk_flutter_image 2 | description: A flutter plugin project with texture implement. 3 | version: 0.1.1 4 | author: 5 | homepage: 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | dev_dependencies: 12 | flutter_test: 13 | sdk: flutter 14 | # For information on the generic Dart part of this file, see the 15 | # following page: https://dart.dev/tools/pub/pubspec 16 | 17 | # The following section is specific to Flutter. 18 | flutter: 19 | # This section identifies this Flutter project as a plugin project. 20 | # The androidPackage and pluginClass identifiers should not ordinarily 21 | # be modified. They are used by the tooling to maintain consistency when 22 | # adding or updating assets for this project. 23 | plugin: 24 | platforms: 25 | android: 26 | package: com.bk.flutter.bk_texture_image 27 | pluginClass: BkFlutterImagePlugin 28 | ios: 29 | pluginClass: BkFlutterImagePlugin 30 | web: 31 | pluginClass: BkFlutterImageImpl 32 | fileName: bk_flutter_image_web.dart 33 | 34 | # To add assets to your plugin package, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | # 39 | # For details regarding assets in packages, see 40 | # https://flutter.dev/assets-and-images/#from-packages 41 | # 42 | # An image asset can refer to one or more resolution-specific "variants", see 43 | # https://flutter.dev/assets-and-images/#resolution-aware. 44 | 45 | # To add custom fonts to your plugin package, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts in packages, see 63 | # https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /test/bk_flutter_image_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:bk_flutter_image/bk_flutter_image.dart'; 4 | 5 | void main() { 6 | const MethodChannel channel = MethodChannel('bk_flutter_image'); 7 | 8 | TestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | setUp(() { 11 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 12 | return '42'; 13 | }); 14 | }); 15 | 16 | tearDown(() { 17 | channel.setMockMethodCallHandler(null); 18 | }); 19 | 20 | test('getPlatformVersion', () async { 21 | // expect(await BkFlutterImage.platformVersion, '42'); 22 | }); 23 | } 24 | --------------------------------------------------------------------------------