├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README-ZH.md ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── thl │ │ │ │ │ └── nine_grid_view_example │ │ │ │ │ └── MainActivity.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.properties │ └── settings.gradle ├── assets │ └── images │ │ └── ali_connors.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── drag_sort_page.dart │ ├── main.dart │ ├── models.dart │ ├── single_picture_page.dart │ └── utils.dart ├── pkgget ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── lib ├── nine_grid_view.dart └── src │ ├── drag_sort_view.dart │ └── nine_grid_view.dart ├── pkgget ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── nine_grid_view1.jpg ├── nine_grid_view2.jpg ├── nine_grid_view3.jpg ├── nine_grid_view4.jpg ├── nine_grid_view5.jpg ├── nine_grid_view6.jpg ├── nine_grid_view7.jpg ├── nine_grid_view8.jpg └── nine_grid_view9.gif └── uploadMaster /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.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: 5f21edf8b66e31a39133177319414395cc5b5f48 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.0 2 | * TODO: Migrate to null-safety. 3 | 4 | ## 1.0.7 5 | 6 | * TODO: delete DragSortView def color. 7 | 8 | ## 1.0.6 9 | 10 | * TODO: fix DragSortView touch position bug. 11 | 12 | ## 1.0.5 13 | 14 | * TODO: space param support QQ Group. 15 | 16 | ## 1.0.3 17 | 18 | * TODO: add QQ Group avatar effects. 19 | 20 | ## 1.0.2 21 | 22 | * TODO: fix bug. 23 | 24 | ## 1.0.1 25 | 26 | * TODO: add license. 27 | 28 | ## 1.0.0 29 | 30 | * TODO: NineGridView & DragSortView. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Sky24n 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | Language: [English](https://github.com/flutterchina/nine_grid_view) | 中文简体 2 | 3 | [![Pub](https://img.shields.io/pub/v/nine_grid_view.svg?style=flat-square&color=009688)](https://pub.dartlang.org/packages/nine_grid_view)      [![Pub](https://img.shields.io/pub/v/nine_grid_view.svg?style=flat-square&color=2196F3)](https://pub.flutter-io.cn/packages/nine_grid_view) 4 | 5 | ### NineGridView 6 | 类似微博动态,微信朋友圈,展示图片的九宫格控件。支持单张大图预览。 7 | 同时也支持微信群组,钉钉群组,QQ讨论组头像效果。 8 | 9 | ### DragSortView 10 | 类似微博/微信发布动态选图九宫格。支持按压放大效果,拖拽排序,拖拽到指定位置删除。 11 | 12 | ### Pub 13 | ```yaml 14 | dependencies: 15 | nine_grid_view: #latest version 16 | ``` 17 | 18 | ### Example 19 | ```yaml 20 | import 'package:nine_grid_view/nine_grid_view.dart'; 21 | 22 | // bigImage参数 单张大图建议使用中等质量图片,因为原图太大加载耗时。 23 | NineGridView( 24 | margin: EdgeInsets.all(12), 25 | padding: EdgeInsets.all(5), 26 | space: 5, 27 | type: NineGridType.weChat, 28 | itemCount: itemCount, 29 | itemBuilder: (BuildContext context, int index) {}, 30 | ); 31 | 32 | // 头像需要设置宽、高参数。 33 | NineGridView( 34 | width: 120, 35 | height: 120, 36 | padding: EdgeInsets.all(5), 37 | space: 5, 38 | type: NineGridType.qqGp, //NineGridType.weChatGp, NineGridType.dingTalkGp 39 | itemCount: itemCount, 40 | itemBuilder: (BuildContext context, int index) {}, 41 | ); 42 | 43 | // 建议使用略微缩图,因为原图太大可能会引起重复加载导致闪动. 44 | DragSortView( 45 | imageList, 46 | space: 5, 47 | margin: EdgeInsets.all(20), 48 | padding: EdgeInsets.all(0), 49 | itemBuilder: (BuildContext context, int index) {}, 50 | initBuilder: (BuildContext context) {}, 51 | onDragListener: (MotionEvent event, double itemWidth) { 52 | /// 判断拖动到指定位置删除 53 | /// return true; 54 | if (event.globalY > 600) { 55 | return true; 56 | } 57 | return false; 58 | }, 59 | ); 60 | ``` 61 | 62 | ### Screenshots 63 | 64 | 截图无法查看? 65 | 掘金地址:[Flutter 仿微信/微博九宫格](https://juejin.im/post/5ee825ab5188251f3f07af75)、[Flutter 仿QQ讨论组头像](https://juejin.im/post/5efd42665188252e6350d496) 66 | 简书地址:[Flutter 仿微信/微博九宫格](https://www.jianshu.com/p/73548cc82326) 67 | 68 | |![](https://s1.ax1x.com/2020/08/05/ar88bR.jpg)|![](https://s1.ax1x.com/2020/08/05/arG6OJ.jpg)|![](https://s1.ax1x.com/2020/08/05/artZyF.jpg)| 69 | |:---:|:---:|:---:| 70 | |![](https://s1.ax1x.com/2020/08/05/artlJx.jpg)|![](https://s1.ax1x.com/2020/08/05/artJyD.jpg)|![](https://s1.ax1x.com/2020/08/05/art2wj.jpg)| 71 | |![](https://s1.ax1x.com/2020/08/05/art4f0.jpg)|![](https://s1.ax1x.com/2020/08/05/artIpV.jpg)|![](https://s1.ax1x.com/2020/08/05/artXkR.gif)| 72 | 73 | ## Changelog 74 | Please see the [Changelog](CHANGELOG.md) page to know what's recently changed. 75 | 76 | ## App 77 | [Moss App](https://github.com/Sky24n/Moss) 78 | 79 | ### Others 80 | 另外一个[NineGridView](https://github.com/flutterchina/flukit)在 [flukit](https://github.com/flutterchina/flukit) UI组件库里面,通过封装GridView实现。本项目使用的Stack + Positioned实现。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Language: English | [中文简体](README-ZH.md) 2 | 3 | [![Pub](https://img.shields.io/pub/v/nine_grid_view.svg?style=flat-square&color=009688)](https://pub.dartlang.org/packages/nine_grid_view)      [![Pub](https://img.shields.io/pub/v/nine_grid_view.svg?style=flat-square&color=2196F3)](https://pub.flutter-io.cn/packages/nine_grid_view) 4 | 5 | ### NineGridView 6 | Similar to Weibo dynamics, WeChat circle of friends, nine grid view controls to display pictures. Support single big picture preview. 7 | It also supports WeChat group , DingTalk group, QQ group avatar effects. 8 | 9 | ### DragSortView 10 | Similar to Weibo/WeChat release dynamic picture selection nine grid view. Support press to enlarge effect, drag and drop sorting, drag and drop to a specified location to delete. 11 | 12 | ### Pub 13 | ```yaml 14 | dependencies: 15 | nine_grid_view: ^2.0.0 16 | ``` 17 | 18 | ### Example 19 | ```yaml 20 | import 'package:nine_grid_view/nine_grid_view.dart'; 21 | 22 | // bigImage param, It is recommended to use a medium-quality picture, because the original picture is too large and takes time to load. 23 | NineGridView( 24 | margin: EdgeInsets.all(12), 25 | padding: EdgeInsets.all(5), 26 | space: 5, 27 | type: NineGridType.weChat,//NineGridType.weChat, NineGridType.weiBo 28 | itemCount: itemCount, 29 | itemBuilder: (BuildContext context, int index) {}, 30 | ); 31 | 32 | // group avatar. 33 | // need width, height param. 34 | NineGridView( 35 | width: 120, 36 | height: 120, 37 | padding: EdgeInsets.all(5), 38 | space: 5, 39 | type: NineGridType.qqGp, //NineGridType.weChatGp, NineGridType.dingTalkGp 40 | itemCount: itemCount, 41 | itemBuilder: (BuildContext context, int index) {}, 42 | ); 43 | 44 | // It is recommended to use a thumbnail picture,because the original picture is too large, it may cause repeated loading and cause flashing. 45 | DragSortView( 46 | imageList, 47 | space: 5, 48 | margin: EdgeInsets.all(20), 49 | padding: EdgeInsets.all(0), 50 | itemBuilder: (BuildContext context, int index) {}, 51 | initBuilder: (BuildContext context) {}, 52 | onDragListener: (MotionEvent event, double itemWidth) { 53 | /// Judge to drag to the specified position to delete 54 | /// return true; 55 | if (event.globalY > 600) { 56 | return true; 57 | } 58 | return false; 59 | }, 60 | ); 61 | ``` 62 | 63 | ### Screenshots 64 | |![](https://s1.ax1x.com/2020/08/05/ar88bR.jpg)|![](https://s1.ax1x.com/2020/08/05/arG6OJ.jpg)|![](https://s1.ax1x.com/2020/08/05/artZyF.jpg)| 65 | |:---:|:---:|:---:| 66 | |![](https://s1.ax1x.com/2020/08/05/artlJx.jpg)|![](https://s1.ax1x.com/2020/08/05/artJyD.jpg)|![](https://s1.ax1x.com/2020/08/05/art2wj.jpg)| 67 | |![](https://s1.ax1x.com/2020/08/05/art4f0.jpg)|![](https://s1.ax1x.com/2020/08/05/artIpV.jpg)|![](https://s1.ax1x.com/2020/08/05/artXkR.gif)| 68 | 69 | ## Changelog 70 | Please see the [Changelog](CHANGELOG.md) page to know what's recently changed. 71 | 72 | ## Others 73 | Another [NineGridView](https://github.com/flutterchina/flukit) in [flukit](https://github.com/flutterchina/flukit) UI Kit,using GridView implementation。But in this project used Stack + Positioned。 74 | 75 | ## App 76 | [Moss](https://github.com/Sky24n/Moss). 77 | A GitHub client app developed with Flutter, which supports Android iOS Web. 78 | Web :[Flutter Web](https://sky24n.github.io/Sky24n/moss). 79 | 80 | |![](https://z3.ax1x.com/2021/04/26/gp1hm6.jpg)|![](https://z3.ax1x.com/2021/04/26/gp1Tte.jpg)|![](https://z3.ax1x.com/2021/04/26/gp17fH.jpg)| 81 | |:---:|:---:|:---:| 82 | 83 | 84 | -------------------------------------------------------------------------------- /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 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /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: 5f21edf8b66e31a39133177319414395cc5b5f48 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # nine_grid_view_example 2 | 3 | A new Flutter application. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /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 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.thl.nine_grid_view_example" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | } 42 | 43 | buildTypes { 44 | release { 45 | // TODO: Add your own signing config for the release build. 46 | // Signing with the debug keys for now, so `flutter run --release` works. 47 | signingConfig signingConfigs.debug 48 | } 49 | } 50 | } 51 | 52 | flutter { 53 | source '../..' 54 | } 55 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 13 | 20 | 24 | 27 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/thl/nine_grid_view_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.thl.nine_grid_view_example; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter 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 | include ':app' 6 | 7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 8 | def properties = new Properties() 9 | 10 | assert localPropertiesFile.exists() 11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 12 | 13 | def flutterSdkPath = properties.getProperty("flutter.sdk") 14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 | -------------------------------------------------------------------------------- /example/assets/images/ali_connors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/example/assets/images/ali_connors.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 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, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - path_provider (0.0.1): 7 | - Flutter 8 | - path_provider_linux (0.0.1): 9 | - Flutter 10 | - path_provider_macos (0.0.1): 11 | - Flutter 12 | - sqflite (0.0.1): 13 | - Flutter 14 | - FMDB (~> 2.7.2) 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 19 | - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) 20 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 21 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 22 | 23 | SPEC REPOS: 24 | trunk: 25 | - FMDB 26 | 27 | EXTERNAL SOURCES: 28 | Flutter: 29 | :path: Flutter 30 | path_provider: 31 | :path: ".symlinks/plugins/path_provider/ios" 32 | path_provider_linux: 33 | :path: ".symlinks/plugins/path_provider_linux/ios" 34 | path_provider_macos: 35 | :path: ".symlinks/plugins/path_provider_macos/ios" 36 | sqflite: 37 | :path: ".symlinks/plugins/sqflite/ios" 38 | 39 | SPEC CHECKSUMS: 40 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 41 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 42 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 43 | path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 44 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 45 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 46 | 47 | PODFILE CHECKSUM: c34e2287a9ccaa606aeceab922830efb9a6ff69a 48 | 49 | COCOAPODS: 1.8.4 50 | -------------------------------------------------------------------------------- /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 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 8FABF52CA6D6971F294870FC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06C3D5CA10B89B03B8FB58E9 /* Pods_Runner.framework */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 06C3D5CA10B89B03B8FB58E9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 0DC07D18723EC2323CFCF4AA /* 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 = ""; }; 35 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 36 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 37 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 38 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 39 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 41 | 7FD2A6C47B7BB400C1658E49 /* 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 = ""; }; 42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | ABE3E14B0FA4973798DD1DCB /* 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 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 8FABF52CA6D6971F294870FC /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 2ED435FCCE9B1D032CC80B9C /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 0DC07D18723EC2323CFCF4AA /* Pods-Runner.debug.xcconfig */, 68 | ABE3E14B0FA4973798DD1DCB /* Pods-Runner.release.xcconfig */, 69 | 7FD2A6C47B7BB400C1658E49 /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | name = Pods; 72 | path = Pods; 73 | sourceTree = ""; 74 | }; 75 | 9740EEB11CF90186004384FC /* Flutter */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 82 | ); 83 | name = Flutter; 84 | sourceTree = ""; 85 | }; 86 | 97C146E51CF9000F007C117D = { 87 | isa = PBXGroup; 88 | children = ( 89 | 9740EEB11CF90186004384FC /* Flutter */, 90 | 97C146F01CF9000F007C117D /* Runner */, 91 | 97C146EF1CF9000F007C117D /* Products */, 92 | 2ED435FCCE9B1D032CC80B9C /* Pods */, 93 | C47AA03365D25578BC5EE4D2 /* Frameworks */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 97C146EF1CF9000F007C117D /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 97C146EE1CF9000F007C117D /* Runner.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 97C146F01CF9000F007C117D /* Runner */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 109 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 110 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 111 | 97C147021CF9000F007C117D /* Info.plist */, 112 | 97C146F11CF9000F007C117D /* Supporting Files */, 113 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 114 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 115 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 116 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 117 | ); 118 | path = Runner; 119 | sourceTree = ""; 120 | }; 121 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | C47AA03365D25578BC5EE4D2 /* Frameworks */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 06C3D5CA10B89B03B8FB58E9 /* Pods_Runner.framework */, 132 | ); 133 | name = Frameworks; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 97C146ED1CF9000F007C117D /* Runner */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 142 | buildPhases = ( 143 | 64328642DFF20AA12B2BC2C5 /* [CP] Check Pods Manifest.lock */, 144 | 9740EEB61CF901F6004384FC /* Run Script */, 145 | 97C146EA1CF9000F007C117D /* Sources */, 146 | 97C146EB1CF9000F007C117D /* Frameworks */, 147 | 97C146EC1CF9000F007C117D /* Resources */, 148 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 149 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 150 | 0197C28B68C8E7F206E4946F /* [CP] Embed Pods Frameworks */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | ); 156 | name = Runner; 157 | productName = Runner; 158 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 159 | productType = "com.apple.product-type.application"; 160 | }; 161 | /* End PBXNativeTarget section */ 162 | 163 | /* Begin PBXProject section */ 164 | 97C146E61CF9000F007C117D /* Project object */ = { 165 | isa = PBXProject; 166 | attributes = { 167 | LastUpgradeCheck = 1020; 168 | ORGANIZATIONNAME = ""; 169 | TargetAttributes = { 170 | 97C146ED1CF9000F007C117D = { 171 | CreatedOnToolsVersion = 7.3.1; 172 | LastSwiftMigration = 1100; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 177 | compatibilityVersion = "Xcode 9.3"; 178 | developmentRegion = en; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | Base, 183 | ); 184 | mainGroup = 97C146E51CF9000F007C117D; 185 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | 97C146ED1CF9000F007C117D /* Runner */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | 97C146EC1CF9000F007C117D /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 200 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 201 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 202 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXShellScriptBuildPhase section */ 209 | 0197C28B68C8E7F206E4946F /* [CP] Embed Pods Frameworks */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 216 | "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", 217 | "${PODS_ROOT}/../Flutter/Flutter.framework", 218 | "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", 219 | "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", 220 | ); 221 | name = "[CP] Embed Pods Frameworks"; 222 | outputPaths = ( 223 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", 224 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 225 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", 226 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | shellPath = /bin/sh; 230 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 231 | showEnvVarsInLog = 0; 232 | }; 233 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 234 | isa = PBXShellScriptBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | ); 238 | inputPaths = ( 239 | ); 240 | name = "Thin Binary"; 241 | outputPaths = ( 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | shellPath = /bin/sh; 245 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 246 | }; 247 | 64328642DFF20AA12B2BC2C5 /* [CP] Check Pods Manifest.lock */ = { 248 | isa = PBXShellScriptBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | ); 252 | inputFileListPaths = ( 253 | ); 254 | inputPaths = ( 255 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 256 | "${PODS_ROOT}/Manifest.lock", 257 | ); 258 | name = "[CP] Check Pods Manifest.lock"; 259 | outputFileListPaths = ( 260 | ); 261 | outputPaths = ( 262 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | 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"; 267 | showEnvVarsInLog = 0; 268 | }; 269 | 9740EEB61CF901F6004384FC /* Run Script */ = { 270 | isa = PBXShellScriptBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputPaths = ( 275 | ); 276 | name = "Run Script"; 277 | outputPaths = ( 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | shellPath = /bin/sh; 281 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 282 | }; 283 | /* End PBXShellScriptBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 97C146EA1CF9000F007C117D /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 291 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXSourcesBuildPhase section */ 296 | 297 | /* Begin PBXVariantGroup section */ 298 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 299 | isa = PBXVariantGroup; 300 | children = ( 301 | 97C146FB1CF9000F007C117D /* Base */, 302 | ); 303 | name = Main.storyboard; 304 | sourceTree = ""; 305 | }; 306 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | 97C147001CF9000F007C117D /* Base */, 310 | ); 311 | name = LaunchScreen.storyboard; 312 | sourceTree = ""; 313 | }; 314 | /* End PBXVariantGroup section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 318 | isa = XCBuildConfiguration; 319 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INFINITE_RECURSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 339 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 342 | CLANG_WARN_STRICT_PROTOTYPES = YES; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 347 | COPY_PHASE_STRIP = NO; 348 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 349 | ENABLE_NS_ASSERTIONS = NO; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu99; 352 | GCC_NO_COMMON_BLOCKS = YES; 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 360 | MTL_ENABLE_DEBUG_INFO = NO; 361 | SDKROOT = iphoneos; 362 | SUPPORTED_PLATFORMS = iphoneos; 363 | TARGETED_DEVICE_FAMILY = "1,2"; 364 | VALIDATE_PRODUCT = YES; 365 | }; 366 | name = Profile; 367 | }; 368 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 369 | isa = XCBuildConfiguration; 370 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 371 | buildSettings = { 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 373 | CLANG_ENABLE_MODULES = YES; 374 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 375 | ENABLE_BITCODE = NO; 376 | FRAMEWORK_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "$(PROJECT_DIR)/Flutter", 379 | ); 380 | INFOPLIST_FILE = Runner/Info.plist; 381 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 382 | LIBRARY_SEARCH_PATHS = ( 383 | "$(inherited)", 384 | "$(PROJECT_DIR)/Flutter", 385 | ); 386 | PRODUCT_BUNDLE_IDENTIFIER = com.thl.nineGridViewExample; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 389 | SWIFT_VERSION = 5.0; 390 | VERSIONING_SYSTEM = "apple-generic"; 391 | }; 392 | name = Profile; 393 | }; 394 | 97C147031CF9000F007C117D /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_ANALYZER_NONNULL = YES; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_COMMA = YES; 407 | CLANG_WARN_CONSTANT_CONVERSION = YES; 408 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 409 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN_ENUM_CONVERSION = YES; 412 | CLANG_WARN_INFINITE_RECURSION = YES; 413 | CLANG_WARN_INT_CONVERSION = YES; 414 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 416 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 417 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 418 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 419 | CLANG_WARN_STRICT_PROTOTYPES = YES; 420 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 424 | COPY_PHASE_STRIP = NO; 425 | DEBUG_INFORMATION_FORMAT = dwarf; 426 | ENABLE_STRICT_OBJC_MSGSEND = YES; 427 | ENABLE_TESTABILITY = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu99; 429 | GCC_DYNAMIC_NO_PIC = NO; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_OPTIMIZATION_LEVEL = 0; 432 | GCC_PREPROCESSOR_DEFINITIONS = ( 433 | "DEBUG=1", 434 | "$(inherited)", 435 | ); 436 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 437 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 438 | GCC_WARN_UNDECLARED_SELECTOR = YES; 439 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 440 | GCC_WARN_UNUSED_FUNCTION = YES; 441 | GCC_WARN_UNUSED_VARIABLE = YES; 442 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 443 | MTL_ENABLE_DEBUG_INFO = YES; 444 | ONLY_ACTIVE_ARCH = YES; 445 | SDKROOT = iphoneos; 446 | TARGETED_DEVICE_FAMILY = "1,2"; 447 | }; 448 | name = Debug; 449 | }; 450 | 97C147041CF9000F007C117D /* Release */ = { 451 | isa = XCBuildConfiguration; 452 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 453 | buildSettings = { 454 | ALWAYS_SEARCH_USER_PATHS = NO; 455 | CLANG_ANALYZER_NONNULL = YES; 456 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 457 | CLANG_CXX_LIBRARY = "libc++"; 458 | CLANG_ENABLE_MODULES = YES; 459 | CLANG_ENABLE_OBJC_ARC = YES; 460 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 461 | CLANG_WARN_BOOL_CONVERSION = YES; 462 | CLANG_WARN_COMMA = YES; 463 | CLANG_WARN_CONSTANT_CONVERSION = YES; 464 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 465 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 466 | CLANG_WARN_EMPTY_BODY = YES; 467 | CLANG_WARN_ENUM_CONVERSION = YES; 468 | CLANG_WARN_INFINITE_RECURSION = YES; 469 | CLANG_WARN_INT_CONVERSION = YES; 470 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 471 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 472 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 473 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 474 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 475 | CLANG_WARN_STRICT_PROTOTYPES = YES; 476 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 477 | CLANG_WARN_UNREACHABLE_CODE = YES; 478 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 479 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 480 | COPY_PHASE_STRIP = NO; 481 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 482 | ENABLE_NS_ASSERTIONS = NO; 483 | ENABLE_STRICT_OBJC_MSGSEND = YES; 484 | GCC_C_LANGUAGE_STANDARD = gnu99; 485 | GCC_NO_COMMON_BLOCKS = YES; 486 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 487 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 488 | GCC_WARN_UNDECLARED_SELECTOR = YES; 489 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 490 | GCC_WARN_UNUSED_FUNCTION = YES; 491 | GCC_WARN_UNUSED_VARIABLE = YES; 492 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 493 | MTL_ENABLE_DEBUG_INFO = NO; 494 | SDKROOT = iphoneos; 495 | SUPPORTED_PLATFORMS = iphoneos; 496 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | VALIDATE_PRODUCT = YES; 499 | }; 500 | name = Release; 501 | }; 502 | 97C147061CF9000F007C117D /* Debug */ = { 503 | isa = XCBuildConfiguration; 504 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 505 | buildSettings = { 506 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 507 | CLANG_ENABLE_MODULES = YES; 508 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 509 | ENABLE_BITCODE = NO; 510 | FRAMEWORK_SEARCH_PATHS = ( 511 | "$(inherited)", 512 | "$(PROJECT_DIR)/Flutter", 513 | ); 514 | INFOPLIST_FILE = Runner/Info.plist; 515 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 516 | LIBRARY_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "$(PROJECT_DIR)/Flutter", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = com.thl.nineGridViewExample; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 523 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 524 | SWIFT_VERSION = 5.0; 525 | VERSIONING_SYSTEM = "apple-generic"; 526 | }; 527 | name = Debug; 528 | }; 529 | 97C147071CF9000F007C117D /* Release */ = { 530 | isa = XCBuildConfiguration; 531 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 532 | buildSettings = { 533 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 534 | CLANG_ENABLE_MODULES = YES; 535 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 536 | ENABLE_BITCODE = NO; 537 | FRAMEWORK_SEARCH_PATHS = ( 538 | "$(inherited)", 539 | "$(PROJECT_DIR)/Flutter", 540 | ); 541 | INFOPLIST_FILE = Runner/Info.plist; 542 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 543 | LIBRARY_SEARCH_PATHS = ( 544 | "$(inherited)", 545 | "$(PROJECT_DIR)/Flutter", 546 | ); 547 | PRODUCT_BUNDLE_IDENTIFIER = com.thl.nineGridViewExample; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 550 | SWIFT_VERSION = 5.0; 551 | VERSIONING_SYSTEM = "apple-generic"; 552 | }; 553 | name = Release; 554 | }; 555 | /* End XCBuildConfiguration section */ 556 | 557 | /* Begin XCConfigurationList section */ 558 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 559 | isa = XCConfigurationList; 560 | buildConfigurations = ( 561 | 97C147031CF9000F007C117D /* Debug */, 562 | 97C147041CF9000F007C117D /* Release */, 563 | 249021D3217E4FDB00AE95B9 /* Profile */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 569 | isa = XCConfigurationList; 570 | buildConfigurations = ( 571 | 97C147061CF9000F007C117D /* Debug */, 572 | 97C147071CF9000F007C117D /* Release */, 573 | 249021D4217E4FDB00AE95B9 /* Profile */, 574 | ); 575 | defaultConfigurationIsVisible = 0; 576 | defaultConfigurationName = Release; 577 | }; 578 | /* End XCConfigurationList section */ 579 | }; 580 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 581 | } 582 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/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/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | nine_grid_view_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" 2 | -------------------------------------------------------------------------------- /example/lib/drag_sort_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:nine_grid_view/nine_grid_view.dart'; 3 | 4 | import 'models.dart'; 5 | import 'utils.dart'; 6 | 7 | class DragSortPage extends StatefulWidget { 8 | @override 9 | _DragSortPageState createState() => _DragSortPageState(); 10 | } 11 | 12 | class _DragSortPageState extends State { 13 | List imageList = []; 14 | int moveAction = MotionEvent.actionUp; 15 | bool _canDelete = false; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _init(); 21 | } 22 | 23 | void _init() { 24 | imageList = Utils.getTestData(); 25 | } 26 | 27 | void _loadAssets(BuildContext context) { 28 | // pick Images. 29 | Utils.showSnackBar(context, "pick Images."); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar( 36 | title: const Text('DragSortView'), 37 | centerTitle: true, 38 | actions: [ 39 | IconButton( 40 | icon: Icon(Icons.refresh), 41 | onPressed: () { 42 | setState(() { 43 | _init(); 44 | }); 45 | }), 46 | ], 47 | ), 48 | body: ListView( 49 | children: [ 50 | DragSortView( 51 | imageList, 52 | space: 5, 53 | margin: EdgeInsets.all(20), 54 | padding: EdgeInsets.all(0), 55 | itemBuilder: (BuildContext context, int index) { 56 | ImageBean bean = imageList[index]; 57 | // It is recommended to use a thumbnail picture 58 | return Utils.getWidget(bean.thumbPath!); 59 | }, 60 | initBuilder: (BuildContext context) { 61 | return InkWell( 62 | onTap: () { 63 | _loadAssets(context); 64 | }, 65 | child: Container( 66 | color: Color(0XFFCCCCCC), 67 | child: Center( 68 | child: Icon( 69 | Icons.add, 70 | ), 71 | ), 72 | ), 73 | ); 74 | }, 75 | onDragListener: (MotionEvent event, double itemWidth) { 76 | switch (event.action) { 77 | case MotionEvent.actionDown: 78 | moveAction = event.action!; 79 | setState(() {}); 80 | break; 81 | case MotionEvent.actionMove: 82 | double x = event.globalX! + itemWidth; 83 | double y = event.globalY! + itemWidth; 84 | double maxX = MediaQuery.of(context).size.width - 1 * 100; 85 | double maxY = MediaQuery.of(context).size.height - 1 * 100; 86 | print('Sky24n maxX: $maxX, maxY: $maxY, x: $x, y: $y'); 87 | if (_canDelete && (x < maxX || y < maxY)) { 88 | setState(() { 89 | _canDelete = false; 90 | }); 91 | } else if (!_canDelete && x > maxX && y > maxY) { 92 | setState(() { 93 | _canDelete = true; 94 | }); 95 | } 96 | break; 97 | case MotionEvent.actionUp: 98 | moveAction = event.action!; 99 | if (_canDelete) { 100 | setState(() { 101 | _canDelete = false; 102 | }); 103 | return true; 104 | } else { 105 | setState(() {}); 106 | } 107 | break; 108 | } 109 | return false; 110 | }, 111 | ), 112 | ], 113 | ), 114 | floatingActionButton: moveAction == MotionEvent.actionUp 115 | ? null 116 | : FloatingActionButton( 117 | onPressed: () {}, 118 | child: Icon(_canDelete ? Icons.delete : Icons.delete_outline), 119 | ), 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:nine_grid_view/nine_grid_view.dart'; 7 | import 'package:nine_grid_view_example/drag_sort_page.dart'; 8 | import 'package:nine_grid_view_example/models.dart'; 9 | import 'package:nine_grid_view_example/single_picture_page.dart'; 10 | import 'package:nine_grid_view_example/utils.dart'; 11 | 12 | void main() { 13 | runApp(MyApp()); 14 | if (Platform.isAndroid) { 15 | SystemChrome.setSystemUIOverlayStyle( 16 | SystemUiOverlayStyle(statusBarColor: Colors.transparent)); 17 | } 18 | } 19 | 20 | class MyApp extends StatefulWidget { 21 | @override 22 | _MyAppState createState() => _MyAppState(); 23 | } 24 | 25 | class _MyAppState extends State { 26 | @override 27 | void initState() { 28 | super.initState(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return MaterialApp( 34 | home: HomePage(), 35 | ); 36 | } 37 | } 38 | 39 | class HomePage extends StatefulWidget { 40 | @override 41 | _HomePageState createState() => _HomePageState(); 42 | } 43 | 44 | class _HomePageState extends State { 45 | String _title = 'QQ Group'; 46 | 47 | NineGridType _gridType = NineGridType.qqGp; 48 | 49 | List imageList = []; 50 | 51 | @override 52 | void initState() { 53 | super.initState(); 54 | imageList = Utils.getTestData(); 55 | } 56 | 57 | Widget _buildItem(BuildContext context, int _index) { 58 | int itemCount = _index % 9 + 1; 59 | if (_gridType == NineGridType.normal || 60 | _gridType == NineGridType.weiBo || 61 | _gridType == NineGridType.weChat) { 62 | return Container( 63 | decoration: BoxDecoration( 64 | border: Border( 65 | bottom: BorderSide(width: 0.33, color: Color(0xffe5e5e5)))), 66 | padding: EdgeInsets.all(0), 67 | child: NineGridView( 68 | margin: EdgeInsets.all(12), 69 | padding: EdgeInsets.all(5), 70 | space: 5, 71 | type: _gridType, 72 | color: Color(0XFFE5E5E5), 73 | itemCount: itemCount, 74 | itemBuilder: (BuildContext context, int index) { 75 | ImageBean bean = imageList[index]; 76 | return Utils.getWidget(bean.middlePath!); 77 | }, 78 | ), 79 | ); 80 | } 81 | itemCount = (_index % (_gridType == NineGridType.dingTalkGp ? 4 : 9) + 1); 82 | Decoration decoration = BoxDecoration( 83 | color: Color(0XFFE5E5E5), 84 | shape: BoxShape.rectangle, 85 | borderRadius: BorderRadius.all(Radius.circular(4)), 86 | ); 87 | Widget header = NineGridView( 88 | width: 120, 89 | height: 120, 90 | padding: EdgeInsets.all(2), 91 | alignment: Alignment.center, 92 | space: 3, 93 | type: _gridType, 94 | decoration: _gridType == NineGridType.weChatGp ? decoration : null, 95 | itemCount: itemCount, 96 | itemBuilder: (BuildContext context, int index) { 97 | ImageBean bean = imageList[index]; 98 | return Utils.getWidget(bean.middlePath!); 99 | }, 100 | ); 101 | 102 | return InkWell( 103 | onTap: () {}, 104 | child: Container( 105 | padding: EdgeInsets.only(left: 12, top: 12, right: 12, bottom: 12), 106 | decoration: BoxDecoration( 107 | border: Border( 108 | bottom: BorderSide(width: 0.33, color: Color(0xffe5e5e5)))), 109 | child: Row( 110 | children: [header], 111 | ), 112 | ), 113 | ); 114 | } 115 | 116 | Widget _buildGroup(BuildContext context) { 117 | Decoration decoration = BoxDecoration( 118 | color: Color(0XFFE5E5E5), 119 | shape: BoxShape.rectangle, 120 | borderRadius: BorderRadius.all(Radius.circular(4)), 121 | ); 122 | int total = 1; 123 | switch (_gridType) { 124 | case NineGridType.qqGp: 125 | total = 5; 126 | break; 127 | case NineGridType.weChatGp: 128 | total = 9; 129 | break; 130 | case NineGridType.dingTalkGp: 131 | total = 4; 132 | break; 133 | } 134 | List children = []; 135 | for (int i = 0; i < 9; i++) { 136 | children.add(NineGridView( 137 | width: (MediaQuery.of(context).size.width - 60) / 3, 138 | height: (MediaQuery.of(context).size.width - 60) / 3, 139 | padding: EdgeInsets.all(2), 140 | margin: EdgeInsets.all(5), 141 | alignment: Alignment.center, 142 | space: 3, 143 | //arcAngle: 60, 144 | type: _gridType, 145 | decoration: _gridType == NineGridType.dingTalkGp ? null : decoration, 146 | itemCount: i % total + 1, 147 | itemBuilder: (BuildContext context, int index) { 148 | ImageBean bean = imageList[index]; 149 | return Utils.getWidget(bean.middlePath!); 150 | }, 151 | )); 152 | } 153 | return Wrap( 154 | alignment: WrapAlignment.center, 155 | children: children, 156 | ); 157 | } 158 | 159 | void _onPopSelected(NineGridType value) { 160 | print('Sky24n _onPopSelected...... $value'); 161 | if (_gridType != value) { 162 | _gridType = value; 163 | switch (value) { 164 | case NineGridType.qqGp: 165 | _title = 'QQ Group'; 166 | break; 167 | case NineGridType.weChatGp: 168 | _title = 'WeChat Group'; 169 | break; 170 | case NineGridType.dingTalkGp: 171 | _title = 'DingTalk Group'; 172 | break; 173 | case NineGridType.weChat: 174 | _title = 'WeChat'; 175 | break; 176 | case NineGridType.weiBo: 177 | _title = 'WeiBo Intl'; 178 | break; 179 | case NineGridType.normal: 180 | _title = 'Normal'; 181 | break; 182 | } 183 | setState(() {}); 184 | } 185 | } 186 | 187 | @override 188 | Widget build(BuildContext context) { 189 | return Scaffold( 190 | appBar: AppBar( 191 | title: Text('$_title'), 192 | centerTitle: true, 193 | actions: [ 194 | PopupMenuButton( 195 | icon: Icon(Icons.settings), 196 | padding: const EdgeInsets.all(0.0), 197 | onSelected: _onPopSelected, 198 | itemBuilder: (BuildContext context) => 199 | >[ 200 | PopupMenuItem( 201 | value: NineGridType.qqGp, 202 | child: ListTile( 203 | contentPadding: EdgeInsets.all(0.0), 204 | dense: false, 205 | title: Text( 206 | 'QQ Group', 207 | ))), 208 | PopupMenuItem( 209 | value: NineGridType.weChatGp, 210 | child: ListTile( 211 | contentPadding: EdgeInsets.all(0.0), 212 | dense: false, 213 | title: Text( 214 | 'WeChat Group', 215 | ))), 216 | PopupMenuItem( 217 | value: NineGridType.dingTalkGp, 218 | child: ListTile( 219 | contentPadding: EdgeInsets.all(0.0), 220 | dense: false, 221 | title: Text( 222 | 'DingTalk Group', 223 | ))), 224 | PopupMenuItem( 225 | value: NineGridType.weChat, 226 | child: ListTile( 227 | contentPadding: EdgeInsets.all(0.0), 228 | dense: false, 229 | title: Text( 230 | 'WeChat', 231 | ))), 232 | PopupMenuItem( 233 | value: NineGridType.weiBo, 234 | child: ListTile( 235 | contentPadding: EdgeInsets.all(0.0), 236 | dense: false, 237 | title: Text( 238 | 'WeiBo', 239 | ))), 240 | PopupMenuItem( 241 | value: NineGridType.normal, 242 | child: ListTile( 243 | contentPadding: EdgeInsets.all(0.0), 244 | dense: false, 245 | title: Text( 246 | 'Normal', 247 | ))), 248 | PopupMenuItem( 249 | value: NineGridType.normal, 250 | child: ListTile( 251 | contentPadding: EdgeInsets.all(0.0), 252 | dense: false, 253 | title: Text( 254 | 'Single Picture', 255 | ), 256 | onTap: () { 257 | Utils.pushPage(context, SinglePicturePage()); 258 | }, 259 | )), 260 | ]), 261 | ], 262 | ), 263 | body: (_gridType == NineGridType.qqGp || 264 | _gridType == NineGridType.weChatGp || 265 | _gridType == NineGridType.dingTalkGp) 266 | ? ListView( 267 | physics: AlwaysScrollableScrollPhysics( 268 | parent: BouncingScrollPhysics()), 269 | children: [ 270 | SizedBox(height: 15), 271 | _buildGroup(context), 272 | Offstage( 273 | offstage: _gridType != NineGridType.qqGp, 274 | child: QQGroup(), 275 | ), 276 | ], 277 | ) 278 | : ListView.builder( 279 | physics: BouncingScrollPhysics(), 280 | itemCount: 9, 281 | padding: EdgeInsets.all(0), 282 | itemBuilder: (BuildContext context, int index) { 283 | return _buildItem(context, index); 284 | }), 285 | floatingActionButton: FloatingActionButton( 286 | onPressed: () { 287 | Utils.pushPage(context, DragSortPage()); 288 | }, 289 | child: Icon(Icons.camera_alt), 290 | ), 291 | ); 292 | } 293 | } 294 | 295 | class QQGroup extends StatefulWidget { 296 | @override 297 | _QQGroupState createState() => _QQGroupState(); 298 | } 299 | 300 | class _QQGroupState extends State with TickerProviderStateMixin { 301 | late AnimationController _controller; 302 | List imageList = []; 303 | 304 | @override 305 | void initState() { 306 | super.initState(); 307 | imageList = Utils.getTestData(); 308 | _controller = AnimationController( 309 | duration: Duration(milliseconds: 2000), vsync: this); 310 | _controller.addListener(() { 311 | setState(() {}); 312 | }); 313 | _controller.repeat(reverse: true); 314 | } 315 | 316 | @override 317 | void dispose() { 318 | _controller.dispose(); 319 | super.dispose(); 320 | } 321 | 322 | @override 323 | Widget build(BuildContext context) { 324 | return Center( 325 | child: NineGridView( 326 | width: 200, 327 | height: 200, 328 | arcAngle: (_controller.value * 180).round().toDouble(), 329 | type: NineGridType.qqGp, 330 | itemCount: 5, 331 | itemBuilder: (BuildContext context, int index) { 332 | ImageBean bean = imageList[index]; 333 | return Utils.getWidget(bean.middlePath!); 334 | }, 335 | ), 336 | ); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /example/lib/models.dart: -------------------------------------------------------------------------------- 1 | import 'package:nine_grid_view/nine_grid_view.dart'; 2 | 3 | class ImageBean extends DragBean { 4 | ImageBean({ 5 | this.originPath, 6 | this.middlePath, 7 | this.thumbPath, 8 | this.originalWidth, 9 | this.originalHeight, 10 | }); 11 | 12 | /// origin picture file path. 13 | String? originPath; 14 | 15 | /// middle picture file path. 16 | String? middlePath; 17 | 18 | /// thumb picture file path. 19 | /// It is recommended to use a thumbnail picture,because the original picture is too large, 20 | /// it may cause repeated loading and cause flashing. 21 | String? thumbPath; 22 | 23 | /// original image width. 24 | int? originalWidth; 25 | 26 | /// original image height. 27 | int? originalHeight; 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/single_picture_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:nine_grid_view/nine_grid_view.dart'; 3 | 4 | import 'models.dart'; 5 | import 'utils.dart'; 6 | 7 | class SinglePicturePage extends StatefulWidget { 8 | @override 9 | _SinglePicturePageState createState() => _SinglePicturePageState(); 10 | } 11 | 12 | class _SinglePicturePageState extends State { 13 | List imageList = []; 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | imageList = Utils.getTestData(); 19 | } 20 | 21 | Widget _buildItem(BuildContext context, int _index) { 22 | int itemCount = 1; 23 | 24 | ImageBean imageBean = imageList[_index]; 25 | String url = imageBean.middlePath!; 26 | 27 | int? originalWidth; 28 | int? originalHeight; 29 | 30 | Image? bigImage; 31 | String? bigImageUrl; 32 | 33 | // Single picture display big picture. 34 | if (itemCount == 1) { 35 | // ImageBean imageBean = imageList[0]; 36 | originalWidth = imageBean.originalWidth; 37 | originalHeight = imageBean.originalHeight; 38 | bigImage = Utils.getBigImage(imageBean.middlePath); 39 | bigImageUrl = imageBean.middlePath; 40 | } 41 | 42 | return Column( 43 | mainAxisSize: MainAxisSize.min, 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | children: [ 46 | NineGridView( 47 | margin: EdgeInsets.all(12), 48 | padding: EdgeInsets.all(5), 49 | space: 5, 50 | bigImageWidth: originalWidth, 51 | bigImageHeight: originalHeight, 52 | bigImage: bigImage, 53 | bigImageUrl: bigImageUrl, 54 | itemCount: itemCount, 55 | itemBuilder: (BuildContext context, int index) { 56 | return Utils.getWidget(url); 57 | }, 58 | ) 59 | ], 60 | ); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: AppBar( 67 | title: const Text('Single Picture'), 68 | centerTitle: true, 69 | ), 70 | body: ListView.builder( 71 | physics: BouncingScrollPhysics(), 72 | itemCount: imageList.length, 73 | padding: EdgeInsets.all(0), 74 | itemBuilder: (BuildContext context, int index) { 75 | return _buildItem(context, index); 76 | }), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/lib/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'models.dart'; 5 | 6 | class Utils { 7 | static String getImgPath(String name, {String format: 'png'}) { 8 | return 'assets/images/$name.$format'; 9 | } 10 | 11 | static Future pushPage( 12 | BuildContext context, Widget page) { 13 | return Navigator.push( 14 | context, 15 | CupertinoPageRoute(builder: (ctx) => page), 16 | ); 17 | } 18 | 19 | static void showSnackBar(BuildContext context, String msg) { 20 | ScaffoldMessenger.of(context).showSnackBar( 21 | SnackBar( 22 | content: Text(msg), 23 | duration: Duration(seconds: 2), 24 | ), 25 | ); 26 | } 27 | 28 | static Widget getWidget(String url) { 29 | if (url.startsWith('http')) { 30 | //return CachedNetworkImage(imageUrl: url, fit: BoxFit.cover); 31 | return Image.network(url, fit: BoxFit.cover); 32 | } 33 | if (url.endsWith('.png')) { 34 | return Image.asset(url, 35 | fit: BoxFit.cover, package: 'flutter_gallery_assets'); 36 | } 37 | //return Image.file(File(url), fit: BoxFit.cover); 38 | return Image.asset(getImgPath(url), fit: BoxFit.cover); 39 | } 40 | 41 | static Image? getBigImage(String? url) { 42 | if (url == null || url.isEmpty) return null; 43 | if (url.startsWith('http')) { 44 | //return Image(image: CachedNetworkImageProvider(url), fit: BoxFit.cover); 45 | return Image.network(url, fit: BoxFit.cover); 46 | } 47 | if (url.endsWith('.png')) { 48 | return Image.asset(url, 49 | fit: BoxFit.cover, package: 'flutter_gallery_assets'); 50 | } 51 | //return Image.file(File(url), fit: BoxFit.cover); 52 | return Image.asset(getImgPath(url), fit: BoxFit.cover); 53 | } 54 | 55 | static List getTestData() { 56 | List urlList = [ 57 | 'ali_connors', 58 | 'people/square/ali.png', 59 | 'people/ali_landscape.png', 60 | 'people/square/peter.png', 61 | 'people/square/sandra.png', 62 | 'people/square/trevor.png', 63 | 'places/india_tanjore_bronze_works.png', 64 | 'places/india_tanjore_market_merchant.png', 65 | 'places/india_tanjore_thanjavur_temple_carvings.png', 66 | ]; 67 | List list = []; 68 | for (int i = 0; i < urlList.length; i++) { 69 | String url = urlList[i]; 70 | list.add(ImageBean( 71 | originPath: url, 72 | middlePath: url, 73 | thumbPath: url, 74 | originalWidth: i == 0 ? 264 : null, 75 | originalHeight: i == 0 ? 258 : null, 76 | )); 77 | } 78 | return list; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/pkgget: -------------------------------------------------------------------------------- 1 | export PUB_HOSTED_URL=https://pub.flutter-io.cn 2 | export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn 3 | flutter packages get -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.1.3" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_gallery_assets: 66 | dependency: "direct main" 67 | description: 68 | name: flutter_gallery_assets 69 | url: "https://pub.flutter-io.cn" 70 | source: hosted 71 | version: "0.2.1" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.12.10" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.3.0" 91 | nine_grid_view: 92 | dependency: "direct main" 93 | description: 94 | path: ".." 95 | relative: true 96 | source: path 97 | version: "2.0.0" 98 | path: 99 | dependency: transitive 100 | description: 101 | name: path 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.8.0" 105 | sky_engine: 106 | dependency: transitive 107 | description: flutter 108 | source: sdk 109 | version: "0.0.99" 110 | source_span: 111 | dependency: transitive 112 | description: 113 | name: source_span 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.8.0" 117 | stack_trace: 118 | dependency: transitive 119 | description: 120 | name: stack_trace 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.10.0" 124 | stream_channel: 125 | dependency: transitive 126 | description: 127 | name: stream_channel 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "2.1.0" 131 | string_scanner: 132 | dependency: transitive 133 | description: 134 | name: string_scanner 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.1.0" 138 | term_glyph: 139 | dependency: transitive 140 | description: 141 | name: term_glyph 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.2.0" 145 | test_api: 146 | dependency: transitive 147 | description: 148 | name: test_api 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "0.2.19" 152 | typed_data: 153 | dependency: transitive 154 | description: 155 | name: typed_data 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.3.0" 159 | vector_math: 160 | dependency: transitive 161 | description: 162 | name: vector_math 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "2.1.0" 166 | sdks: 167 | dart: ">=2.12.0 <3.0.0" 168 | flutter: ">=0.1.0" 169 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: nine_grid_view_example 2 | description: A new Flutter application. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^0.1.3 31 | flutter_gallery_assets: ^0.2.1 32 | 33 | # nine_grid_view: ^0.2.1 34 | nine_grid_view: 35 | path: ../ 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | 41 | # For information on the generic Dart part of this file, see the 42 | # following page: https://dart.dev/tools/pub/pubspec 43 | 44 | # The following section is specific to Flutter. 45 | flutter: 46 | 47 | # The following line ensures that the Material Icons font is 48 | # included with your application, so that you can use the icons in 49 | # the material Icons class. 50 | uses-material-design: true 51 | 52 | assets: 53 | - assets/images/ 54 | - packages/flutter_gallery_assets/people/ali_landscape.png 55 | - packages/flutter_gallery_assets/people/square/ali.png 56 | - packages/flutter_gallery_assets/people/square/peter.png 57 | - packages/flutter_gallery_assets/people/square/sandra.png 58 | - packages/flutter_gallery_assets/people/square/stella.png 59 | - packages/flutter_gallery_assets/people/square/trevor.png 60 | - packages/flutter_gallery_assets/places/india_tanjore_bronze_works.png 61 | - packages/flutter_gallery_assets/places/india_tanjore_market_merchant.png 62 | - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple_carvings.png 63 | 64 | # To add assets to your application, add an assets section, like this: 65 | # assets: 66 | # - images/a_dot_burr.jpeg 67 | # - images/a_dot_ham.jpeg 68 | 69 | # An image asset can refer to one or more resolution-specific "variants", see 70 | # https://flutter.dev/assets-and-images/#resolution-aware. 71 | 72 | # For details regarding adding assets from package dependencies, see 73 | # https://flutter.dev/assets-and-images/#from-packages 74 | 75 | # To add custom fonts to your application, add a fonts section here, 76 | # in this "flutter" section. Each entry in this list should have a 77 | # "family" key with the font family name, and a "fonts" key with a 78 | # list giving the asset and other descriptors for the font. For 79 | # example: 80 | # fonts: 81 | # - family: Schyler 82 | # fonts: 83 | # - asset: fonts/Schyler-Regular.ttf 84 | # - asset: fonts/Schyler-Italic.ttf 85 | # style: italic 86 | # - family: Trajan Pro 87 | # fonts: 88 | # - asset: fonts/TrajanPro.ttf 89 | # - asset: fonts/TrajanPro_Bold.ttf 90 | # weight: 700 91 | # 92 | # For details regarding fonts from package dependencies, 93 | # see https://flutter.dev/custom-fonts/#from-packages 94 | -------------------------------------------------------------------------------- /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:nine_grid_view_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/nine_grid_view.dart: -------------------------------------------------------------------------------- 1 | library nine_grid_view; 2 | 3 | export 'src/nine_grid_view.dart'; 4 | export 'src/drag_sort_view.dart'; 5 | -------------------------------------------------------------------------------- /lib/src/drag_sort_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:math' as math; 3 | 4 | /** 5 | * @Author: Sky24n 6 | * @GitHub: https://github.com/Sky24n 7 | * @Description: DragSortView. 8 | * @Date: 2020/06/16 9 | */ 10 | 11 | /// DragBean 12 | class DragBean { 13 | DragBean({ 14 | this.index = 0, 15 | this.selected = false, 16 | }); 17 | 18 | int index; 19 | bool selected; 20 | } 21 | 22 | /// on drag listener. 23 | /// if return true, delete drag index child image. default return false. 24 | typedef OnDragListener = bool Function(MotionEvent event, double itemWidth); 25 | 26 | /// Object used to report movement events. 27 | class MotionEvent { 28 | static const int actionDown = 0; 29 | static const int actionUp = 1; 30 | static const int actionMove = 2; 31 | 32 | /// action. 33 | int? action; 34 | 35 | /// drag index. 36 | int? dragIndex; 37 | 38 | /// the global x coordinate system in logical pixels. 39 | double? globalX; 40 | 41 | /// the global y coordinate system in logical pixels. 42 | double? globalY; 43 | } 44 | 45 | /// Drag sort view. 46 | /// Similar to the dynamic nine grid of weiBo / weChat publishing. 47 | /// It supports pressing the zoom effect, dragging and sorting, and dragging to the specified location to delete. 48 | class DragSortView extends StatefulWidget { 49 | /// create DragSortView. 50 | /// It is recommended to use a thumbnail picture,because the original picture is too large, it may cause repeated loading and cause flashing. 51 | /// 建议使用略微缩图,因为原图太大可能会引起重复加载导致闪动. 52 | DragSortView( 53 | this.data, { 54 | Key? key, 55 | this.width, 56 | this.space = 5, 57 | this.padding = EdgeInsets.zero, 58 | this.margin = EdgeInsets.zero, 59 | required this.itemBuilder, 60 | required this.initBuilder, 61 | this.onDragListener, 62 | }) : super(key: key); 63 | 64 | /// picture data. 65 | final List data; 66 | 67 | /// View width. 68 | final double? width; 69 | 70 | /// The number of logical pixels between each child. 71 | final double space; 72 | 73 | /// View padding. 74 | final EdgeInsets padding; 75 | 76 | /// View margin. 77 | final EdgeInsets margin; 78 | 79 | /// Called to build children for the view. 80 | final IndexedWidgetBuilder itemBuilder; 81 | 82 | /// Called to build init children for the view. 83 | final WidgetBuilder initBuilder; 84 | 85 | /// On drag listener. 86 | final OnDragListener? onDragListener; 87 | 88 | @override 89 | State createState() { 90 | return DragSortViewState(); 91 | } 92 | } 93 | 94 | class DragSortViewState extends State 95 | with TickerProviderStateMixin { 96 | /// child transposition anim. 97 | late AnimationController _controller; 98 | 99 | /// child zoom anim. 100 | late AnimationController _zoomController; 101 | 102 | /// child float anim. 103 | late AnimationController _floatController; 104 | 105 | /// child positions. 106 | List _positions = []; 107 | 108 | /// cache data. 109 | List _cacheData = []; 110 | 111 | /// drag child index. 112 | int _dragIndex = -1; 113 | 114 | /// drag child bean. 115 | DragBean? _dragBean; 116 | 117 | /// MotionEvent 118 | MotionEvent _motionEvent = MotionEvent(); 119 | 120 | /// overlay entry. 121 | static OverlayEntry? _overlayEntry; 122 | 123 | /// child count. 124 | int _itemCount = 0; 125 | 126 | /// child width. 127 | double _itemWidth = 0; 128 | 129 | Offset _downGlobalPos = Offset.zero; 130 | double _downLeft = 0; 131 | double _downTop = 0; 132 | double _floatLeft = 0; 133 | double _floatTop = 0; 134 | double _fromTop = 0; 135 | double _fromLeft = 0; 136 | double _toTop = 0; 137 | double _toLeft = 0; 138 | 139 | @override 140 | void initState() { 141 | super.initState(); 142 | _controller = 143 | AnimationController(duration: Duration(milliseconds: 200), vsync: this); 144 | _zoomController = 145 | AnimationController(duration: Duration(milliseconds: 100), vsync: this); 146 | _floatController = 147 | AnimationController(duration: Duration(milliseconds: 200), vsync: this); 148 | _controller.addListener(() { 149 | setState(() {}); 150 | }); 151 | _zoomController.addListener(() { 152 | _updateOverlay(); 153 | }); 154 | _floatController.addListener(() { 155 | _floatLeft = 156 | _toLeft + (_fromLeft - _toLeft) * (1 - _floatController.value); 157 | _floatTop = _toTop + (_fromTop - _toTop) * (1 - _floatController.value); 158 | _updateOverlay(); 159 | }); 160 | _floatController.addStatusListener((AnimationStatus status) { 161 | if (status == AnimationStatus.completed) { 162 | _clearAll(); 163 | } 164 | }); 165 | } 166 | 167 | @override 168 | void dispose() { 169 | _controller.dispose(); 170 | _zoomController.dispose(); 171 | _floatController.dispose(); 172 | _removeOverlay(); 173 | super.dispose(); 174 | } 175 | 176 | /// init child size and positions. 177 | void _init(BuildContext context, EdgeInsets padding, EdgeInsets margin) { 178 | double space = widget.space; 179 | double width = 180 | widget.width ?? (MediaQuery.of(context).size.width - margin.horizontal); 181 | width = width - padding.horizontal; 182 | _itemWidth = (width - space * 2) / 3; 183 | _positions.clear(); 184 | for (int i = 0; i < 9; i++) { 185 | double left = (space + _itemWidth) * (i % 3); 186 | double top = (space + _itemWidth) * (i ~/ 3); 187 | _positions.add(Rect.fromLTWH(left, top, _itemWidth, _itemWidth)); 188 | } 189 | } 190 | 191 | RenderBox? _getRenderBox(BuildContext context) { 192 | RenderObject? renderObject = context.findRenderObject(); 193 | RenderBox? box; 194 | if (renderObject != null) { 195 | box = renderObject as RenderBox; 196 | } 197 | return box; 198 | } 199 | 200 | /// get widget global coordinate system in logical pixels. 201 | Offset _getWidgetLocalToGlobal(BuildContext context) { 202 | RenderBox? box = _getRenderBox(context); 203 | return box == null ? Offset.zero : box.localToGlobal(Offset.zero); 204 | } 205 | 206 | /// get drag index. 207 | int _getDragIndex(Offset offset) { 208 | for (int i = 0; i < _itemCount; i++) { 209 | if (_positions[i].contains(offset)) { 210 | return i; 211 | } 212 | } 213 | return -1; 214 | } 215 | 216 | /// init child index. 217 | void _initIndex() { 218 | for (int i = 0; i < _itemCount; i++) { 219 | widget.data[i].index = i; 220 | } 221 | _cacheData.clear(); 222 | _cacheData.addAll(widget.data); 223 | } 224 | 225 | /// add overlay. 226 | void _addOverlay(BuildContext context, Widget overlay) { 227 | OverlayState? overlayState = Overlay.of(context); 228 | if (overlayState == null) return; 229 | double space = widget.space; 230 | if (_overlayEntry == null) { 231 | _overlayEntry = OverlayEntry(builder: (BuildContext context) { 232 | return Positioned( 233 | left: _floatLeft - space * _zoomController.value, 234 | top: _floatTop - space * _zoomController.value, 235 | child: Material( 236 | child: Container( 237 | width: _itemWidth + space * _zoomController.value * 2, 238 | height: _itemWidth + space * _zoomController.value * 2, 239 | child: overlay, 240 | ), 241 | )); 242 | }); 243 | overlayState.insert(_overlayEntry!); 244 | } else { 245 | //重新绘制UI,类似setState 246 | _overlayEntry?.markNeedsBuild(); 247 | } 248 | _zoomController.reset(); 249 | _zoomController.forward(); 250 | } 251 | 252 | /// update overlay. 253 | void _updateOverlay() { 254 | _overlayEntry?.markNeedsBuild(); 255 | } 256 | 257 | /// remove overlay. 258 | void _removeOverlay() { 259 | _overlayEntry?.remove(); 260 | _overlayEntry = null; 261 | } 262 | 263 | /// get next child index. 264 | int _getNextIndex(Rect curRect, Rect origin) { 265 | if (_itemCount == 1) return 0; 266 | bool outside = true; 267 | for (int i = 0; i < _itemCount; i++) { 268 | Rect rect = _positions[i]; 269 | bool overlaps = rect.overlaps(curRect); 270 | if (overlaps) { 271 | outside = false; 272 | Rect over = rect.intersect(curRect); 273 | Rect ori = origin.intersect(curRect); 274 | if (_getRectArea(over) > _itemWidth * _itemWidth / 2 || 275 | _getRectArea(over) > _getRectArea(ori)) { 276 | return i; 277 | } 278 | } 279 | } 280 | int index = -1; 281 | if (outside) { 282 | if (curRect.bottom < 0) { 283 | index = _checkIndexTop(curRect); 284 | } else if (curRect.top > _itemWidth) { 285 | index = _checkIndexBottom(curRect); 286 | } 287 | } 288 | return index; 289 | } 290 | 291 | /// get area. 292 | double _getRectArea(Rect rect) { 293 | return rect.width * rect.height; 294 | } 295 | 296 | /// check top index. 297 | int _checkIndexTop(Rect other) { 298 | int index = -1; 299 | double? area; 300 | for (int i = 0; (i < 3 && i < _itemCount); i++) { 301 | Rect rect = _positions[i]; 302 | Rect over = rect.intersect(other); 303 | double _area = _getRectArea(over); 304 | if (area == null || _area <= area) { 305 | area = _area; 306 | index = i; 307 | } 308 | } 309 | return index; 310 | } 311 | 312 | /// check bottom index. 313 | int _checkIndexBottom(Rect other) { 314 | int tagIndex = -1; 315 | double? area; 316 | for (int i = 0; (i < 3 && i < _itemCount); i++) { 317 | Rect _rect = _positions[i]; 318 | Rect over = _rect.intersect(other); 319 | double _area = _getRectArea(over); 320 | if (area == null || _area <= area) { 321 | area = _area; 322 | tagIndex = i; 323 | } 324 | } 325 | if (tagIndex != -1) { 326 | for (int i = _itemCount - 1; i >= 0; i--) { 327 | if (((i + 1) / 3).ceil() >= (((_dragIndex + 1) / 3).ceil()) && 328 | (i % 3 == tagIndex)) { 329 | return i; 330 | } 331 | } 332 | } 333 | return -1; 334 | } 335 | 336 | /// clear all. 337 | void _clearAll() { 338 | _removeOverlay(); 339 | _cacheData.clear(); 340 | int count = math.min(9, widget.data.length); 341 | for (int i = 0; i < count; i++) { 342 | widget.data[i].index = i; 343 | widget.data[i].selected = false; 344 | } 345 | setState(() {}); 346 | } 347 | 348 | /// trigger drag event. 349 | bool _triggerDragEvent(int action) { 350 | if (widget.onDragListener != null && _dragIndex != -1) { 351 | _motionEvent.dragIndex = _dragIndex; 352 | _motionEvent.action = action; 353 | _motionEvent.globalX = _floatLeft; 354 | _motionEvent.globalY = _floatTop; 355 | return widget.onDragListener!(_motionEvent, _itemWidth); 356 | } 357 | return false; 358 | } 359 | 360 | /// build child. 361 | Widget _buildChild(BuildContext context) { 362 | List children = []; 363 | if (_cacheData.isEmpty) { 364 | for (int i = 0; i < _itemCount; i++) { 365 | children.add( 366 | Positioned.fromRect( 367 | rect: _positions[i], 368 | child: widget.itemBuilder(context, i), 369 | ), 370 | ); 371 | } 372 | } else { 373 | for (int i = 0; i < _itemCount; i++) { 374 | int curIndex = widget.data[i].index; 375 | int lastIndex = _cacheData[i].index; 376 | double left = _positions[curIndex].left + 377 | (_positions[lastIndex].left - _positions[curIndex].left) * 378 | _controller.value; 379 | double top = _positions[curIndex].top + 380 | (_positions[lastIndex].top - _positions[curIndex].top) * 381 | _controller.value; 382 | children.add(Positioned( 383 | left: left, 384 | top: top, 385 | width: _itemWidth, 386 | height: _itemWidth, 387 | child: Offstage( 388 | offstage: widget.data[i].selected == true, 389 | child: widget.itemBuilder(context, i), 390 | ), 391 | )); 392 | } 393 | } 394 | 395 | if (_itemCount < 9) { 396 | children.add(Positioned.fromRect( 397 | rect: _positions[_itemCount], 398 | child: widget.initBuilder(context), 399 | )); 400 | } 401 | return Stack( 402 | children: children, 403 | ); 404 | } 405 | 406 | @override 407 | Widget build(BuildContext context) { 408 | _itemCount = math.min(9, widget.data.length); 409 | EdgeInsets padding = widget.padding; 410 | EdgeInsets margin = widget.margin; 411 | if (_itemWidth == 0) { 412 | _init(context, padding, margin); 413 | } 414 | 415 | int column = (_itemCount > 3 ? 3 : _itemCount + 1); 416 | int row = ((_itemCount + (_itemCount < 9 ? 1 : 0)) / 3).ceil(); 417 | double realWidth = 418 | _itemWidth * column + widget.space * (column - 1) + padding.horizontal; 419 | double realHeight = 420 | _itemWidth * row + widget.space * (row - 1) + padding.vertical; 421 | double left = margin.left + padding.left; 422 | double top = margin.top + padding.top; 423 | 424 | return GestureDetector( 425 | onLongPressStart: (LongPressStartDetails details) { 426 | Offset offset = _getWidgetLocalToGlobal(context); 427 | _dragIndex = _getDragIndex(details.localPosition - Offset(left, top)); 428 | if (_dragIndex == -1) return; 429 | _initIndex(); 430 | widget.data[_dragIndex].selected = true; 431 | _dragBean = widget.data[_dragIndex]; 432 | _downGlobalPos = details.globalPosition; 433 | _downLeft = left + _positions[_dragIndex].left; 434 | _downTop = top + _positions[_dragIndex].top; 435 | _toLeft = offset.dx + left + _positions[_dragIndex].left; 436 | _toTop = offset.dy + top + _positions[_dragIndex].top; 437 | _floatLeft = _toLeft; 438 | _floatTop = _toTop; 439 | Widget overlay = widget.itemBuilder(context, _dragIndex); 440 | _addOverlay(context, overlay); 441 | _triggerDragEvent(MotionEvent.actionDown); 442 | setState(() {}); 443 | }, 444 | onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) { 445 | if (_dragIndex == -1) return; 446 | _floatLeft = _toLeft + (details.globalPosition.dx - _downGlobalPos.dx); 447 | _floatTop = _toTop + (details.globalPosition.dy - _downGlobalPos.dy); 448 | 449 | double left = 450 | _downLeft + (details.globalPosition.dx - _downGlobalPos.dx); 451 | double top = _downTop + (details.globalPosition.dy - _downGlobalPos.dy); 452 | Rect cRect = Rect.fromLTWH(left, top, _itemWidth, _itemWidth); 453 | int index = _getNextIndex(cRect, _positions[_dragIndex]); 454 | if (index != -1 && _dragIndex != index) { 455 | _initIndex(); 456 | _dragIndex = index; 457 | widget.data.remove(_dragBean); 458 | widget.data.insert(_dragIndex, _dragBean!); 459 | _controller.reset(); 460 | _controller.forward(); 461 | } 462 | _updateOverlay(); 463 | _triggerDragEvent(MotionEvent.actionMove); 464 | }, 465 | onLongPressEnd: (LongPressEndDetails details) { 466 | if (_dragIndex == -1) return; 467 | _fromLeft = _toLeft + (details.globalPosition.dx - _downGlobalPos.dx); 468 | _fromTop = _toTop + (details.globalPosition.dy - _downGlobalPos.dy); 469 | Offset offset = _getWidgetLocalToGlobal(context); 470 | _toLeft = offset.dx + left + _positions[_dragIndex].left; 471 | _toTop = offset.dy + top + _positions[_dragIndex].top; 472 | }, 473 | onLongPressUp: () { 474 | _dragBean = null; 475 | bool isCatch = _triggerDragEvent(MotionEvent.actionUp); 476 | if (isCatch) { 477 | widget.data.removeAt(_dragIndex); 478 | _clearAll(); 479 | } else { 480 | _floatController.reset(); 481 | _floatController.forward(); 482 | } 483 | }, 484 | child: Container( 485 | width: realWidth, 486 | height: realHeight, 487 | margin: margin, 488 | padding: padding, 489 | child: _buildChild(context), 490 | ), 491 | ); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /lib/src/nine_grid_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'dart:async'; 4 | import 'dart:collection'; 5 | import 'dart:math' as math; 6 | 7 | /** 8 | * @Author: Sky24n 9 | * @GitHub: https://github.com/Sky24n 10 | * @Description: NineGridView. 11 | * @Date: 2020/06/16 12 | */ 13 | 14 | /// NineGridView Type. 15 | enum NineGridType { 16 | /// normal NineGridView. 17 | normal, 18 | 19 | /// like WeChat NineGridView. 20 | weChat, 21 | 22 | /// like WeiBo International NineGridView. 23 | weiBo, 24 | 25 | /// like WeChat group. 26 | weChatGp, 27 | 28 | /// like DingTalk group. 29 | dingTalkGp, 30 | 31 | /// like QQ group. 32 | qqGp, 33 | } 34 | 35 | /// big images size cache map. 36 | Map ngvBigImageSizeMap = HashMap(); 37 | 38 | /// NineGridView. 39 | /// like WeChat, WeiBo International, WeChat group, DingTalk group, QQ group. 40 | /// 41 | /// Another [NineGridView](https://github.com/flutterchina/flukit) in [flukit](https://github.com/flutterchina/flukit) UI Kit,using GridView implementation。 42 | class NineGridView extends StatefulWidget { 43 | /// create NineGridView. 44 | /// If you want to show a single big picture. 45 | /// It is recommended to use a medium-quality picture, because the original picture is too large and takes time to load. 46 | /// 单张大图建议使用中等质量图片,因为原图太大加载耗时。 47 | /// you need input (bigImageWidth + bigImageHeight) or (bigImage + bigImageUrl). 48 | NineGridView({ 49 | Key? key, 50 | this.width, 51 | this.height, 52 | this.space = 3, 53 | this.arcAngle = 0, 54 | this.initIndex = 1, 55 | this.padding = EdgeInsets.zero, 56 | this.margin = EdgeInsets.zero, 57 | this.alignment, 58 | this.color, 59 | this.decoration, 60 | this.type = NineGridType.weChat, 61 | required this.itemCount, 62 | required this.itemBuilder, 63 | this.bigImageWidth, 64 | this.bigImageHeight, 65 | this.bigImage, 66 | this.bigImageUrl, 67 | }) : super(key: key); 68 | 69 | /// View width. 70 | final double? width; 71 | 72 | /// View height. 73 | final double? height; 74 | 75 | /// The number of logical pixels between each child. 76 | final double space; 77 | 78 | /// QQ group arc angle (0 ~ 180). 79 | final double arcAngle; 80 | 81 | /// QQ group init index (0 or 1). def 1. 82 | final int initIndex; 83 | 84 | /// View padding. 85 | final EdgeInsets padding; 86 | 87 | /// View margin. 88 | final EdgeInsets margin; 89 | 90 | /// Align the [child] within the container. 91 | final AlignmentGeometry? alignment; 92 | 93 | /// The color to paint behind the [child]. 94 | final Color? color; 95 | 96 | /// The decoration to paint behind the [child]. 97 | final Decoration? decoration; 98 | 99 | /// NineGridView type. 100 | final NineGridType type; 101 | 102 | /// The total number of children this delegate can provide. 103 | final int itemCount; 104 | 105 | /// Called to build children for the view. 106 | final IndexedWidgetBuilder itemBuilder; 107 | 108 | /// Single big picture width. 109 | final int? bigImageWidth; 110 | 111 | /// Single big picture height. 112 | final int? bigImageHeight; 113 | 114 | /// It is recommended to use a medium-quality picture, because the original picture is too large and takes time to load. 115 | /// 单张大图建议使用中等质量图片,因为原图太大加载耗时。 116 | /// Single big picture Image. 117 | final Image? bigImage; 118 | 119 | /// Single big picture url. 120 | final String? bigImageUrl; 121 | 122 | @override 123 | State createState() { 124 | return _NineGridViewState(); 125 | } 126 | } 127 | 128 | /// _NineGridViewState. 129 | class _NineGridViewState extends State { 130 | /// init view size. 131 | Rect _initSize(BuildContext context) { 132 | int itemCount = math.min(9, widget.itemCount); 133 | EdgeInsets padding = widget.padding; 134 | if (itemCount == 0) { 135 | return Rect.fromLTRB(0, 0, padding.horizontal, padding.vertical); 136 | } 137 | double width = widget.width ?? 138 | (MediaQuery.of(context).size.width - widget.margin.horizontal); 139 | width = width - padding.horizontal; 140 | double space = widget.space; 141 | double itemW; 142 | if (widget.type == NineGridType.weiBo && 143 | (itemCount == 1 || itemCount == 2 || itemCount == 4)) { 144 | itemW = (width - space) / 2; 145 | } else { 146 | itemW = (width - space * 2) / 3; 147 | } 148 | bool fourGrid = (itemCount == 4 && widget.type != NineGridType.normal); 149 | int column = fourGrid ? 2 : math.min(3, itemCount); 150 | int row = fourGrid ? 2 : (itemCount / 3).ceil(); 151 | double realWidth = 152 | itemW * column + space * (column - 1) + padding.horizontal; 153 | double realHeight = itemW * row + space * (row - 1) + padding.vertical; 154 | return Rect.fromLTRB(itemW, 0, realWidth, realHeight); 155 | } 156 | 157 | /// build nine grid view. 158 | Widget _buildChild(BuildContext context, double itemW) { 159 | int itemCount = math.min(9, widget.itemCount); 160 | double space = widget.space; 161 | int column = (itemCount == 4 && widget.type != NineGridType.normal) ? 2 : 3; 162 | List list = []; 163 | for (int i = 0; i < itemCount; i++) { 164 | list.add(Positioned( 165 | top: (space + itemW) * (i ~/ column), 166 | left: (space + itemW) * (i % column), 167 | child: SizedBox( 168 | width: itemW, 169 | height: itemW, 170 | child: widget.itemBuilder(context, i), 171 | ))); 172 | } 173 | return Stack( 174 | children: list, 175 | ); 176 | } 177 | 178 | /// build one child. 179 | Widget? _buildOneChild(BuildContext context) { 180 | double? bigImgWidth = widget.bigImageWidth?.toDouble(); 181 | double? bigImgHeight = widget.bigImageHeight?.toDouble(); 182 | if (!_isZero(bigImgWidth) && !_isZero(bigImgHeight)) { 183 | return _getOneChild(context, bigImgWidth!, bigImgHeight!); 184 | } else if (widget.bigImage != null) { 185 | String bigImageUrl = widget.bigImageUrl!; 186 | Rect? bigImgRect = ngvBigImageSizeMap[bigImageUrl]; 187 | bigImgWidth = bigImgRect?.width; 188 | bigImgHeight = bigImgRect?.height; 189 | if (!_isZero(bigImgWidth) && !_isZero(bigImgHeight)) { 190 | return _getOneChild(context, bigImgWidth!, bigImgHeight!); 191 | } else { 192 | _ImageUtil().getImageSize(widget.bigImage)?.then((rect) { 193 | ngvBigImageSizeMap[bigImageUrl] = rect; 194 | if (!mounted) return; 195 | setState(() {}); 196 | }).catchError((e) {}); 197 | } 198 | } 199 | return null; 200 | } 201 | 202 | /// get one child. 203 | Widget _getOneChild(BuildContext context, double width, double height) { 204 | Rect rect = _getBigImgSize(width, height); 205 | return SizedBox( 206 | width: rect.width, 207 | height: rect.height, 208 | child: widget.itemBuilder(context, 0), 209 | ); 210 | } 211 | 212 | /// build weChat group. 213 | Widget _buildWeChatGroup(BuildContext context) { 214 | int itemCount = math.min(9, widget.itemCount); 215 | double width = widget.width! - widget.padding.horizontal; 216 | double space = widget.space; 217 | double itemW; 218 | 219 | int column = itemCount < 5 ? 2 : 3; 220 | int row = 0; 221 | if (itemCount == 1) { 222 | row = 1; 223 | itemW = width; 224 | } else if (itemCount < 5) { 225 | row = itemCount == 2 ? 1 : 2; 226 | itemW = (width - space) / 2; 227 | } else if (itemCount < 7) { 228 | row = 2; 229 | itemW = (width - space * 2) / 3; 230 | } else { 231 | row = 3; 232 | itemW = (width - space * 2) / 3; 233 | } 234 | 235 | int first = itemCount % column; 236 | List list = []; 237 | for (int i = 0; i < itemCount; i++) { 238 | double left; 239 | if (first > 0 && i < first) { 240 | left = (width - itemW * first - space * (first - 1)) / 2 + 241 | (itemW + space) * i; 242 | } else { 243 | left = (space + itemW) * ((i - first) % column); 244 | } 245 | 246 | int itemIndex = (first > 0 && i < first) 247 | ? 0 248 | : (first > 0 ? (i + column - first) : i) ~/ column; 249 | 250 | double top = (width - itemW * row - space * (row - 1)) / 2 + 251 | (space + itemW) * itemIndex; 252 | 253 | list.add(Positioned( 254 | top: top, 255 | left: left, 256 | child: SizedBox( 257 | width: itemW, 258 | height: itemW, 259 | child: widget.itemBuilder(context, i), 260 | ))); 261 | } 262 | return Stack( 263 | children: list, 264 | ); 265 | } 266 | 267 | /// build dingTalk group. 268 | Widget _buildDingTalkGroup(BuildContext context) { 269 | double width = widget.width! - widget.padding.horizontal; 270 | int itemCount = math.min(4, widget.itemCount); 271 | double itemW = (width - widget.space) / 2; 272 | List children = []; 273 | for (int i = 0; i < itemCount; i++) { 274 | children.add(Positioned( 275 | top: (widget.space + itemW) * (i ~/ 2), 276 | left: (widget.space + itemW) * 277 | (((itemCount == 3 && i == 2) ? i + 1 : i) % 2), 278 | child: SizedBox( 279 | width: itemCount == 1 ? width : itemW, 280 | height: 281 | (itemCount == 1 || itemCount == 2 || (itemCount == 3 && i == 0)) 282 | ? width 283 | : itemW, 284 | child: widget.itemBuilder(context, i), 285 | ))); 286 | } 287 | return ClipOval( 288 | child: Stack( 289 | children: children, 290 | ), 291 | ); 292 | } 293 | 294 | /// build QQ group. 295 | Widget _buildQQGroup(BuildContext context) { 296 | double width = widget.width! - widget.padding.horizontal; 297 | int itemCount = math.min(5, widget.itemCount); 298 | if (itemCount == 1) { 299 | return ClipOval( 300 | child: SizedBox( 301 | width: width, 302 | height: width, 303 | child: widget.itemBuilder(context, 0), 304 | )); 305 | } 306 | 307 | List children = []; 308 | double startDegree = 0; 309 | double r = 0; 310 | double r1 = 0; 311 | double centerX = width / 2; 312 | double centerY = width / 2; 313 | switch (itemCount) { 314 | case 2: 315 | startDegree = 135; 316 | r = width / (2 + 2 * math.sin(math.pi / 4)); 317 | r1 = r; 318 | break; 319 | case 3: 320 | startDegree = 210; 321 | r = width / (2 + 4 * math.sin(math.pi * (3 - 2) / (2 * 3))); 322 | r1 = r / math.cos(math.pi * (3 - 2) / (2 * 3)); 323 | double R = r * 324 | (1 + math.sin(math.pi / itemCount)) / 325 | math.sin(math.pi / itemCount); 326 | double dy = 0.5 * (width - R - r * (1 + 1 / math.tan(math.pi / 3))); 327 | centerY = dy + r + r1; 328 | break; 329 | case 4: 330 | startDegree = 180; 331 | r = width / 4; 332 | r1 = r / math.cos(math.pi / 4); 333 | break; 334 | case 5: 335 | startDegree = 126; 336 | r = width / (2 + 4 * math.sin(math.pi * (5 - 2) / (2 * 5))); 337 | r1 = r / math.cos(math.pi * (5 - 2) / (2 * 5)); 338 | double R = r * 339 | (1 + math.sin(math.pi / itemCount)) / 340 | math.sin(math.pi / itemCount); 341 | double dy = 0.5 * (width - R - r * (1 + 1 / math.tan(math.pi / 5))); 342 | centerY = dy + r + r1; 343 | break; 344 | } 345 | 346 | for (int i = 0; i < itemCount; i++) { 347 | double degree1 = (itemCount == 2 || itemCount == 4) ? (-math.pi / 4) : 0; 348 | double x = centerX + r1 * math.sin(degree1 + i * 2 * math.pi / itemCount); 349 | double y = centerY - r1 * math.cos(degree1 + i * 2 * math.pi / itemCount); 350 | 351 | double degree = startDegree + i * 2 * 180 / itemCount; 352 | if (degree >= 360) degree = degree % 360; 353 | double previousX = r + 2 * r * math.sin(degree / 180 * math.pi); 354 | double previousY = r - 2 * r * math.cos(degree / 180 * math.pi); 355 | 356 | Widget child = Positioned.fromRect( 357 | rect: Rect.fromCircle(center: Offset(x, y), radius: r), 358 | child: ClipPath( 359 | clipper: QQClipper( 360 | total: itemCount, 361 | index: i, 362 | initIndex: widget.initIndex, 363 | previousX: previousX, 364 | previousY: previousY, 365 | degree: degree, 366 | arcAngle: widget.arcAngle, 367 | space: widget.space, 368 | ), 369 | child: widget.itemBuilder(context, i), 370 | ), 371 | ); 372 | children.add(child); 373 | } 374 | 375 | return Stack(children: children); 376 | } 377 | 378 | /// double is zero. 379 | bool _isZero(double? value) { 380 | return value == null || value == 0; 381 | } 382 | 383 | /// get big image size. 384 | Rect _getBigImgSize(double originalWidth, double originalHeight) { 385 | double width = widget.width ?? 386 | (MediaQuery.of(context).size.width - widget.margin.horizontal); 387 | width = width - widget.padding.horizontal; 388 | double itemW = (width - widget.space * 2) / 3; 389 | 390 | //double devicePixelRatio = MediaQuery.of(context)?.devicePixelRatio ?? 3; 391 | double devicePixelRatio = 1.0; 392 | double tempWidth = originalWidth / devicePixelRatio; 393 | double tempHeight = originalHeight / devicePixelRatio; 394 | double maxW = itemW * 2 + widget.space; 395 | double minW = width / 2; 396 | 397 | double relWidth = tempWidth >= maxW ? maxW : math.max(minW, tempWidth); 398 | 399 | double relHeight; 400 | double ratio = tempWidth / tempHeight; 401 | if (tempWidth == tempHeight) { 402 | relHeight = relWidth; 403 | } else if (tempWidth > tempHeight) { 404 | relHeight = relWidth / (math.min(ratio, 4 / 3)); 405 | } else { 406 | relHeight = relWidth / (math.max(ratio, 3 / 4)); 407 | } 408 | return Rect.fromLTRB(0, 0, relWidth, relHeight); 409 | } 410 | 411 | @override 412 | Widget build(BuildContext context) { 413 | Widget? child = Container(); 414 | double? realWidth = widget.width; 415 | double? realHeight = widget.height; 416 | switch (widget.type) { 417 | case NineGridType.normal: 418 | case NineGridType.weiBo: 419 | case NineGridType.weChat: 420 | Rect size = _initSize(context); 421 | if (widget.itemCount == 1) { 422 | child = _buildOneChild(context); 423 | if (child == null) { 424 | realWidth = size.right; 425 | realHeight = size.bottom; 426 | child = _buildChild(context, size.left); 427 | } 428 | } else { 429 | realWidth = size.right; 430 | realHeight = size.bottom; 431 | child = _buildChild(context, size.left); 432 | } 433 | break; 434 | case NineGridType.weChatGp: 435 | child = _buildWeChatGroup(context); 436 | break; 437 | case NineGridType.dingTalkGp: 438 | child = _buildDingTalkGroup(context); 439 | break; 440 | case NineGridType.qqGp: 441 | child = _buildQQGroup(context); 442 | break; 443 | } 444 | return Container( 445 | alignment: widget.alignment, 446 | color: widget.color, 447 | decoration: widget.decoration, 448 | margin: widget.margin, 449 | padding: widget.padding, 450 | width: realWidth, 451 | height: realHeight, 452 | child: child, 453 | ); 454 | } 455 | } 456 | 457 | /// image util. 458 | class _ImageUtil { 459 | late ImageStreamListener listener; 460 | late ImageStream imageStream; 461 | 462 | /// get image size. 463 | Future? getImageSize(Image? image) { 464 | if (image == null) { 465 | return null; 466 | } 467 | Completer completer = Completer(); 468 | listener = ImageStreamListener( 469 | (ImageInfo info, bool synchronousCall) { 470 | imageStream.removeListener(listener); 471 | if (!completer.isCompleted) { 472 | completer.complete(Rect.fromLTWH( 473 | 0, 0, info.image.width.toDouble(), info.image.height.toDouble())); 474 | } 475 | }, 476 | onError: (dynamic exception, StackTrace? stackTrace) { 477 | imageStream.removeListener(listener); 478 | if (!completer.isCompleted) { 479 | completer.completeError(exception, stackTrace); 480 | } 481 | }, 482 | ); 483 | imageStream = image.image.resolve(ImageConfiguration()); 484 | imageStream.addListener(listener); 485 | return completer.future; 486 | } 487 | } 488 | 489 | /// QQ Clipper. 490 | class QQClipper extends CustomClipper { 491 | QQClipper({ 492 | this.total = 0, 493 | this.index = 0, 494 | this.initIndex = 1, 495 | this.previousX = 0, 496 | this.previousY = 0, 497 | this.degree = 0, 498 | this.arcAngle = 0, 499 | this.space = 0, 500 | }) : assert(arcAngle >= 0 && arcAngle <= 180); 501 | 502 | final int total; 503 | final int index; 504 | final int initIndex; 505 | final double previousX; 506 | final double previousY; 507 | final double degree; 508 | final double arcAngle; 509 | final double space; 510 | 511 | @override 512 | Path getClip(Size size) { 513 | double r = size.width / 2; 514 | Path path = Path(); 515 | List points = []; 516 | 517 | if (total == 2 && index == initIndex) { 518 | path.addOval(Rect.fromLTRB(0, 0, size.width, size.height)); 519 | } else { 520 | /// arcAngle and space, prefer to use arcAngle. 521 | double spaceA = arcAngle > 0 522 | ? (arcAngle / 2) 523 | : (math.acos((r - math.min(r, space)) / r) / math.pi * 180); 524 | double startA = degree + spaceA; 525 | double endA = degree - spaceA; 526 | for (double i = startA; i <= 360 + endA; i = i + 1) { 527 | double x1 = r + r * math.sin(d2r(i)); 528 | double y1 = r - r * math.cos(d2r(i)); 529 | points.add(Offset(x1, y1)); 530 | } 531 | 532 | double spaceB = math.atan( 533 | r * math.sin(d2r(spaceA)) / (2 * r - r * math.cos(d2r(spaceA)))) / 534 | math.pi * 535 | 180; 536 | double r1 = (2 * r - r * math.cos(d2r(spaceA))) / math.cos(d2r(spaceB)); 537 | double startB = degree - 180 - spaceB; 538 | double endB = degree - 180 + spaceB; 539 | List pointsB = []; 540 | for (double i = startB; i < endB; i = i + 1) { 541 | double x1 = previousX + r1 * math.sin(d2r(i)); 542 | double y1 = previousY - r1 * math.cos(d2r(i)); 543 | pointsB.add(Offset(x1, y1)); 544 | } 545 | points.addAll(pointsB.reversed); 546 | path.addPolygon(points, true); 547 | } 548 | return path; 549 | } 550 | 551 | /// degree to radian. 552 | double d2r(double degree) { 553 | return degree / 180 * math.pi; 554 | } 555 | 556 | @override 557 | bool shouldReclip(CustomClipper oldClipper) { 558 | return this != oldClipper; 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /pkgget: -------------------------------------------------------------------------------- 1 | export PUB_HOSTED_URL=https://pub.flutter-io.cn 2 | export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn 3 | flutter packages get -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.10" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.3.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.8.0" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "0.2.19" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.0" 145 | sdks: 146 | dart: ">=2.12.0-259.9.beta <3.0.0" 147 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: nine_grid_view 2 | description: Flutter NineGridView & DragSortView, Similar to Weibo / WeChat nine grid view controls to display pictures. 3 | version: 2.0.0 4 | homepage: https://github.com/flutterchina/nine_grid_view 5 | 6 | environment: 7 | sdk: ">=2.12.0-259.9.beta <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://dart.dev/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | 23 | # To add assets to your package, add an assets section, like this: 24 | # assets: 25 | # - images/a_dot_burr.jpeg 26 | # - images/a_dot_ham.jpeg 27 | # 28 | # For details regarding assets in packages, see 29 | # https://flutter.dev/assets-and-images/#from-packages 30 | # 31 | # An image asset can refer to one or more resolution-specific "variants", see 32 | # https://flutter.dev/assets-and-images/#resolution-aware. 33 | 34 | # To add custom fonts to your package, add a fonts section here, 35 | # in this "flutter" section. Each entry in this list should have a 36 | # "family" key with the font family name, and a "fonts" key with a 37 | # list giving the asset and other descriptors for the font. For 38 | # example: 39 | # fonts: 40 | # - family: Schyler 41 | # fonts: 42 | # - asset: fonts/Schyler-Regular.ttf 43 | # - asset: fonts/Schyler-Italic.ttf 44 | # style: italic 45 | # - family: Trajan Pro 46 | # fonts: 47 | # - asset: fonts/TrajanPro.ttf 48 | # - asset: fonts/TrajanPro_Bold.ttf 49 | # weight: 700 50 | # 51 | # For details regarding fonts in packages, see 52 | # https://flutter.dev/custom-fonts/#from-packages 53 | -------------------------------------------------------------------------------- /screenshots/nine_grid_view1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view1.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view2.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view3.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view4.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view5.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view6.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view7.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view8.jpg -------------------------------------------------------------------------------- /screenshots/nine_grid_view9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutterchina/nine_grid_view/2d7768240363c8268aab37a54374e23a404b50bf/screenshots/nine_grid_view9.gif -------------------------------------------------------------------------------- /uploadMaster: -------------------------------------------------------------------------------- 1 | git push origin master 2 | --------------------------------------------------------------------------------