hyzp_ybqx-Commit154:使用自定义 my_flutter_drag_scale 插件,完美解决Listview滚动与图片缩放拖动之间的手势冲突,不会消耗点击事件,滚动很灵敏

master
WinUser01 4 years ago
parent f02a06d237
commit e08f8bc930

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 035e0765cc575c3b455689c2402cce073d564fce
channel: master
project_type: plugin

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

@ -0,0 +1 @@
TODO: Add your license here.

@ -0,0 +1,68 @@
# flutter_drag_scale
A new flutter plugin project.
```
可缩放可拖拽的功能可实现图片或者其他widget的缩放已经拖拽
并支持双击放大的功能
```
wechat 674668211 加微信进flutter微信群
掘金: https://juejin.im/user/581206302f301e005c60cd2f
简书https://www.jianshu.com/u/4a5dce56807b
csdnhttps://me.csdn.net/liu__520
github : https://github.com/LiuC520/
我们知道官方提供了双击缩放,但是不支持拖拽的功能,我们要实现向百度地图那样可以缩放又可以拖拽的功能,官方的方法就不支持了。
下面先演示下功能:
![sample.gif](https://upload-images.jianshu.io/upload_images/3463020-7823ae1e8d9bf0f9.gif?imageMogr2/auto-orient/strip)
参数只有两个:
1、child 是一个widget可以是图片或者任意的widget
2、doubleTapStillScale默认是true意思是双击一直放大还是只放大一次再次双击缩小到原图片的大小如果为false第一次双击放大图片2倍再次双击回位。
用法很简单:
1、导入依赖库
```
dependencies:
flutter:
sdk: flutter
flutter_drag_scale:
git: https://github.com/LiuC520/flutter_drag_scale.git
```
2、引入库
```
import 'package:flutter_drag_scale/flutter_drag_scale.dart';
```
3、如下的用法
```
import 'package:flutter/material.dart';
import 'package:flutter_drag_scale/flutter_drag_scale.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 400.0,
width: 400,
child: Center(
child: DragScaleContainer(
doubleTapStillScale: true,
child: new Image(
image: new NetworkImage(
'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),
),
),
),
);
}
}
```

@ -0,0 +1,34 @@
group 'com.example.flutter_drag_scale'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
}

@ -0,0 +1 @@
rootProject.name = 'flutter_drag_scale'

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_drag_scale">
</manifest>

@ -0,0 +1,25 @@
package com.example.flutter_drag_scale;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
/** FlutterDragScalePlugin */
public class FlutterDragScalePlugin implements MethodCallHandler {
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_drag_scale");
channel.setMethodCallHandler(new FlutterDragScalePlugin());
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
}

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 035e0765cc575c3b455689c2402cce073d564fce
channel: master
project_type: app

@ -0,0 +1,16 @@
# flutter_drag_scale_example
Demonstrates how to use the flutter_drag_scale plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.io/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

@ -0,0 +1,61 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutter_drag_scale_example"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_drag_scale_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_drag_scale_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_drag_scale_example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1,13 @@
package com.example.flutter_drag_scale_example;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

@ -0,0 +1,25 @@
package io.flutter.plugins;
import io.flutter.plugin.common.PluginRegistry;
import com.example.flutter_drag_scale.FlutterDragScalePlugin;
/**
* Generated file. Do not edit.
*/
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
FlutterDragScalePlugin.registerWith(registry.registrarFor("com.example.flutter_drag_scale.FlutterDragScalePlugin"));
}
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {
return true;
}
registry.registrarFor(key);
return false;
}
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_drag_scale_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -0,0 +1,29 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

@ -0,0 +1,2 @@
sdk.dir=S:\\Android\\Android-SDK-Windows
flutter.sdk=R:\\Flutter\\FlutterSDK\\1.22.6

@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>

@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,7 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/majialun/Documents/flutter/flutter
FLUTTER_APPLICATION_PATH=/Users/majialun/Desktop/github/flutter_drag_scale/example
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
SYMROOT=${SOURCE_ROOT}/../build/ios
FLUTTER_FRAMEWORK_DIR=/Users/majialun/Documents/flutter/flutter/bin/cache/artifacts/engine/ios

@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

@ -0,0 +1,69 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_ary = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end
target 'Runner' do
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
# Flutter Pods
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join('.symlinks', 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
end
}
# Plugin Pods
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p|
symlink = File.join('.symlinks', 'plugins', p[:name])
File.symlink(p[:path], symlink)
pod p[:name], :path => File.join(symlink, 'ios')
}
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

@ -0,0 +1,506 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0910;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterDragScaleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterDragScaleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterDragScaleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,6 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

@ -0,0 +1,13 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
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.

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
#import "GeneratedPluginRegistrant.h"
#if __has_include(<flutter_drag_scale/FlutterDragScalePlugin.h>)
#import <flutter_drag_scale/FlutterDragScalePlugin.h>
#else
@import flutter_drag_scale;
#endif
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[FlutterDragScalePlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterDragScalePlugin"]];
}
@end

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_drag_scale_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

@ -0,0 +1,9 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_drag_scale/flutter_drag_scale.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 400.0,
width: 400,
child: Center(
child: DragScaleContainer(
doubleTapStillScale: true,
child: new Image(
image: new NetworkImage(
'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),
),
),
),
);
}
}

@ -0,0 +1,66 @@
name: flutter_drag_scale_example
description: Demonstrates how to use the flutter_drag_scale plugin.
publish_to: 'none'
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_drag_scale:
git: https://github.com/LiuC520/flutter_drag_scale.git
# flutter_drag_scale:
# path: ../
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages

@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_drag_scale_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface FlutterDragScalePlugin : NSObject<FlutterPlugin>
@end

@ -0,0 +1,20 @@
#import "FlutterDragScalePlugin.h"
@implementation FlutterDragScalePlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"flutter_drag_scale"
binaryMessenger:[registrar messenger]];
FlutterDragScalePlugin* instance = [[FlutterDragScalePlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
} else {
result(FlutterMethodNotImplemented);
}
}
@end

@ -0,0 +1,21 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'flutter_drag_scale'
s.version = '0.0.1'
s.summary = 'A new flutter plugin project.'
s.description = <<-DESC
A new flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.ios.deployment_target = '8.0'
end

@ -0,0 +1,925 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// import 'package:flutter/cupertino.dart';
// import 'package:photo_view_test/common.dart';
// import 'package:photo_view_test/EventBus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'
show
GestureRecognizer,
GestureTapDownCallback,
GestureTapUpCallback,
GestureTapCallback,
GestureTapCancelCallback,
GestureLongPressUpCallback,
GestureLongPressCallback,
GestureDragStartCallback,
GestureDragUpdateCallback,
GestureDragEndCallback,
GestureDragCancelCallback,
GestureDragDownCallback,
GestureForcePressStartCallback,
GestureForcePressPeakCallback,
GestureForcePressUpdateCallback,
GestureForcePressEndCallback,
TapGestureRecognizer,
LongPressGestureRecognizer,
VerticalDragGestureRecognizer,
ForcePressGestureRecognizer,
HorizontalDragGestureRecognizer,
TapDownDetails,
PanGestureRecognizer,
TapUpDetails,
DragUpdateDetails,
DragDownDetails,
DragStartDetails,
DragEndDetails;
import 'package:flutter/rendering.dart';
import 'package:flutter/src/widgets/basic.dart' show Listener;
import 'package:flutter/src/widgets/framework.dart'
show
StatelessWidget,
StatefulWidget,
State,
Widget,
Element,
BuildContext,
SingleChildRenderObjectWidget;
import './scale.dart';
import './multitap.dart';
import './double_details.dart';
export 'package:flutter/gestures.dart'
show
DragDownDetails,
DragStartDetails,
DragUpdateDetails,
DragEndDetails,
GestureTapDownCallback,
GestureTapUpCallback,
GestureTapCallback,
GestureTapCancelCallback,
GestureLongPressCallback,
GestureDragDownCallback,
GestureDragStartCallback,
GestureDragUpdateCallback,
GestureDragEndCallback,
GestureDragCancelCallback,
// GestureScaleStartCallback,
// GestureScaleUpdateCallback,
// GestureScaleEndCallback,
GestureForcePressStartCallback,
GestureForcePressPeakCallback,
GestureForcePressEndCallback,
GestureForcePressUpdateCallback,
// ScaleStartDetails,
// ScaleUpdateDetails,
// ScaleEndDetails,
TapDownDetails,
TapUpDetails,
Velocity;
export './scale.dart'
show
GestureScaleStartCallback,
GestureScaleUpdateCallback,
GestureScaleEndCallback,
ScaleStartDetails,
ScaleUpdateDetails,
ScaleEndDetails;
export './double_details.dart' show DoubleDetails;
// Examples can assume:
// bool _lights;
// void setState(VoidCallback fn) { }
// String _last;
/// Factory for creating gesture recognizers.
///
/// `T` is the type of gesture recognizer this class manages.
///
/// Used by [RawGestureDetector.gestures].
@optionalTypeArgs
abstract class GestureRecognizerFactory<T extends GestureRecognizer> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const GestureRecognizerFactory();
/// Must return an instance of T.
T constructor();
/// Must configure the given instance (which will have been created by
/// `constructor`).
///
/// This normally means setting the callbacks.
void initializer(T instance);
bool _debugAssertTypeMatches(Type type) {
assert(
type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.');
return true;
}
}
/// Signature for closures that implement [GestureRecognizerFactory.constructor].
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
/// Signature for closures that implement [GestureRecognizerFactory.initializer].
typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(
T instance);
/// Factory for creating gesture recognizers that delegates to callbacks.
///
/// Used by [RawGestureDetector.gestures].
class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer>
extends GestureRecognizerFactory<T> {
/// Creates a gesture recognizer factory with the given callbacks.
///
/// The arguments must not be null.
const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
: assert(_constructor != null),
assert(_initializer != null);
final GestureRecognizerFactoryConstructor<T> _constructor;
final GestureRecognizerFactoryInitializer<T> _initializer;
@override
T constructor() => _constructor();
@override
void initializer(T instance) => _initializer(instance);
}
/// A widget that detects gestures.
///
/// Attempts to recognize gestures that correspond to its non-null callbacks.
///
/// If this widget has a child, it defers to that child for its sizing behavior.
/// If it does not have a child, it grows to fit the parent instead.
///
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
/// See <http://flutter.io/gestures/> for additional information.
///
/// Material design applications typically react to touches with ink splash
/// effects. The [InkWell] class implements this effect and can be used in place
/// of a [GestureDetector] for handling taps.
///
/// {@tool sample}
///
/// This example makes a rectangle react to being tapped by setting the
/// `_lights` field:
///
/// ```dart
/// GestureDetector(
/// onTap: () {
/// setState(() { _lights = true; });
/// },
/// child: Container(
/// color: Colors.yellow,
/// child: Text('TURN LIGHTS ON'),
/// ),
/// )
/// ```
/// {@end-tool}
///
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
class GestureDetector extends StatelessWidget {
/// Creates a widget that detects gestures.
///
/// Pan and scale callbacks cannot be used simultaneously because scale is a
/// superset of pan. Simply use the scale callbacks instead.
///
/// Horizontal and vertical drag callbacks cannot be used simultaneously
/// because a combination of a horizontal and vertical drag is a pan. Simply
/// use the pan callbacks instead.
///
/// By default, gesture detectors contribute semantic information to the tree
/// that is used by assistive technology.
GestureDetector(
{Key key,
this.child,
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressUp,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onForcePressStart,
this.onForcePressPeak,
this.onForcePressUpdate,
this.onForcePressEnd,
this.pointerDownCallback,
this.pointerUpCallback,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.behavior,
this.excludeFromSemantics = false})
: assert(excludeFromSemantics != null),
assert(() {
final bool haveVerticalDrag = onVerticalDragStart != null ||
onVerticalDragUpdate != null ||
onVerticalDragEnd != null;
final bool haveHorizontalDrag = onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null;
final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
final bool haveScale =
onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
if (havePan || haveScale) {
if (havePan && haveScale) {
throw FlutterError('Incorrect GestureDetector arguments.\n'
'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.');
}
final String recognizer = havePan ? 'pan' : 'scale';
if (haveVerticalDrag && haveHorizontalDrag) {
throw FlutterError('Incorrect GestureDetector arguments.\n'
'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.');
}
}
return true;
}()),
super(key: key);
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// A pointer that might cause a tap has contacted the screen at a particular
/// location.
///
/// This is called after a short timeout, even if the winning gesture has not
/// yet been selected. If the tap gesture wins, [onTapUp] will be called,
/// otherwise [onTapCancel] will be called.
final GestureTapDownCallback onTapDown;
/// A pointer that will trigger a tap has stopped contacting the screen at a
/// particular location.
///
/// This triggers immediately before [onTap] in the case of the tap gesture
/// winning. If the tap gesture did not win, [onTapCancel] is called instead.
final GestureTapUpCallback onTapUp;
/// A tap has occurred.
///
/// This triggers when the tap gesture wins. If the tap gesture did not win,
/// [onTapCancel] is called instead.
///
/// See also:
///
/// * [onTapUp], which is called at the same time but includes details
/// regarding the pointer position.
final GestureTapCallback onTap;
/// The pointer that previously triggered [onTapDown] will not end up causing
/// a tap.
///
/// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
/// the tap gesture did not win.
final GestureTapCancelCallback onTapCancel;
/// The user has tapped the screen at the same location twice in quick
/// succession.
final GestureDoubleTapCallback onDoubleTap;
/// A pointer has remained in contact with the screen at the same location for
/// a long period of time.
final GestureLongPressCallback onLongPress;
/// A pointer that has triggered a long-press has stopped contacting the screen.
final GestureLongPressUpCallback onLongPressUp;
/// A pointer has contacted the screen and might begin to move vertically.
final GestureDragDownCallback onVerticalDragDown;
/// A pointer has contacted the screen and has begun to move vertically.
final GestureDragStartCallback onVerticalDragStart;
/// A pointer that is in contact with the screen and moving vertically has
/// moved in the vertical direction.
final GestureDragUpdateCallback onVerticalDragUpdate;
/// A pointer that was previously in contact with the screen and moving
/// vertically is no longer in contact with the screen and was moving at a
/// specific velocity when it stopped contacting the screen.
final GestureDragEndCallback onVerticalDragEnd;
/// The pointer that previously triggered [onVerticalDragDown] did not
/// complete.
final GestureDragCancelCallback onVerticalDragCancel;
/// A pointer has contacted the screen and might begin to move horizontally.
final GestureDragDownCallback onHorizontalDragDown;
/// A pointer has contacted the screen and has begun to move horizontally.
final GestureDragStartCallback onHorizontalDragStart;
/// A pointer that is in contact with the screen and moving horizontally has
/// moved in the horizontal direction.
final GestureDragUpdateCallback onHorizontalDragUpdate;
/// A pointer that was previously in contact with the screen and moving
/// horizontally is no longer in contact with the screen and was moving at a
/// specific velocity when it stopped contacting the screen.
final GestureDragEndCallback onHorizontalDragEnd;
/// The pointer that previously triggered [onHorizontalDragDown] did not
/// complete.
final GestureDragCancelCallback onHorizontalDragCancel;
Function() pointerDownCallback; //
Function() pointerUpCallback; //
/// A pointer has contacted the screen and might begin to move.
final GestureDragDownCallback onPanDown;
/// A pointer has contacted the screen and has begun to move.
final GestureDragStartCallback onPanStart;
/// A pointer that is in contact with the screen and moving has moved again.
final GestureDragUpdateCallback onPanUpdate;
/// A pointer that was previously in contact with the screen and moving
/// is no longer in contact with the screen and was moving at a specific
/// velocity when it stopped contacting the screen.
final GestureDragEndCallback onPanEnd;
/// The pointer that previously triggered [onPanDown] did not complete.
final GestureDragCancelCallback onPanCancel;
/// The pointers in contact with the screen have established a focal point and
/// initial scale of 1.0.
final GestureScaleStartCallback onScaleStart;
/// The pointers in contact with the screen have indicated a new focal point
/// and/or scale.
final GestureScaleUpdateCallback onScaleUpdate;
/// The pointers are no longer in contact with the screen.
final GestureScaleEndCallback onScaleEnd;
/// The pointer is in contact with the screen and has pressed with sufficient
/// force to initiate a force press. The amount of force is at least
/// [ForcePressGestureRecognizer.startPressure].
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressStartCallback onForcePressStart;
/// The pointer is in contact with the screen and has pressed with the maximum
/// force. The amount of force is at least
/// [ForcePressGestureRecognizer.peakPressure].
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressPeakCallback onForcePressPeak;
/// A pointer is in contact with the screen, has previously passed the
/// [ForcePressGestureRecognizer.startPressure] and is either moving on the
/// plane of the screen, pressing the screen with varying forces or both
/// simultaneously.
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressUpdateCallback onForcePressUpdate;
/// The pointer is no longer in contact with the screen.
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressEndCallback onForcePressEnd;
/// How this gesture detector should behave during hit testing.
///
/// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
/// [HitTestBehavior.translucent] if child is null.
final HitTestBehavior behavior;
/// Whether to exclude these gestures from the semantics tree. For
/// example, the long-press gesture for showing a tooltip is
/// excluded because the tooltip itself is included in the semantics
/// tree directly and so having a gesture to show it would result in
/// duplication of information.
final bool excludeFromSemantics;
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel;
},
);
}
if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => DoubleTapGestureRecognizer(debugOwner: this),
(DoubleTapGestureRecognizer instance) {
instance..onDoubleTap = onDoubleTap;
},
);
}
if (onLongPress != null || onLongPressUp != null) {
gestures[LongPressGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) {
instance
..onLongPress = onLongPress
..onLongPressUp = onLongPressUp;
},
);
}
if (onVerticalDragDown != null ||
onVerticalDragStart != null ||
onVerticalDragUpdate != null ||
onVerticalDragEnd != null ||
onVerticalDragCancel != null) {
gestures[VerticalDragGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(debugOwner: this),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = onVerticalDragDown
..onStart = onVerticalDragStart
..onUpdate = onVerticalDragUpdate
..onEnd = onVerticalDragEnd
..onCancel = onVerticalDragCancel;
},
);
}
if (onHorizontalDragDown != null ||
onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null ||
onHorizontalDragCancel != null) {
gestures[HorizontalDragGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(debugOwner: this),
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = onHorizontalDragDown
..onStart = onHorizontalDragStart
..onUpdate = onHorizontalDragUpdate
..onEnd = onHorizontalDragEnd
..onCancel = onHorizontalDragCancel;
},
);
}
if (onPanDown != null ||
onPanStart != null ||
onPanUpdate != null ||
onPanEnd != null ||
onPanCancel != null) {
gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(debugOwner: this),
(PanGestureRecognizer instance) {
instance
..onDown = onPanDown
..onStart = onPanStart
..onUpdate = onPanUpdate
..onEnd = onPanEnd
..onCancel = onPanCancel;
},
);
}
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
gestures[ScaleGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
() => ScaleGestureRecognizer(debugOwner: this),
(ScaleGestureRecognizer instance) {
instance
..onStart = onScaleStart
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd;
},
);
}
if (onForcePressStart != null ||
onForcePressPeak != null ||
onForcePressUpdate != null ||
onForcePressEnd != null) {
gestures[ForcePressGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
() => ForcePressGestureRecognizer(debugOwner: this),
(ForcePressGestureRecognizer instance) {
instance
..onStart = onForcePressStart
..onPeak = onForcePressPeak
..onUpdate = onForcePressUpdate
..onEnd = onForcePressEnd;
},
);
}
return RawGestureDetector(
gestures: gestures,
pointerDownCallback: pointerDownCallback,
pointerUpCallback: pointerUpCallback,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
}
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
/// [RawGestureDetector] is useful primarily when developing your
/// own gesture recognizers.
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
/// {@tool sample}
///
/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that
/// the code is being used inside a [State] object with a `_last` field that is
/// then displayed as the child of the gesture detector.
///
/// ```dart
/// RawGestureDetector(
/// gestures: <Type, GestureRecognizerFactory>{
/// TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
/// () => TapGestureRecognizer(),
/// (TapGestureRecognizer instance) {
/// instance
/// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
/// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
/// ..onTap = () { setState(() { _last = 'tap'; }); }
/// ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
/// },
/// ),
/// },
/// child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [GestureDetector], a less flexible but much simpler widget that does the same thing.
/// * [Listener], a widget that reports raw pointer events.
/// * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
class RawGestureDetector extends StatefulWidget {
/// Creates a widget that detects gestures.
///
/// By default, gesture detectors contribute semantic information to the tree
/// that is used by assistive technology. This can be controlled using
/// [excludeFromSemantics].
RawGestureDetector(
{Key key,
this.child,
this.gestures = const <Type, GestureRecognizerFactory>{},
this.pointerDownCallback,
this.pointerUpCallback,
this.behavior,
this.excludeFromSemantics = false})
: assert(gestures != null),
assert(excludeFromSemantics != null),
super(key: key);
Function() pointerDownCallback; //
Function() pointerUpCallback; //
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// The gestures that this widget will attempt to recognize.
///
/// This should be a map from [GestureRecognizer] subclasses to
/// [GestureRecognizerFactory] subclasses specialized with the same type.
///
/// This value can be late-bound at layout time using
/// [RawGestureDetectorState.replaceGestureRecognizers].
final Map<Type, GestureRecognizerFactory> gestures;
/// How this gesture detector should behave during hit testing.
///
/// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
/// [HitTestBehavior.translucent] if child is null.
final HitTestBehavior behavior;
/// Whether to exclude these gestures from the semantics tree. For
/// example, the long-press gesture for showing a tooltip is
/// excluded because the tooltip itself is included in the semantics
/// tree directly and so having a gesture to show it would result in
/// duplication of information.
final bool excludeFromSemantics;
@override
RawGestureDetectorState createState() => RawGestureDetectorState();
}
/// State for a [RawGestureDetector].
class RawGestureDetectorState extends State<RawGestureDetector> {
Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};
@override
void initState() {
super.initState();
_syncAll(widget.gestures);
}
@override
void didUpdateWidget(RawGestureDetector oldWidget) {
super.didUpdateWidget(oldWidget);
_syncAll(widget.gestures);
}
/// This method can be called after the build phase, during the
/// layout of the nearest descendant [RenderObjectWidget] of the
/// gesture detector, to update the list of active gesture
/// recognizers.
///
/// The typical use case is [Scrollable]s, which put their viewport
/// in their gesture detector, and then need to know the dimensions
/// of the viewport and the viewport's child to determine whether
/// the gesture detector should be enabled.
///
/// The argument should follow the same conventions as
/// [RawGestureDetector.gestures]. It acts like a temporary replacement for
/// that value until the next build.
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
assert(() {
if (!context.findRenderObject().owner.debugDoingLayout) {
throw FlutterError(
'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
'The replaceGestureRecognizers() method can only be called during the layout phase. '
'To set the gesture recognizers at other times, trigger a new build using setState() '
'and provide the new gesture recognizers as constructor arguments to the corresponding '
'RawGestureDetector or GestureDetector object.');
}
return true;
}());
_syncAll(gestures);
if (!widget.excludeFromSemantics) {
final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
context.visitChildElements((Element element) {
final _GestureSemantics widget = element.widget;
widget._updateHandlers(semanticsGestureHandler);
});
}
}
/// This method can be called outside of the build phase to filter the list of
/// available semantic actions.
///
/// The actual filtering is happening in the next frame and a frame will be
/// scheduled if non is pending.
///
/// This is used by [Scrollable] to configure system accessibility tools so
/// that they know in which direction a particular list can be scrolled.
///
/// If this is never called, then the actions are not filtered. If the list of
/// actions to filter changes, it must be called again.
void replaceSemanticsActions(Set<SemanticsAction> actions) {
assert(() {
final Element element = context;
if (element.owner.debugBuilding) {
throw FlutterError(
'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
'The replaceSemanticsActions() method can only be called outside of the build phase.');
}
return true;
}());
if (!widget.excludeFromSemantics) {
final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
semanticsGestureHandler.validActions =
actions; // will call _markNeedsSemanticsUpdate(), if required.
}
}
@override
void dispose() {
for (GestureRecognizer recognizer in _recognizers.values) recognizer.dispose();
_recognizers = null;
super.dispose();
}
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
assert(_recognizers != null);
final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
_recognizers = <Type, GestureRecognizer>{};
for (Type type in gestures.keys) {
assert(gestures[type] != null);
assert(gestures[type]._debugAssertTypeMatches(type));
assert(!_recognizers.containsKey(type));
_recognizers[type] = oldRecognizers[type] ?? gestures[type].constructor();
assert(_recognizers[type].runtimeType == type,
'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers[type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
gestures[type].initializer(_recognizers[type]);
}
for (Type type in oldRecognizers.keys) {
if (!_recognizers.containsKey(type)) oldRecognizers[type].dispose();
}
}
void _handlePointerUp(PointerUpEvent event) {
if (widget.pointerUpCallback != null) {
widget.pointerUpCallback();
}
}
void _handlePointerDown(PointerDownEvent event) {
if (widget.pointerDownCallback != null) {
widget.pointerDownCallback();
}
assert(_recognizers != null);
for (GestureRecognizer recognizer in _recognizers.values) recognizer.addPointer(event);
}
HitTestBehavior get _defaultBehavior {
return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
}
void _handleSemanticsTap() {
final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
assert(recognizer != null);
if (recognizer.onTapDown != null) recognizer.onTapDown(TapDownDetails());
if (recognizer.onTapUp != null) recognizer.onTapUp(TapUpDetails());
if (recognizer.onTap != null) recognizer.onTap();
}
void _handleSemanticsLongPress() {
final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
assert(recognizer != null);
if (recognizer.onLongPress != null) recognizer.onLongPress();
}
void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) {
{
final HorizontalDragGestureRecognizer recognizer =
_recognizers[HorizontalDragGestureRecognizer];
if (recognizer != null) {
if (recognizer.onDown != null) recognizer.onDown(DragDownDetails());
if (recognizer.onStart != null) recognizer.onStart(DragStartDetails());
if (recognizer.onUpdate != null) recognizer.onUpdate(updateDetails);
if (recognizer.onEnd != null) recognizer.onEnd(DragEndDetails(primaryVelocity: 0.0));
return;
}
}
{
final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
if (recognizer != null) {
if (recognizer.onDown != null) recognizer.onDown(DragDownDetails());
if (recognizer.onStart != null) recognizer.onStart(DragStartDetails());
if (recognizer.onUpdate != null) recognizer.onUpdate(updateDetails);
if (recognizer.onEnd != null) recognizer.onEnd(DragEndDetails());
return;
}
}
}
void _handleSemanticsVerticalDragUpdate(DragUpdateDetails updateDetails) {
{
final VerticalDragGestureRecognizer recognizer = _recognizers[VerticalDragGestureRecognizer];
if (recognizer != null) {
if (recognizer.onDown != null) recognizer.onDown(DragDownDetails());
if (recognizer.onStart != null) recognizer.onStart(DragStartDetails());
if (recognizer.onUpdate != null) recognizer.onUpdate(updateDetails);
if (recognizer.onEnd != null) recognizer.onEnd(DragEndDetails(primaryVelocity: 0.0));
return;
}
}
{
final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
if (recognizer != null) {
if (recognizer.onDown != null) recognizer.onDown(DragDownDetails());
if (recognizer.onStart != null) recognizer.onStart(DragStartDetails());
if (recognizer.onUpdate != null) recognizer.onUpdate(updateDetails);
if (recognizer.onEnd != null) recognizer.onEnd(DragEndDetails());
return;
}
}
}
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
onPointerUp: _handlePointerUp,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child);
if (!widget.excludeFromSemantics) result = _GestureSemantics(owner: this, child: result);
return result;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
if (_recognizers == null) {
properties.add(DiagnosticsNode.message('DISPOSED'));
} else {
final List<String> gestures = _recognizers.values
.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription)
.toList();
properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers.values,
level: DiagnosticLevel.fine));
}
properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
}
}
class _GestureSemantics extends SingleChildRenderObjectWidget {
const _GestureSemantics({Key key, Widget child, this.owner}) : super(key: key, child: child);
final RawGestureDetectorState owner;
@override
RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
return RenderSemanticsGestureHandler(
onTap: _onTapHandler,
onLongPress: _onLongPressHandler,
onHorizontalDragUpdate: _onHorizontalDragUpdateHandler,
onVerticalDragUpdate: _onVerticalDragUpdateHandler,
);
}
void _updateHandlers(RenderSemanticsGestureHandler renderObject) {
renderObject
..onTap = _onTapHandler
..onLongPress = _onLongPressHandler
..onHorizontalDragUpdate = _onHorizontalDragUpdateHandler
..onVerticalDragUpdate = _onVerticalDragUpdateHandler;
}
@override
void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
_updateHandlers(renderObject);
}
GestureTapCallback get _onTapHandler {
return owner._recognizers.containsKey(TapGestureRecognizer) ? owner._handleSemanticsTap : null;
}
GestureTapCallback get _onLongPressHandler {
return owner._recognizers.containsKey(LongPressGestureRecognizer)
? owner._handleSemanticsLongPress
: null;
}
GestureDragUpdateCallback get _onHorizontalDragUpdateHandler {
return owner._recognizers.containsKey(HorizontalDragGestureRecognizer) ||
owner._recognizers.containsKey(PanGestureRecognizer)
? owner._handleSemanticsHorizontalDragUpdate
: null;
}
GestureDragUpdateCallback get _onVerticalDragUpdateHandler {
return owner._recognizers.containsKey(VerticalDragGestureRecognizer) ||
owner._recognizers.containsKey(PanGestureRecognizer)
? owner._handleSemanticsVerticalDragUpdate
: null;
}
}

@ -0,0 +1,14 @@
import 'package:flutter/src/gestures/events.dart' show PointerEvent;
/// Signature for callback when the user has tapped the screen at the same
/// location twice in quick succession.
typedef GestureDoubleTapCallback = void Function(DoubleDetails details);
/// double tap callback details
///
class DoubleDetails {
DoubleDetails({this.pointerEvent});
final PointerEvent pointerEvent;
@override
String toString() => 'DoubleDetails(pointerEvent: $pointerEvent)';
}

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import './touchable_container.dart';
@immutable
class DragScaleContainer extends StatefulWidget {
Widget child;
/// true
/// false
bool doubleTapStillScale;
ValueChanged<ScaleChangedModel> scaleChangedCallback; //
Function() pointerDownCallback; //
Function() pointerUpCallback; //
DragScaleContainer({
Key key,
@required this.child,
this.scaleChangedCallback,
this.pointerDownCallback,
this.pointerUpCallback,
this.doubleTapStillScale = true,
});
@override
State<StatefulWidget> createState() {
return _DragScaleContainerState();
}
}
class _DragScaleContainerState extends State<DragScaleContainer> {
@override
Widget build(BuildContext context) {
return ClipRect(
child: TouchableContainer(
child: widget.child,
doubleTapStillScale: widget.doubleTapStillScale,
scaleChangedCallback: widget.scaleChangedCallback,
pointerDownCallback: widget.pointerDownCallback,
pointerUpCallback: widget.pointerUpCallback,
),
);
}
}

@ -0,0 +1,403 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui' show Offset;
import 'package:flutter/src/gestures/binding.dart';
import 'package:flutter/src/gestures/pointer_router.dart';
import 'package:flutter/src/gestures/tap.dart';
import 'package:flutter/src/gestures/arena.dart';
import 'package:flutter/src/gestures/constants.dart';
import 'package:flutter/src/gestures/events.dart';
import 'package:flutter/src/gestures/recognizer.dart';
import 'package:flutter/src/gestures/velocity_tracker.dart';
import './double_details.dart';
/// Signature used by [MultiTapGestureRecognizer] for when a pointer that might
/// cause a tap has contacted the screen at a particular location.
typedef GestureMultiTapDownCallback = void Function(
int pointer, TapDownDetails details);
/// Signature used by [MultiTapGestureRecognizer] for when a pointer that will
/// trigger a tap has stopped contacting the screen at a particular location.
typedef GestureMultiTapUpCallback = void Function(
int pointer, TapUpDetails details);
/// Signature used by [MultiTapGestureRecognizer] for when a tap has occurred.
typedef GestureMultiTapCallback = void Function(int pointer);
/// Signature for when the pointer that previously triggered a
/// [GestureMultiTapDownCallback] will not end up causing a tap.
typedef GestureMultiTapCancelCallback = void Function(int pointer);
/// TapTracker helps track individual tap sequences as part of a
/// larger gesture.
class _TapTracker {
_TapTracker({PointerDownEvent event, this.entry})
: pointer = event.pointer,
_initialPosition = event.position;
final int pointer;
final GestureArenaEntry entry;
final Offset _initialPosition;
bool _isTrackingPointer = false;
void startTrackingPointer(PointerRoute route) {
if (!_isTrackingPointer) {
_isTrackingPointer = true;
GestureBinding.instance.pointerRouter.addRoute(pointer, route);
}
}
void stopTrackingPointer(PointerRoute route) {
if (_isTrackingPointer) {
_isTrackingPointer = false;
GestureBinding.instance.pointerRouter.removeRoute(pointer, route);
}
}
bool isWithinTolerance(PointerEvent event, double tolerance) {
final Offset offset = event.position - _initialPosition;
return offset.distance <= tolerance;
}
}
/// Recognizes when the user has tapped the screen at the same location twice in
/// quick succession.
class DoubleTapGestureRecognizer extends GestureRecognizer {
/// Create a gesture recognizer for double taps.
DoubleTapGestureRecognizer({Object debugOwner})
: super(debugOwner: debugOwner);
// Implementation notes:
// The double tap recognizer can be in one of four states. There's no
// explicit enum for the states, because they are already captured by
// the state of existing fields. Specifically:
// Waiting on first tap: In this state, the _trackers list is empty, and
// _firstTap is null.
// First tap in progress: In this state, the _trackers list contains all
// the states for taps that have begun but not completed. This list can
// have more than one entry if two pointers begin to tap.
// Waiting on second tap: In this state, one of the in-progress taps has
// completed successfully. The _trackers list is again empty, and
// _firstTap records the successful tap.
// Second tap in progress: Much like the "first tap in progress" state, but
// _firstTap is non-null. If a tap completes successfully while in this
// state, the callback is called and the state is reset.
// There are various other scenarios that cause the state to reset:
// - All in-progress taps are rejected (by time, distance, pointercancel, etc)
// - The long timer between taps expires
// - The gesture arena decides we have been rejected wholesale
/// Called when the user has tapped the screen at the same location twice in
/// quick succession.
GestureDoubleTapCallback onDoubleTap;
Timer _doubleTapTimer;
_TapTracker _firstTap;
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
@override
void addPointer(PointerEvent event) {
// Ignore out-of-bounds second taps.
if (_firstTap != null &&
!_firstTap.isWithinTolerance(event, kDoubleTapSlop)) return;
_stopDoubleTapTimer();
final _TapTracker tracker = _TapTracker(
event: event,
entry: GestureBinding.instance.gestureArena.add(event.pointer, this));
_trackers[event.pointer] = tracker;
tracker.startTrackingPointer(_handleEvent);
}
void _handleEvent(PointerEvent event) {
final _TapTracker tracker = _trackers[event.pointer];
assert(tracker != null);
if (event is PointerUpEvent) {
if (_firstTap == null)
_registerFirstTap(tracker);
else
_registerSecondTap(tracker, event);
} else if (event is PointerMoveEvent) {
if (!tracker.isWithinTolerance(event, kDoubleTapTouchSlop))
_reject(tracker);
} else if (event is PointerCancelEvent) {
_reject(tracker);
}
}
@override
void acceptGesture(int pointer) {}
@override
void rejectGesture(int pointer) {
_TapTracker tracker = _trackers[pointer];
// If tracker isn't in the list, check if this is the first tap tracker
if (tracker == null && _firstTap != null && _firstTap.pointer == pointer)
tracker = _firstTap;
// If tracker is still null, we rejected ourselves already
if (tracker != null) _reject(tracker);
}
void _reject(_TapTracker tracker) {
_trackers.remove(tracker.pointer);
tracker.entry.resolve(GestureDisposition.rejected);
_freezeTracker(tracker);
// If the first tap is in progress, and we've run out of taps to track,
// reset won't have any work to do. But if we're in the second tap, we need
// to clear intermediate state.
if (_firstTap != null && (_trackers.isEmpty || tracker == _firstTap))
_reset();
}
@override
void dispose() {
_reset();
super.dispose();
}
void _reset() {
_stopDoubleTapTimer();
if (_firstTap != null) {
// Note, order is important below in order for the resolve -> reject logic
// to work properly.
final _TapTracker tracker = _firstTap;
_firstTap = null;
_reject(tracker);
GestureBinding.instance.gestureArena.release(tracker.pointer);
}
_clearTrackers();
}
void _registerFirstTap(_TapTracker tracker) {
_startDoubleTapTimer();
GestureBinding.instance.gestureArena.hold(tracker.pointer);
// Note, order is important below in order for the clear -> reject logic to
// work properly.
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_clearTrackers();
_firstTap = tracker;
}
void _registerSecondTap(_TapTracker tracker, PointerEvent event) {
_firstTap.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
if (onDoubleTap != null)
invokeCallback<void>(
'onDoubleTap', () => onDoubleTap(DoubleDetails(pointerEvent: event)));
_reset();
}
void _clearTrackers() {
_trackers.values.toList().forEach(_reject);
assert(_trackers.isEmpty);
}
void _freezeTracker(_TapTracker tracker) {
tracker.stopTrackingPointer(_handleEvent);
}
void _startDoubleTapTimer() {
_doubleTapTimer ??= Timer(kDoubleTapTimeout, _reset);
}
void _stopDoubleTapTimer() {
if (_doubleTapTimer != null) {
_doubleTapTimer.cancel();
_doubleTapTimer = null;
}
}
@override
String get debugDescription => 'double tap';
}
/// TapGesture represents a full gesture resulting from a single tap sequence,
/// as part of a [MultiTapGestureRecognizer]. Tap gestures are passive, meaning
/// that they will not preempt any other arena member in play.
class _TapGesture extends _TapTracker {
_TapGesture(
{this.gestureRecognizer, PointerEvent event, Duration longTapDelay})
: _lastPosition = event.position,
super(
event: event,
entry: GestureBinding.instance.gestureArena
.add(event.pointer, gestureRecognizer)) {
startTrackingPointer(handleEvent);
if (longTapDelay > Duration.zero) {
_timer = Timer(longTapDelay, () {
_timer = null;
gestureRecognizer._dispatchLongTap(event.pointer, _lastPosition);
});
}
}
final MultiTapGestureRecognizer gestureRecognizer;
bool _wonArena = false;
Timer _timer;
Offset _lastPosition;
Offset _finalPosition;
void handleEvent(PointerEvent event) {
assert(event.pointer == pointer);
if (event is PointerMoveEvent) {
if (!isWithinTolerance(event, kTouchSlop))
cancel();
else
_lastPosition = event.position;
} else if (event is PointerCancelEvent) {
cancel();
} else if (event is PointerUpEvent) {
stopTrackingPointer(handleEvent);
_finalPosition = event.position;
_check();
}
}
@override
void stopTrackingPointer(PointerRoute route) {
_timer?.cancel();
_timer = null;
super.stopTrackingPointer(route);
}
void accept() {
_wonArena = true;
_check();
}
void reject() {
stopTrackingPointer(handleEvent);
gestureRecognizer._dispatchCancel(pointer);
}
void cancel() {
// If we won the arena already, then entry is resolved, so resolving
// again is a no-op. But we still need to clean up our own state.
if (_wonArena)
reject();
else
entry.resolve(GestureDisposition.rejected); // eventually calls reject()
}
void _check() {
if (_wonArena && _finalPosition != null)
gestureRecognizer._dispatchTap(pointer, _finalPosition);
}
}
/// Recognizes taps on a per-pointer basis.
///
/// [MultiTapGestureRecognizer] considers each sequence of pointer events that
/// could constitute a tap independently of other pointers: For example, down-1,
/// down-2, up-1, up-2 produces two taps, on up-1 and up-2.
///
/// See also:
///
/// * [TapGestureRecognizer]
class MultiTapGestureRecognizer extends GestureRecognizer {
/// Creates a multi-tap gesture recognizer.
///
/// The [longTapDelay] defaults to [Duration.zero], which means
/// [onLongTapDown] is called immediately after [onTapDown].
MultiTapGestureRecognizer({
this.longTapDelay = Duration.zero,
Object debugOwner,
}) : super(debugOwner: debugOwner);
/// A pointer that might cause a tap has contacted the screen at a particular
/// location.
GestureMultiTapDownCallback onTapDown;
/// A pointer that will trigger a tap has stopped contacting the screen at a
/// particular location.
GestureMultiTapUpCallback onTapUp;
/// A tap has occurred.
GestureMultiTapCallback onTap;
/// The pointer that previously triggered [onTapDown] will not end up causing
/// a tap.
GestureMultiTapCancelCallback onTapCancel;
/// The amount of time between [onTapDown] and [onLongTapDown].
Duration longTapDelay;
/// A pointer that might cause a tap is still in contact with the screen at a
/// particular location after [longTapDelay].
GestureMultiTapDownCallback onLongTapDown;
final Map<int, _TapGesture> _gestureMap = <int, _TapGesture>{};
@override
void addPointer(PointerEvent event) {
assert(!_gestureMap.containsKey(event.pointer));
_gestureMap[event.pointer] = _TapGesture(
gestureRecognizer: this, event: event, longTapDelay: longTapDelay);
if (onTapDown != null)
invokeCallback<void>(
'onTapDown',
() => onTapDown(
event.pointer, TapDownDetails(globalPosition: event.position)));
}
@override
void acceptGesture(int pointer) {
assert(_gestureMap.containsKey(pointer));
_gestureMap[pointer].accept();
}
@override
void rejectGesture(int pointer) {
assert(_gestureMap.containsKey(pointer));
_gestureMap[pointer].reject();
assert(!_gestureMap.containsKey(pointer));
}
void _dispatchCancel(int pointer) {
assert(_gestureMap.containsKey(pointer));
_gestureMap.remove(pointer);
if (onTapCancel != null)
invokeCallback<void>('onTapCancel', () => onTapCancel(pointer));
}
void _dispatchTap(int pointer, Offset globalPosition) {
assert(_gestureMap.containsKey(pointer));
_gestureMap.remove(pointer);
if (onTapUp != null)
invokeCallback<void>('onTapUp',
() => onTapUp(pointer, TapUpDetails(globalPosition: globalPosition)));
if (onTap != null) invokeCallback<void>('onTap', () => onTap(pointer));
}
void _dispatchLongTap(int pointer, Offset lastPosition) {
assert(_gestureMap.containsKey(pointer));
if (onLongTapDown != null)
invokeCallback<void>(
'onLongTapDown',
() => onLongTapDown(
pointer, TapDownDetails(globalPosition: lastPosition)));
}
@override
void dispose() {
final List<_TapGesture> localGestures =
List<_TapGesture>.from(_gestureMap.values);
for (_TapGesture gesture in localGestures) gesture.cancel();
// Rejection of each gesture should cause it to be removed from our map
assert(_gestureMap.isEmpty);
super.dispose();
}
@override
String get debugDescription => 'multitap';
}

@ -0,0 +1,513 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:async' show Timer;
import 'package:flutter/src/gestures/arena.dart';
import 'package:flutter/src/gestures/constants.dart';
import 'package:flutter/src/gestures/events.dart';
import 'package:flutter/src/gestures/recognizer.dart';
import 'package:flutter/src/gestures/velocity_tracker.dart';
import './double_details.dart';
/// The possible states of a [ScaleGestureRecognizer].
enum _ScaleState {
/// The recognizer is ready to start recognizing a gesture.
ready,
/// The sequence of pointer events seen thus far is consistent with a scale
/// gesture but the gesture has not been accepted definitively.
possible,
/// The sequence of pointer events seen thus far has been accepted
/// definitively as a scale gesture.
accepted,
/// The sequence of pointer events seen thus far has been accepted
/// definitively as a scale gesture and the pointers established a focal point
/// and initial scale.
started,
}
/// Details for [GestureScaleStartCallback].
class ScaleStartDetails {
/// Creates details for [GestureScaleStartCallback].
///
/// The [focalPoint] argument must not be null.
ScaleStartDetails({this.focalPoint = Offset.zero})
: assert(focalPoint != null);
/// The initial focal point of the pointers in contact with the screen.
/// Reported in global coordinates.
final Offset focalPoint;
@override
String toString() => 'ScaleStartDetails(focalPoint: $focalPoint)';
}
/// Details for [GestureScaleUpdateCallback].
class ScaleUpdateDetails {
/// Creates details for [GestureScaleUpdateCallback].
///
/// The [focalPoint], [scale], [rotation] arguments must not be null. The [scale]
/// argument must be greater than or equal to zero.
ScaleUpdateDetails({
this.focalPoint = Offset.zero,
this.scale = 1.0,
this.rotation = 0.0,
this.pointerEvent,
this.pointCount = 1,
}) : assert(scale != null && scale >= 0.0),
assert(rotation != null);
/// The focal point of the pointers in contact with the screen. Reported in
/// global coordinates.
final Offset focalPoint;
/// The scale implied by the pointers in contact with the screen. A value
/// greater than or equal to zero.
final double scale;
/// The angle implied by the first two pointers to enter in contact with
/// the screen. Expressed in radians.
final double rotation;
final PointerEvent pointerEvent;
final int pointCount;
@override
String toString() =>
'ScaleUpdateDetails(focalPoint: $focalPoint, scale: $scale, rotation: $rotation, pointerEvent: $pointerEvent, pointCount: $pointCount)';
}
/// Details for [GestureScaleEndCallback].
class ScaleEndDetails {
/// Creates details for [GestureScaleEndCallback].
///
/// The [velocity] argument must not be null.
ScaleEndDetails({this.velocity = Velocity.zero}) : assert(velocity != null);
/// The velocity of the last pointer to be lifted off of the screen.
final Velocity velocity;
@override
String toString() => 'ScaleEndDetails(velocity: $velocity)';
}
/// Signature for when the pointers in contact with the screen have established
/// a focal point and initial scale of 1.0.
typedef GestureScaleStartCallback = void Function(ScaleStartDetails details);
/// Signature for when the pointers in contact with the screen have indicated a
/// new focal point and/or scale.
typedef GestureScaleUpdateCallback = void Function(ScaleUpdateDetails details);
/// Signature for when the pointers are no longer in contact with the screen.
typedef GestureScaleEndCallback = void Function(ScaleEndDetails details);
bool _isFlingGesture(Velocity velocity) {
assert(velocity != null);
final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
}
/// Defines a line between two pointers on screen.
///
/// [_LineBetweenPointers] is an abstraction of a line between two pointers in
/// contact with the screen. Used to track the rotation of a scale gesture.
class _LineBetweenPointers {
/// Creates a [_LineBetweenPointers]. None of the [pointerStartLocation], [pointerStartId]
/// [pointerEndLocation] and [pointerEndId] must be null. [pointerStartId] and [pointerEndId]
/// should be different.
_LineBetweenPointers(
{this.pointerStartLocation = Offset.zero,
this.pointerStartId = 0,
this.pointerEndLocation = Offset.zero,
this.pointerEndId = 1})
: assert(pointerStartLocation != null && pointerEndLocation != null),
assert(pointerStartId != null && pointerEndId != null),
assert(pointerStartId != pointerEndId);
// The location and the id of the pointer that marks the start of the line.
final Offset pointerStartLocation;
final int pointerStartId;
// The location and the id of the pointer that marks the end of the line.
final Offset pointerEndLocation;
final int pointerEndId;
}
/// Recognizes a scale gesture.
///
/// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
/// calculates their focal point, indicated scale, and rotation. When a focal
/// pointer is established, the recognizer calls [onStart]. As the focal point,
/// scale, rotation change, the recognizer calls [onUpdate]. When the pointers
/// are no longer in contact with the screen, the recognizer calls [onEnd].
class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
/// Create a gesture recognizer for interactions intended for scaling content.
ScaleGestureRecognizer({Object debugOwner}) : super(debugOwner: debugOwner);
/// The pointers in contact with the screen have established a focal point and
/// initial scale of 1.0.
GestureScaleStartCallback onStart;
/// The pointers in contact with the screen have indicated a new focal point
/// and/or scale.
GestureScaleUpdateCallback onUpdate;
/// The pointers are no longer in contact with the screen.
GestureScaleEndCallback onEnd;
_ScaleState _state = _ScaleState.ready;
Offset _initialFocalPoint;
Offset _currentFocalPoint;
double _initialSpan;
double _currentSpan;
_LineBetweenPointers _initialLine;
_LineBetweenPointers _currentLine;
Map<int, Offset> _pointerLocations;
List<int> _pointerQueue;
int pointCount = 0;
bool isOnlyOnePoint = true; //
/// --------------------------DoubleTap-start--------------------------
/// Called when the user has tapped the screen at the same location twice in
/// quick succession.
GestureDoubleTapCallback onDoubleTap;
/// is track pointer
///
bool _isTrackingPointer = false;
bool isFirstTap = true;
/// timer
Timer _doubleTapTimer;
/// start track pointer
///
void startDoubleTracking() {
if (!_isTrackingPointer) {
_isTrackingPointer = true;
}
}
/// stop track pointer
///
void stopDoubleTracking() {
if (_isTrackingPointer) {
_isTrackingPointer = false;
}
}
/// is two point within tolerance
///
bool isWithinTolerance(PointerEvent event, double tolerance) {
final Offset offset = event.position - _initialFocalPoint;
return offset.distance <= tolerance;
}
void _reset() {
_stopDoubleTapTimer();
if (!isFirstTap) {
// Note, order is important below in order for the resolve -> reject logic
// to work properly.
isFirstTap = true;
}
}
void _registerFirstTap() {
_startDoubleTapTimer();
// Note, order is important below in order for the clear -> reject logic to
// work properly.
isFirstTap = false;
}
void _registerSecondTap(PointerEvent event) {
if (onDoubleTap != null)
invokeCallback<void>(
'onDoubleTap', () => onDoubleTap(DoubleDetails(pointerEvent: event)));
_reset();
}
void _startDoubleTapTimer() {
_doubleTapTimer ??= Timer(kDoubleTapTimeout, _reset);
}
void _stopDoubleTapTimer() {
if (_doubleTapTimer != null) {
_doubleTapTimer.cancel();
_doubleTapTimer = null;
}
}
void _reject() {
if (!isFirstTap) _reset();
}
void _doubleTapAddPoniter(PointerEvent event) {
_stopDoubleTapTimer();
if (event is PointerUpEvent) {
if (isFirstTap) {
_registerFirstTap();
} else {
_registerSecondTap(event);
}
} else if (event is PointerMoveEvent) {
if (!isWithinTolerance(event, kDoubleTapTouchSlop)) _reject();
} else if (event is PointerCancelEvent) {
_reject();
}
}
/// --------------------------DoubleTap-end--------------------------
/// A queue to sort pointers in order of entrance
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
double get _scaleFactor =>
_initialSpan > 0.0 ? _currentSpan / _initialSpan : 1.0;
double _computeRotationFactor() {
if (_initialLine == null || _currentLine == null) {
return 0.0;
}
final double fx = _initialLine.pointerStartLocation.dx;
final double fy = _initialLine.pointerStartLocation.dy;
final double sx = _initialLine.pointerEndLocation.dx;
final double sy = _initialLine.pointerEndLocation.dy;
final double nfx = _currentLine.pointerStartLocation.dx;
final double nfy = _currentLine.pointerStartLocation.dy;
final double nsx = _currentLine.pointerEndLocation.dx;
final double nsy = _currentLine.pointerEndLocation.dy;
final double angle1 = math.atan2(fy - sy, fx - sx);
final double angle2 = math.atan2(nfy - nsy, nfx - nsx);
return angle2 - angle1;
}
@override
void addPointer(PointerEvent event) {
startTrackingPointer(event.pointer);
_velocityTrackers[event.pointer] = VelocityTracker();
if (_state == _ScaleState.ready) {
_state = _ScaleState.possible;
_initialSpan = 0.0;
_currentSpan = 0.0;
_pointerLocations = <int, Offset>{};
_pointerQueue = <int>[];
}
_doubleTapAddPoniter(event);
// else if (_state == _ScaleState.accepted && _pointerQueue.length == 0) {
// resolve(GestureDisposition.accepted);
// }
}
@override
void handleEvent(PointerEvent event) {
assert(_state != _ScaleState.ready);
bool didChangeConfiguration = false;
bool shouldStartIfAccepted = false;
if (event is PointerMoveEvent) {
final VelocityTracker tracker = _velocityTrackers[event.pointer];
assert(tracker != null);
if (!event.synthesized)
tracker.addPosition(event.timeStamp, event.position);
_pointerLocations[event.pointer] = event.position;
shouldStartIfAccepted = true;
pointCount = _pointerLocations.keys.length;
if (pointCount <= 1 && onUpdate != null && isOnlyOnePoint && !isFirstTap)
invokeCallback<void>(
'onUpdate',
() => onUpdate(ScaleUpdateDetails(
scale: _scaleFactor,
focalPoint: _currentFocalPoint,
rotation: _computeRotationFactor(),
pointerEvent: event,
pointCount: pointCount)));
} else if (event is PointerDownEvent) {
isOnlyOnePoint = _pointerQueue.length == 0;
_pointerLocations[event.pointer] = event.position;
_pointerQueue.add(event.pointer);
didChangeConfiguration = true;
shouldStartIfAccepted = true;
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
_pointerLocations.remove(event.pointer);
_pointerQueue.remove(event.pointer);
didChangeConfiguration = true;
}
_updateLines();
_update();
if (!didChangeConfiguration || _reconfigure(event.pointer)) {
_advanceStateMachine(shouldStartIfAccepted, event);
}
stopTrackingIfPointerNoLongerDown(event);
}
void _update() {
final int count = _pointerLocations.keys.length;
// Compute the focal point
Offset focalPoint = Offset.zero;
for (int pointer in _pointerLocations.keys)
focalPoint += _pointerLocations[pointer];
_currentFocalPoint =
count > 0 ? focalPoint / count.toDouble() : Offset.zero;
// Span is the average deviation from focal point
double totalDeviation = 0.0;
for (int pointer in _pointerLocations.keys)
totalDeviation +=
(_currentFocalPoint - _pointerLocations[pointer]).distance;
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
}
/// Updates [_initialLine] and [_currentLine] accordingly to the situation of
/// the registered pointers
void _updateLines() {
final int count = _pointerLocations.keys.length;
assert(_pointerQueue.length >= count);
/// In case of just one pointer registered, reconfigure [_initialLine]
if (count < 2) {
_initialLine = _currentLine;
} else if (_initialLine != null &&
_initialLine.pointerStartId == _pointerQueue[0] &&
_initialLine.pointerEndId == _pointerQueue[1]) {
/// Rotation updated, set the [_currentLine]
_currentLine = _LineBetweenPointers(
pointerStartId: _pointerQueue[0],
pointerStartLocation: _pointerLocations[_pointerQueue[0]],
pointerEndId: _pointerQueue[1],
pointerEndLocation: _pointerLocations[_pointerQueue[1]]);
} else {
/// A new rotation process is on the way, set the [_initialLine]
_initialLine = _LineBetweenPointers(
pointerStartId: _pointerQueue[0],
pointerStartLocation: _pointerLocations[_pointerQueue[0]],
pointerEndId: _pointerQueue[1],
pointerEndLocation: _pointerLocations[_pointerQueue[1]]);
_currentLine = null;
}
}
bool _reconfigure(int pointer) {
_initialFocalPoint = _currentFocalPoint;
_initialSpan = _currentSpan;
_initialLine = _currentLine;
if (_state == _ScaleState.started) {
if (onEnd != null) {
final VelocityTracker tracker = _velocityTrackers[pointer];
assert(tracker != null);
Velocity velocity = tracker.getVelocity();
if (_isFlingGesture(velocity)) {
final Offset pixelsPerSecond = velocity.pixelsPerSecond;
if (pixelsPerSecond.distanceSquared >
kMaxFlingVelocity * kMaxFlingVelocity)
velocity = Velocity(
pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) *
kMaxFlingVelocity);
invokeCallback<void>(
'onEnd', () => onEnd(ScaleEndDetails(velocity: velocity)));
} else {
invokeCallback<void>(
'onEnd', () => onEnd(ScaleEndDetails(velocity: Velocity.zero)));
}
}
_state = _ScaleState.accepted;
return false;
}
return true;
}
void _advanceStateMachine(
bool shouldStartIfAccepted, PointerEvent pointerEvent) {
if (_state == _ScaleState.ready) _state = _ScaleState.possible;
if (_state == _ScaleState.possible) {
final double spanDelta = (_currentSpan - _initialSpan).abs();
final double focalPointDelta =
(_currentFocalPoint - _initialFocalPoint).distance;
if (spanDelta > kScaleSlop || focalPointDelta > kPanSlop)
resolve(GestureDisposition.accepted);
} else if (_state.index >= _ScaleState.accepted.index) {
resolve(GestureDisposition.accepted);
}
if (_state == _ScaleState.accepted && shouldStartIfAccepted) {
_state = _ScaleState.started;
_dispatchOnStartCallbackIfNeeded();
}
// if (_state == _ScaleState.started && onUpdate != null)
if (_state == _ScaleState.started && onUpdate != null)
invokeCallback<void>(
'onUpdate',
() => onUpdate(ScaleUpdateDetails(
scale: _scaleFactor,
focalPoint: _currentFocalPoint,
rotation: _computeRotationFactor(),
pointerEvent: pointerEvent,
pointCount: pointCount)));
}
void _dispatchOnStartCallbackIfNeeded() {
assert(_state == _ScaleState.started);
if (onStart != null)
invokeCallback<void>('onStart',
() => onStart(ScaleStartDetails(focalPoint: _currentFocalPoint)));
}
@override
void acceptGesture(int pointer) {
resolve(GestureDisposition.accepted);
if (_state == _ScaleState.possible) {
_state = _ScaleState.started;
_dispatchOnStartCallbackIfNeeded();
}
}
@override
void rejectGesture(int pointer) {
stopTrackingPointer(pointer);
}
@override
void didStopTrackingLastPointer(int pointer) {
switch (_state) {
case _ScaleState.possible:
resolve(GestureDisposition.rejected);
break;
case _ScaleState.ready:
assert(false); // We should have not seen a pointer yet
break;
case _ScaleState.accepted:
break;
case _ScaleState.started:
assert(false); // We should be in the accepted state when user is done
break;
}
_state = _ScaleState.ready;
}
@override
void dispose() {
_velocityTrackers.clear();
_reset();
super.dispose();
}
@override
String get debugDescription => 'scale';
}

@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import './custom_gesture_detector.dart' as gd;
class ScaleChangedModel {
double scale;
Offset offset;
ScaleChangedModel({this.scale, this.offset});
@override
String toString() {
return 'ScaleChangedModel(scale: $scale, offset:$offset)';
}
}
class TouchableContainer extends StatefulWidget {
final Widget child;
final bool doubleTapStillScale;
///
///margin
final EdgeInsets margin;
ValueChanged<ScaleChangedModel> scaleChangedCallback; //
Function() pointerDownCallback; //
Function() pointerUpCallback; //
TouchableContainer({
Key key,
this.child,
this.margin = const EdgeInsets.all(0),
this.pointerDownCallback,
this.pointerUpCallback,
this.scaleChangedCallback,
this.doubleTapStillScale,
});
_TouchableContainerState createState() => _TouchableContainerState();
}
class _TouchableContainerState extends State<TouchableContainer>
with SingleTickerProviderStateMixin {
double _kMinFlingVelocity = 800.0;
AnimationController _controller;
Animation<Offset> _flingAnimation;
Offset _offset = Offset.zero;
double _scale = 1.0;
Offset _normalizedOffset;
double _previousScale;
Offset doubleDownPositon;
@override
void initState() {
super.initState();
_controller = new AnimationController(vsync: this)..addListener(_handleFlingAnimation);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
// The maximum offset value is 0,0. If the size of this renderer's box is w,h
// then the minimum offset value is w - _scale * w, h - _scale * h.
//000
Offset _clampOffset(Offset offset) {
final Size size = context.size; //
final Offset minOffset = new Offset(size.width, size.height) * (1.0 - _scale);
return new Offset(offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0));
}
void _handleFlingAnimation() {
setState(() {
_offset = _flingAnimation.value;
});
}
void _handleOnScaleStart(gd.ScaleStartDetails details) {
setState(() {
_previousScale = _scale;
_normalizedOffset = (details.focalPoint - _offset) / _scale;
// The fling animation stops if an input gesture starts.
_controller.stop();
});
}
void _handleOnScaleUpdate(gd.ScaleUpdateDetails details) {
setState(() {
if (details.pointCount > 1) {
_scale = (_previousScale * details.scale).clamp(1.0, double.infinity);
}
// Ensure that image location under the focal point stays in the same place despite scaling.
_offset = _clampOffset(details.focalPoint - _normalizedOffset * _scale);
});
ScaleChangedModel model = new ScaleChangedModel(scale: _scale, offset: _offset);
if (widget.scaleChangedCallback != null) widget.scaleChangedCallback(model);
}
void _handleOnScaleEnd(gd.ScaleEndDetails details) {
final double magnitude = details.velocity.pixelsPerSecond.distance;
if (magnitude < _kMinFlingVelocity) return;
final Offset direction = details.velocity.pixelsPerSecond / magnitude;
final double distance = (Offset.zero & context.size).shortestSide;
_flingAnimation =
new Tween<Offset>(begin: _offset, end: _clampOffset(_offset + direction * distance))
.animate(_controller);
_controller
..value = 0.0
..fling(velocity: magnitude / 1000.0);
}
void _onDoubleTap(gd.DoubleDetails details) {
_normalizedOffset = (details.pointerEvent.position - _offset) / _scale;
if (!widget.doubleTapStillScale && _scale != 1.0) {
setState(() {
_scale = 1.0;
_offset = Offset.zero;
});
ScaleChangedModel model = new ScaleChangedModel(scale: _scale, offset: _offset);
if (widget.scaleChangedCallback != null) widget.scaleChangedCallback(model);
return;
}
setState(() {
if (widget.doubleTapStillScale) {
_scale *= (1 + 0.5);
} else {
_scale *= (2);
}
// Ensure that image location under the focal point stays in the same place despite scaling.
// _offset = doubleDownPositon;
_offset = _clampOffset(details.pointerEvent.position - _normalizedOffset * _scale);
});
ScaleChangedModel model = new ScaleChangedModel(scale: _scale, offset: _offset);
if (widget.scaleChangedCallback != null) widget.scaleChangedCallback(model);
}
@override
Widget build(BuildContext context) {
return new gd.GestureDetector(
// onPanDown: _onPanDown,
onDoubleTap: _onDoubleTap,
onScaleStart: _handleOnScaleStart,
onScaleUpdate: _handleOnScaleUpdate,
pointerDownCallback: widget.pointerDownCallback,
pointerUpCallback: widget.pointerUpCallback,
// onScaleEnd: _handleOnScaleEnd,
child: Container(
margin: widget.margin,
constraints: const BoxConstraints(
minWidth: double.maxFinite,
minHeight: double.infinity,
),
child: new Transform(
transform: new Matrix4.identity()
..translate(_offset.dx, _offset.dy)
..scale(_scale, _scale, 1.0),
child: widget.child),
),
);
}
}

@ -0,0 +1,4 @@
library flutter_drag_scale;
export './core/drag_scale_widget.dart';
export './core/touchable_container.dart' show ScaleChangedModel;

@ -0,0 +1,60 @@
name: flutter_drag_scale
description: A new flutter plugin project.
version: 0.0.1
author: liucheng <liu520vr@gmail.com>
homepage: https://github.com/LiuC520/flutter_drag_scale
environment:
sdk: ">=2.0.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# This section identifies this Flutter project as a plugin project.
# The androidPackage and pluginClass identifiers should not ordinarily
# be modified. They are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
androidPackage: com.example.flutter_drag_scale
pluginClass: FlutterDragScalePlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.io/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.io/custom-fonts/#from-packages

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

@ -0,0 +1,7 @@
import 'package:flutter_test/flutter_test.dart';
void main() {
test('getPlatformVersion', () async {
// expect(await FlutterDragScale.platformVersion, '42');
});
}

@ -11,7 +11,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hyzp_ybqx/widget/my_superplayer.dart';
import 'package:keyboard_avoider/keyboard_avoider.dart';
import 'package:photo_view/photo_view.dart';
// import 'package:photo_view/photo_view.dart';
import '../../../components/commonFun.dart';
import '../../../components/dioFun.dart';
@ -254,29 +254,6 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
}
}
// 使 cached_network_image
// 使 flutter_drag_scale PhotoView
Widget getNetworkImage(String url) {
return CachedNetworkImage(
imageUrl: url,
alignment: Alignment.topCenter,
imageBuilder: (context, imageProvider) => DragScaleContainer(
doubleTapStillScale: true, child: Image(image: imageProvider, fit: BoxFit.fill)
// child: Image(
// image: NetworkImage(
// 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),
// ),
),
// imageBuilder: (context, imageProvider) => PhotoView(
// imageProvider: imageProvider,
// ),
//placeholder: (context, url) => CircularProgressIndicator(),
placeholder: (context, url) =>
getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0),
errorWidget: (context, url, error) => Icon(Icons.error),
);
}
//3
Widget getWztpSmxx(int index) {
return Container(
@ -541,6 +518,127 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
);
}
// 使 cached_network_image
// 使 flutter_drag_scale PhotoView
// Widget getNetworkImage0(String url) {
// return CachedNetworkImage(
// imageUrl: url,
// alignment: Alignment.topCenter,
// imageBuilder: (context, imageProvider) => DragScaleContainer(
// doubleTapStillScale: true, child: Image(image: imageProvider, fit: BoxFit.fill)
// // child: Image(
// // image: NetworkImage(
// // 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),
// // ),
// ),
// // imageBuilder: (context, imageProvider) => PhotoView(
// // imageProvider: imageProvider,
// // ),
// //placeholder: (context, url) => CircularProgressIndicator(),
// placeholder: (context, url) =>
// getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0),
// errorWidget: (context, url, error) => Icon(Icons.error),
// );
// }
// 使 Listener onPointerDownonPointerUp
// Widget getNetworkImage1(String url) {
// return ClipRect(
// child: Listener(
// child: PhotoView.customChild(
// // imageProvider: NetworkImage(widget.imageUrl),
// // imageProvider: CachedNetworkImageProvider(widget.imageUrl),
// minScale: 1.0,
// backgroundDecoration: BoxDecoration(color: Colors.white),
// child: CachedNetworkImage(
// alignment: Alignment.center,
// imageUrl: getMediaUrl(url),
// fit: BoxFit.fill,
// placeholder: (context, url) =>
// getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0),
// errorWidget: (context, url, error) => Icon(Icons.error),
// ),
// scaleStateChangedCallback: (PhotoViewScaleState statue) {
// print('PhotoViewScaleState 状态:$statue');
// switch (statue) {
// case PhotoViewScaleState.originalSize:
// case PhotoViewScaleState.zoomedOut:
// case PhotoViewScaleState.initial:
// bZoomedInit = false;
// break;
// default:
// bZoomedInit = true;
// break;
// }
// print("bZoomedIn = $bZoomedInit");
// },
// ),
// onPointerDown: (event) {
// scrollPhysics = null;
// if (bZoomedInit) {
// scrollPhysics = NeverScrollableScrollPhysics();
// setState(() {});
// print("down $event");
// }
// },
// // onPointerMove: (event) => print("move $event"),
// onPointerUp: (event) {
// scrollPhysics = null;
// if (bZoomedInit) {
// scrollPhysics = null;
// setState(() {});
// print("up $event");
// }
// },
// ),
// );
// }
// 使 my_flutter_drag_scale Listview
Widget getNetworkImage(String url) {
return CachedNetworkImage(
imageUrl: url,
alignment: Alignment.topCenter,
placeholder: (context, url) =>
getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0),
errorWidget: (context, url, error) => Icon(Icons.error),
imageBuilder: (context, imageProvider) {
return DragScaleContainer(
doubleTapStillScale: false,
child: Image(image: imageProvider, fit: BoxFit.fill),
scaleChangedCallback: (ScaleChangedModel model) {
print("model.scale = ${model.scale}");
if (1.0 == model.scale) {
bZoomedInit = true;
scrollPhysics = PageScrollPhysics();
} else {
bZoomedInit = false;
scrollPhysics = NeverScrollableScrollPhysics();
}
setState(() {});
},
pointerDownCallback: () {
if (bZoomedInit) {
if (scrollPhysics != PageScrollPhysics()) {
scrollPhysics = PageScrollPhysics();
setState(() {});
}
} else {
if (scrollPhysics != NeverScrollableScrollPhysics()) {
scrollPhysics = NeverScrollableScrollPhysics();
setState(() {});
}
}
},
pointerUpCallback: () {
scrollPhysics = PageScrollPhysics();
setState(() {});
},
);
},
);
}
//2
Widget getWztp(int index) {
print('ratioList[index] = ${ratioList[index]}');
@ -553,12 +651,12 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
height: ScreenUtil().setHeight(22 + 1022 * g_radioImage), //
// height: ScreenUtil()
// .setHeight(22 + 1022 * (ratioList.isNotEmpty ? ratioList[index] : 9 / 16)),
decoration: BoxDecoration(
//color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.all(
// Radius.circular(12),
// ),
// ),
),
Positioned(
//left: ScreenUtil().setWidth(25),
@ -569,9 +667,10 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
// ScreenUtil().setHeight(1022 * (ratioList.isNotEmpty ? ratioList[index] : 9 / 16)),
height: ScreenUtil().setHeight(1022 * g_radioImage),
//
// child: getNetworkImage(getMediaUrl(listGetZpjl[index]['pic_url'])),
child: getNetworkImage(getMediaUrl(listGetZpjl[index]['pic_url'])),
),
)
),
],
);
}
@ -597,6 +696,9 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
double _marginVertical5 = 10;
double _marginVertical6 = 20;
ScrollPhysics scrollPhysics;
bool bZoomedInit = true; //
Future getTopTabsMap() async {
//map
topTabs_map.forEach((key, value) {
@ -628,48 +730,48 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
.add(CarNumberAndCpysItems(index, topTabs_map['cpysText_List'][index]));
//Tab
topTabs_map['listView_List'].add(
//flutterOverflow
KeyboardAvoider(
autoScroll: true,
child: Container(
decoration: new BoxDecoration(
color: Color.fromRGBO(244, 244, 244, 1),
),
child: Column(
children: <Widget>[
//1
getHdAndPlay(index),
//2
getWztp(index),
SizedBox(height: ScreenUtil().setHeight(_marginVer)),
//3
getWztpSmxx(index),
SizedBox(height: ScreenUtil().setHeight(_marginVer)),
//4
widget.hyshlx == 'hyfh' ? getHycsResult(index) : SizedBox.shrink(),
//RadioRadioListItems
//5-6
HyshGroup(
index: index,
hyshlx: hyshlx,
fontSize: _fontSize,
size: Size(_listTileHeight, _listTileHeight),
id: widget.id,
selectedRadio:
hyshlx == 'hyfh' && mapGetHycsShenheData['title'] == "非黑烟车" ? 1 : 0,
),
//RadioRadioListItems
// SizedBox(height: 6),
// Divider(height: 1.0, color: Colors.blue),
// SizedBox(height: 10),
// //9
// getShqr(index),
],
),
),
),
);
topTabs_map['listView_List'].add(SizedBox.shrink()
//flutterOverflow
// KeyboardAvoider(
// autoScroll: true,
// child: Container(
// decoration: new BoxDecoration(
// color: Color.fromRGBO(244, 244, 244, 1),
// ),
// child: Column(
// children: <Widget>[
// //1
// getHdAndPlay(index),
// //2
// getWztp(index),
// SizedBox(height: ScreenUtil().setHeight(_marginVer)),
// //3
// getWztpSmxx(index),
// SizedBox(height: ScreenUtil().setHeight(_marginVer)),
// //4
// widget.hyshlx == 'hyfh' ? getHycsResult(index) : SizedBox.shrink(),
// //RadioRadioListItems
// //5-6
// HyshGroup(
// index: index,
// hyshlx: hyshlx,
// fontSize: _fontSize,
// size: Size(_listTileHeight, _listTileHeight),
// id: widget.id,
// selectedRadio:
// hyshlx == 'hyfh' && mapGetHycsShenheData['title'] == "非黑烟车" ? 1 : 0,
// ),
// //RadioRadioListItems
// // SizedBox(height: 6),
// // Divider(height: 1.0, color: Colors.blue),
// // SizedBox(height: 10),
// // //9
// // getShqr(index),
// ],
// ),
// ),
// ),
);
}
}
@ -830,10 +932,66 @@ class _HyshPageState extends State<HyshContentNew> with SingleTickerProviderStat
//type 'Container' is not a subtype of type 'PreferredSizeWidget'
body: listGetZpjl.isNotEmpty
? TabBarView(
controller: _tabController, //TabControllertab
physics: NeverScrollableScrollPhysics(), //TabBarViewTabBarView-OK
children:
(topTabs_map['listView_List'].isNotEmpty) ? topTabs_map['listView_List'] : [],
controller: _tabController,
//TabControllertab
physics: NeverScrollableScrollPhysics(),
//TabBarViewTabBarView-OK
// children:
// (topTabs_map['listView_List'].isNotEmpty) ? topTabs_map['listView_List'] : [],
// https://blog.csdn.net/shulianghan/article/details/104953053
//
// int length :
// E generator(int index) : , index
// List list_generate = List.generate(3, ( index ) => index * 3);
// children: List.generate(3, (index) => Container()),
children: List.generate(topTabs_map['listView_List'].length, (index) {
return ListView(
// physics: NeverScrollableScrollPhysics(), // ListView
// physics: null, // ListView
physics: scrollPhysics,
children: [
Container(
decoration: new BoxDecoration(
color: Color.fromRGBO(244, 244, 244, 1),
),
child: Column(
children: <Widget>[
//1
getHdAndPlay(index),
//2
getWztp(index),
SizedBox(height: ScreenUtil().setHeight(_marginVer)),
//3
getWztpSmxx(index),
SizedBox(height: ScreenUtil().setHeight(_marginVer)),
//4
widget.hyshlx == 'hyfh' ? getHycsResult(index) : SizedBox.shrink(),
//RadioRadioListItems
//5-6
HyshGroup(
index: index,
hyshlx: hyshlx,
fontSize: _fontSize,
size: Size(_listTileHeight, _listTileHeight),
id: widget.id,
selectedRadio:
hyshlx == 'hyfh' && mapGetHycsShenheData['title'] == "非黑烟车"
? 1
: 0,
),
//RadioRadioListItems
// SizedBox(height: 6),
// Divider(height: 1.0, color: Colors.blue),
// SizedBox(height: 10),
// //9
// getShqr(index),
],
),
),
],
);
}),
)
: getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), //
),

@ -317,11 +317,9 @@ packages:
flutter_drag_scale:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "562ac559370da547783c2c5b1c18c931adddb8a4"
url: "https://github.com/mjl0602/flutter_drag_scale.git"
source: git
path: "lib/my_flutter_drag_scale"
relative: true
source: path
version: "0.0.1"
flutter_easyrefresh:
dependency: "direct main"
@ -620,13 +618,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.8"
photo_view:
dependency: "direct main"
description:
name: photo_view
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.10.3"
platform:
dependency: transitive
description:

@ -129,13 +129,14 @@ dependencies:
image_picker: ^0.6.7+22
cached_network_image: ^2.4.1
flutter_easyrefresh: ^2.1.8
photo_view: ^0.10.3
# photo_view: ^0.10.3
flustars: ^0.3.3
keyboard_avoider: ^0.1.2
scroll_to_index: ^1.0.6
flutter_drag_scale:
path: ./lib/my_flutter_drag_scale
#git: https://github.com/LiuC520/flutter_drag_scale.git
git: https://github.com/mjl0602/flutter_drag_scale.git
#git: https://github.com/mjl0602/flutter_drag_scale.git
# flutter_bmfmap: ^1.0.2
# 准备使用自定义 flutter_bmfmap: ^1.0.2以适应IOS版加载文本标识

Loading…
Cancel
Save