Repository: zhouzaihang/flutter_hand_tracking_plugin
Branch: master
Commit: d4e65eb06197
Files: 70
Total size: 11.0 MB
Directory structure:
gitextract_mt_x1c8l/
├── .gitattributes
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── android/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── libs/
│ │ └── hand_tracking_aar.aar
│ ├── settings.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ ├── hand_landmark.tflite
│ │ ├── handtrackinggpu.binarypb
│ │ ├── palm_detection.tflite
│ │ └── palm_detection_labelmap.txt
│ └── kotlin/
│ └── xyz/
│ └── zhzh/
│ └── flutter_hand_tracking_plugin/
│ ├── FlutterHandTrackingPlugin.kt
│ └── HandTrackingViewFactory.kt
├── example/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── android/
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── build.gradle
│ │ │ └── src/
│ │ │ ├── debug/
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin/
│ │ │ │ │ └── xyz/
│ │ │ │ │ └── zhzh/
│ │ │ │ │ └── flutter_hand_tracking_plugin_example/
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ └── values/
│ │ │ │ └── styles.xml
│ │ │ └── profile/
│ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ └── settings.gradle
│ ├── ios/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Runner/
│ │ │ ├── AppDelegate.h
│ │ │ ├── AppDelegate.m
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── LaunchImage.imageset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── README.md
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ └── main.m
│ │ ├── Runner.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ └── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── Runner.xcscheme
│ │ └── Runner.xcworkspace/
│ │ └── contents.xcworkspacedata
│ ├── lib/
│ │ └── main.dart
│ ├── pubspec.yaml
│ └── test/
│ └── widget_test.dart
├── flutter_hand_tracking_plugin/
│ └── android/
│ └── flutter_hand_tracking_plugin.iml
├── flutter_hand_tracking_plugin.iml
├── ios/
│ ├── .gitignore
│ ├── Assets/
│ │ └── .gitkeep
│ ├── Classes/
│ │ ├── FlutterHandTrackingPlugin.h
│ │ └── FlutterHandTrackingPlugin.m
│ └── flutter_hand_tracking_plugin.podspec
├── lib/
│ ├── HandGestureRecognition.dart
│ ├── flutter_hand_tracking_plugin.dart
│ └── gen/
│ ├── landmark.pb.dart
│ ├── landmark.pbenum.dart
│ ├── landmark.pbjson.dart
│ └── landmark.pbserver.dart
├── protos/
│ ├── landmark.proto
│ └── regenerate.md
└── pubspec.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.aar filter=lfs diff=lfs merge=lfs -text
================================================
FILE: .gitignore
================================================
.idea/
.DS_Store
.dart_tool/
.packages
.pub/
build/
pubspec.lock
================================================
FILE: .metadata
================================================
# 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: 9f5ff2306bb3e30b2b98eee79cd231b1336f41f4
channel: stable
project_type: plugin
================================================
FILE: CHANGELOG.md
================================================
## 0.0.1
* TODO: Describe initial release.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Flutter Hand Tracking Plugin
> 开发时间比较古早,支持的版本已经很老了,慎用!
这个 `Flutter Hand Tracking Plugin` 是为了实现调用 `Andorid` 设备摄像头精确追踪并识别十指的运动路径/轨迹和手势动作, 且输出22个手部关键点以支持更多手势自定义. 基于这个包可以编写业务逻辑将手势信息实时转化为指令信息: 一二三四五, rock, spiderman...同时基于 `Flutter` 可以对不同手势编写不同特效. 可用于短视频直播特效, 智能硬件等领域, 为人机互动带来更自然丰富的体验.

> 源码托管于 Github: [https://github.com/zhouzaihang/flutter_hand_tracking_plugin](https://github.com/zhouzaihang/flutter_hand_tracking_plugin)
> [Bilibili 演示](https://www.bilibili.com/video/av92842489/)
## 使用
> 项目中使用的 `android/libs/hand_tracking_aar.aar` 托管在 `git-lfs`, 在你下载之后需要确认 `.aar` 文件是否存在(且文件超过100MB). 如果你没有安装 `git-lfs` 你可能需要手动下载, 然后替换到你的项目路径中.
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
## 涉及到的技术
1. 编写一个 `Flutter Plugin Package`
1. 使用 `Docker` 配置 `MediaPipe` 开发环境
1. 在 `Gradle` 中使用 `MediaPipe`
1. `Flutter` 程序运行 `MediaPipe` 图
1. `Flutter` 页面中嵌入原生视图
1. `protobuf` 的使用
## 什么是 `Flutter Package`
`Flutter Package` 有以下两种类型:
`Dart Package`: 完全用 `Dart` 编写的包, 例如 `path` 包. 其中一些可能包含 `Flutter` 特定的功能, 这类包完全依赖于 `Flutter` 框架.
`Plugin Package`: 一类依赖于运行平台的包, 其中包含用 `Dart` 代码编写的 `API`, 并结合了针对 `Android` (使用 `Java` 或 `Kotlin`)和 `iOS` (使用 `ObjC` 或 `Swift`)平台特定的实现. 比如说 `battery` 包.
## 为什么需要 `Flutter Plugin Package`
`Flutter` 作为一个跨平台的 `UI` 框架, 本身是不能够直接调用原生的功能的. 如果需要使用原生系统的功能, 就需要对平台特定实现, 然后在 `Flutter` 的 `Dart` 层进行兼容.
此处需要使用调用摄像头和 `GPU` 实现业务. 所以使用 `Flutter Plugin Package`.
## `Flutter Plugin Package` 是如何工作的
以下是 `Flutter Plugin Package` 项目的目录:

- 其中 `pubspec.yaml` 用于添加 `Plugin` 可能会用到的依赖或者资源(图片, 字体等)
- `example` 目录下是一个完整的 `Flutter APP`, 用于测试编写的 `Plugin`
- 另外, 无论在一个 `Flutter app` 项目还是在一个 `Flutter Plugin` 项目中都会有三个目录 `android`, `ios` 和 `lib`. `lib` 目录用于存放 `Dart` 代码, 而另外两个目录则是用于存放平台特定实现的代码. `Flutter` 会运行根据实际运行平台来运行平台对应的代码, 然后使用 [Platform Channels](https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-kotlin-tab) 把代码运行的结果返回给 `Dart` 层.
以下是 `Flutter` 官方给出的一张 `Flutter` 架构图:

从架构图中可以看到 `Flutter` 之所以是一个跨平台框架是因为有 `Embedder` 作为操作系统适配层, `Engine` 层实现渲染引擎等功能, 而 `Framework` 层是一个用 `Dart` 实现的 `UI SDK`. 对于一个 `Flutter Plugin Package` 来说, 就是要在 `Embedder` 层用原生的平台特定实现, 并且在 `Dart` 层中封装为一个 `UI API`, 从而实现跨平台. `Embedder` 层并不能直接和 `Framework` 直接连接, 还必须经过 `Engine` 层的 `Platform Channels`.
使用 `Platform Channels` 在客户端(`UI`) 和主机(特定平台)之间传递的过程如下图所示:

## 新建 `Flutter Plugin Package`
1. 打开 `Android Studio`, 点击 `New Flutter Project`
1. 选择 `Flutter Plugin` 选项
1. 输入项目名字, 描述等信息
## 编写 `Android` 平台 `view`
首先在 `android/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin` 目录下创建两个 `kotlin` 文件: `FlutterHandTrackingPlugin.kt` 和 `HandTrackingViewFactory.kt` 文件.
### 编写 `Factory` 类
在 `HandTrackingViewFactory.kt` 中编写一个 `HandTrackingViewFactory` 类实现抽象类 `PlatformViewFactory`. 之后编写的 `Android` 平台组件都需要用这个 `Factory` 类来生成. 在生成视图的时候需要传入一个参数 `id` 来辨别视图(`id`会由 `Flutter` 创建并传递给 `Factory`):
``` Kotlin
package xyz.zhzh.flutter_hand_tracking_plugin
import android.content.Context
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class HandTrackingViewFactory(private val registrar: PluginRegistry.Registrar) :
PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
return FlutterHandTrackingPlugin(registrar, viewId)
}
}
```
### 编写 `AndroidView` 类
在 `FlutterHandTrackingPlugin.kt` 中编写 `FlutterHandTrackingPlugin` 实现 `PlatformView` 接口, 这个接口需要实现两个方法 `getView` 和 `dispose`.
`getView` 用于返回一个将要嵌入到 `Flutter` 界面的视图
`dispose` 则是在试图关闭的时候进行一些操作
首先要添加一个 `SurfaceView`:
``` kotlin
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
companion object {
private const val TAG = "HandTrackingPlugin"
private const val NAMESPACE = "plugins.zhzh.xyz/flutter_hand_tracking_plugin"
@JvmStatic
fun registerWith(registrar: Registrar) {
registrar.platformViewRegistry().registerViewFactory(
"$NAMESPACE/view",
HandTrackingViewFactory(registrar))
}
init { // Load all native libraries needed by the app.
System.loadLibrary("mediapipe_jni")
System.loadLibrary("opencv_java3")
}
}
private var previewDisplayView: SurfaceView = SurfaceView(r.context())
}
```
然后通过 `getView` 返回添加的 `SurfaceView`:
``` kotlin
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
// ...
override fun getView(): SurfaceView? {
return previewDisplayView
}
override fun dispose() {
// TODO: ViewDispose()
}
}
```
## 在 `Dart` 中调用原生实现的 `View`
> 打开 `plugin package` 项目的 `lib/flutter_hand_tracking_plugin.dart` 进行编辑(具体文件名依据新建项目时创建的包名).
在 `Flutter` 中调用原生的 `Android` 组件需要创建一个 `AndroidView` 并告诉它组建的注册名称, 创建 `AndroidView` 的时候, 会给组件分配一个 `id`, 这个 `id` 可以通过参数 `onPlatformViewCreated` 传入方法获得:
``` dart
AndroidView(
viewType: '$NAMESPACE/blueview',
onPlatformViewCreated: (id) => _id = id),
)
```
由于只实现了 `Android` 平台的组件, 在其他系统上并不可使用, 所以还需要获取 `defaultTargetPlatform` 来判断运行的平台:
``` dart
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hand_tracking_plugin/gen/landmark.pb.dart';
const NAMESPACE = "plugins.zhzh.xyz/flutter_hand_tracking_plugin";
typedef void HandTrackingViewCreatedCallback(
HandTrackingViewController controller);
class HandTrackingView extends StatelessWidget {
const HandTrackingView({@required this.onViewCreated})
: assert(onViewCreated != null);
final HandTrackingViewCreatedCallback onViewCreated;
@override
Widget build(BuildContext context) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return AndroidView(
viewType: "$NAMESPACE/view",
onPlatformViewCreated: (int id) => onViewCreated == null
? null
: onViewCreated(HandTrackingViewController._(id)),
);
case TargetPlatform.fuchsia:
case TargetPlatform.iOS:
default:
throw UnsupportedError(
"Trying to use the default webview implementation for"
" $defaultTargetPlatform but there isn't a default one");
}
}
}
```
上面使用 `typedef` 定义了一个 `HandTrackingViewCreatedCallback`, 传入的参数类型为 `HandTrackingViewController`, 这个 `controller` 用于管理对应 `AndroidView` 的 `id`:
``` dart
class HandTrackingViewController {
final MethodChannel _methodChannel;
HandTrackingViewController._(int id)
: _methodChannel = MethodChannel("$NAMESPACE/$id"),
_eventChannel = EventChannel("$NAMESPACE/$id/landmarks");
Future<String> get platformVersion async =>
await _methodChannel.invokeMethod("getPlatformVersion");
}
```
其中的 `MethodChannel` 用于调用 `Flutter Plugin Package` 的方法, 本次不需要使用到 `MethodChannel`, 所以不用关注.
## 使用 `Docker` 构建 `MediaPipe AAR` 并添加到项目中
`MediaPipe` 是一个 `Google` 发布的使用 `ML pipelines` 技术构建多个模型连接在一起的跨平台框架. (`Machine learning pipelines`: 简单说就是一套 `API` 解决各个模型/算法/`workflow` 之间的数据传输). `MediaPipe` 支持视频, 音频, 等任何 `time series data`([WiKi--Time Series](https://en.wikipedia.org/wiki/Time_series)).
这里利用 `MediaPipe` 将摄像头数据传入到手势检测的 `TFlite` 模型中处理. 然后再把整套程序构建为 `Android archive library`.
`MediaPipe Android archive library` 是一个把 `MediaPipe` 与 `Gradle` 一起使用的方法. `MediaPipe` 不会发布可用于所有项目的常规AAR, 所以需要开发者自行构建. 这是官方给出的[MediaPipe 安装教程](https://github.com/google/mediapipe/blob/master/mediapipe/docs/install.md). 笔者这里是 `Ubuntu` 系统, 选择了 `Docker` 的安装方式(`git clone` 和 `docker pull` 的时候网络不稳定的话可以设置一下 `proxy` 或换源).
安装完成后使用 `docker exec -it mediapipe /bin/bash` 进入 `bash` 操作.
### 创建一个 `mediapipe_aar()`
首先在 `mediapipe/examples/android/src/java/com/google/mediapipe/apps/aar_example` 里创建一个 `BUILD` 文件, 并把一下内容添加到文本文件里.
``` build
load("//mediapipe/java/com/google/mediapipe:mediapipe_aar.bzl", "mediapipe_aar")
mediapipe_aar(
name = "hand_tracking_aar",
calculators = ["//mediapipe/graphs/hand_tracking:mobile_calculators"],
)
```
### 生成 `aar`
根据上面创建的文本文件, 运行 `bazel build` 命令就可以生成一个 `AAR`, 其中 `--action_env=HTTP_PROXY=$HTTP_PROXY`, 这个参数是用来指定设置代理的(因为在构建的过程中会从 `Github` 下载很多依赖.)
``` bash
bazel build -c opt --action_env=HTTP_PROXY=$HTTP_PROXY --action_env=HTTPS_PROXY=$HTTPS_PROXY --fat_apk_cpu=arm64-v8a,armeabi-v7a mediapipe/examples/android/src/java/com/google/mediapipe/apps/aar_example:hand_tracking_aar
```
在容器内的 `/mediapipe/examples/android/src/java/com/google/mediapipe/apps/aar_example` 路径下, 你可以找到刚刚构建得到的 `hand_tracking_aar.aar`, 使用 `docker cp` 从容器中拷贝到项目的 `android/libs` 下.
### 生成 `binary graph`
上面的 `aar` 执行还需要依赖 `MediaPipe binary graph`, 使用以下命令可以构建生成一个 `binary graph`:
``` bash
bazel build -c opt mediapipe/examples/android/src/java/com/google/mediapipe/apps/handtrackinggpu:binary_graph
```
从 `bazel-bin/mediapipe/examples/android/src/java/com/google/mediapipe/apps/handtrackinggpu` 中拷贝刚才 `build` 出来的 `binary graph`, 当到 `android/src/main/assets` 下
### 添加 `assets` 和 `OpenCV library`
在容器的 `/mediapipe/models` 目录下, 会找到 `hand_lanmark.tflite`, `palm_detection.tflite` 和 `palm_detection_labelmap.txt` 把这些也拷贝出来放到 `android/src/main/assets` 下.
另外 `MediaPipe` 依赖 `OpenCV`, 是哦一需要下载 `OpenCV` 预编译的 `JNI libraries` 库, 并放置到 `android/src/main/jniLibs` 路径下. 可以从[此处](https://github.com/opencv/opencv/releases/download/3.4.3/opencv-3.4.3-android-sdk.zip)下载官方的 `OpenCV Android SDK` 然后运行 `cp` 放到对应路径中
``` bash
cp -R ~/Downloads/OpenCV-android-sdk/sdk/native/libs/arm* /path/to/your/plugin/android/src/main/jniLibs/
```

`MediaPipe` 框架使用 `OpenCV`, 要加载 `MediaPipe` 框架首先要在 `Flutter Plugin` 中加载 `OpenCV`, 在 `FlutterHandTrackingPlugin` 的 `companion object` 中使用以下代码来加载这两个依赖项:
``` kotlin
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
companion object {
init { // Load all native libraries needed by the app.
System.loadLibrary("mediapipe_jni")
System.loadLibrary("opencv_java3")
}
}
}
```
### 修改 `build.grage`
打开 `android/build.gradle`, 添加 `MediaPipe dependencies` 和 `MediaPipe AAR` 到 `app/build.gradle`:
```
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation fileTree(dir: 'libs', include: ['*.aar'])
// MediaPipe deps
implementation 'com.google.flogger:flogger:0.3.1'
implementation 'com.google.flogger:flogger-system-backend:0.3.1'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
// CameraX core library
def camerax_version = "1.0.0-alpha06"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.core:core-ktx:1.2.0"
}
```
## 通过 `CameraX` 调用摄像头
### 获取摄像头权限
要在我们的应用程序中使用相机, 我们需要请求用户提供对相机的访问权限. 要请求相机权限, 请将以下内容添加到 `android/src/main/AndroidManifest.xml`:
``` xml
<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<!-- For MediaPipe -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
```
同时在 `build.gradle` 中将最低 `SDK` 版本更改为 `21` 以上, 并将目标 `SDK` 版本更改为 `27` 以上
``` Gradle Script
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "xyz.zhzh.flutter_hand_tracking_plugin_example"
minSdkVersion 21
targetSdkVersion 27
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
```
为了确保提示用户请求照相机权限, 并使我们能够使用 `CameraX` 库访问摄像头. 请求摄像机许可, 可以使用 `MediaPipe` 组件提供的组建 `PermissionHelper` 要使用它. 首先在组件 `init` 内添加请求权限的代码:
``` kotlin
PermissionHelper.checkAndRequestCameraPermissions(activity)
```
这会在屏幕上以对话框提示用户, 以请求在此应用程序中使用相机的权限.
然后在添加以下代码来处理用户响应:
``` kotlin
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
private val activity: Activity = r.activity()
init {
// ...
r.addRequestPermissionsResultListener(CameraRequestPermissionsListener())
PermissionHelper.checkAndRequestCameraPermissions(activity)
if (PermissionHelper.cameraPermissionsGranted(activity)) onResume()
}
private inner class CameraRequestPermissionsListener :
PluginRegistry.RequestPermissionsResultListener {
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>?,
grantResults: IntArray?): Boolean {
return if (requestCode != 0) false
else {
for (result in grantResults!!) {
if (result == PERMISSION_GRANTED) onResume()
else Toast.makeText(activity, "请授予摄像头权限", Toast.LENGTH_LONG).show()
}
true
}
}
}
private fun onResume() {
// ...
if (PermissionHelper.cameraPermissionsGranted(activity)) startCamera()
}
private fun startCamera() {}
}
```
暂时将 `startCamera()` 方法保留为空. 当用户响应提示后, `onResume()` 方法会被调用调用. 该代码将确认已授予使用相机的权限, 然后将启动相机.
### 调用摄像头
现在将 `SurfaceTexture` 和 `SurfaceView` 添加到插件:
``` kotlin
// {@link SurfaceTexture} where the camera-preview frames can be accessed.
private var previewFrameTexture: SurfaceTexture? = null
// {@link SurfaceView} that displays the camera-preview frames processed by a MediaPipe graph.
private var previewDisplayView: SurfaceView = SurfaceView(r.context())
```
在组件 `init` 的方法里, 添加 `setupPreviewDisplayView()` 方法到请求请求摄像头权限的前面:
``` kotlin
init {
r.addRequestPermissionsResultListener(CameraRequestPermissionsListener())
setupPreviewDisplayView()
PermissionHelper.checkAndRequestCameraPermissions(activity)
if (PermissionHelper.cameraPermissionsGranted(activity)) onResume()
}
```
然后编写 `setupPreviewDisplayView` 方法:
``` kotlin
private fun setupPreviewDisplayView() {
previewDisplayView.visibility = View.GONE
// TODO
}
```
要 `previewDisplayView` 用于获取摄像头的数据, 可以使用 `CameraX`, `MediaPipe` 提供了一个名为 `CameraXPreviewHelper` 的类使用 `CameraX`. 开启相机时, 可以更新监听函数 `onCameraStarted(@Nullable SurfaceTexture)`
现在定义一个 `CameraXPreviewHelper`:
``` kotlin
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
// ...
private var cameraHelper: CameraXPreviewHelper? = null
// ...
}
```
然后实现之前的 `startCamera()`:
``` kotlin
private fun startCamera() {
cameraHelper = CameraXPreviewHelper()
cameraHelper!!.setOnCameraStartedListener { surfaceTexture: SurfaceTexture? ->
previewFrameTexture = surfaceTexture
// Make the display view visible to start showing the preview. This triggers the
// SurfaceHolder.Callback added to (the holder of) previewDisplayView.
previewDisplayView.visibility = View.VISIBLE
}
cameraHelper!!.startCamera(activity, CAMERA_FACING, /*surfaceTexture=*/null)
}
```
这将 `new` 一个 `CameraXPreviewHelper` 对象, 并在该对象上添加一个匿名监听. 当有 `cameraHelper` 监听到相机已启动时, `surfaceTexture` 可以抓取摄像头帧, 将其传给 `previewFrameTexture`, 并使 `previewDisplayView` 可见.
在调用摄像头时, 需要确定要使用的相机. `CameraXPreviewHelper` 继承 `CameraHelper` 的两个选项: `FRONT` 和 `BACK`. 并且作为参数 `CAMERA_FACING` 传入 `cameraHelper!!.startCamera` 方法. 这里设置前置摄像头为一个静态变量:
``` kotlin
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
companion object {
private val CAMERA_FACING = CameraHelper.CameraFacing.FRONT
// ...
}
//...
}
```
## 使用 `ExternalTextureConverter` 转换摄像头图像帧数据
上面使用 `SurfaceTexture` 将流中的摄像头图像帧捕获并存在 `OpenGL ES texture` 对象中. 要使用 `MediaPipe graph`, 需要把摄像机捕获的帧存在在普通的 `Open GL texture` 对象中. `MediaPipe` 提供了类 `ExternalTextureConverter` 类用于将存储在 `SurfaceTexture` 对象中的图像帧转换为常规 `OpenGL texture` 对象.
要使用 `ExternalTextureConverter`, 还需要一个由 `EglManager` 对象创建和管理的 `EGLContext`. 在插件中添加以下声明:
``` kotlin
// Creates and manages an {@link EGLContext}.
private var eglManager: EglManager = EglManager(null)
// Converts the GL_TEXTURE_EXTERNAL_OES texture from Android camera into a regular texture to be
// consumed by {@link FrameProcessor} and the underlying MediaPipe graph.
private var converter: ExternalTextureConverter? = null
```
修改之前编写的 `onResume()` 方法添加初始化 `converter` 对象的代码:
``` kotlin
private fun onResume() {
converter = ExternalTextureConverter(eglManager.context)
converter!!.setFlipY(FLIP_FRAMES_VERTICALLY)
if (PermissionHelper.cameraPermissionsGranted(activity)) {
startCamera()
}
}
```
要把 `previewFrameTexture` 传输到 `converter` 进行转换, 将以下代码块添加到 `setupPreviewDisplayView()`:
``` kotlin
private fun setupPreviewDisplayView() {
previewDisplayView.visibility = View.GONE
previewDisplayView.holder.addCallback(
object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
processor.videoSurfaceOutput.setSurface(holder.surface)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { // (Re-)Compute the ideal size of the camera-preview display (the area that the
// camera-preview frames get rendered onto, potentially with scaling and rotation)
// based on the size of the SurfaceView that contains the display.
val viewSize = Size(width, height)
val displaySize = cameraHelper!!.computeDisplaySizeFromViewSize(viewSize)
val isCameraRotated = cameraHelper!!.isCameraRotated
// Connect the converter to the camera-preview frames as its input (via
// previewFrameTexture), and configure the output width and height as the computed
// display size.
converter!!.setSurfaceTextureAndAttachToGLContext(
previewFrameTexture,
if (isCameraRotated) displaySize.height else displaySize.width,
if (isCameraRotated) displaySize.width else displaySize.height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
// TODO
}
})
}
```
在上面的代码中, 首先自定义并添加 `SurfaceHolder.Callback` 到 `previewDisplayView` 并实现 `surfaceChanged(SurfaceHolder holder, int format, int width, int height)`:
1. 计算摄像头的帧在设备屏幕上适当的显示尺寸
2. 传入 `previewFrameTexture` 和 `displaySize` 到 `converter`
现在摄像头获取到的图像帧已经可以传入到 `MediaPipe graph` 中了.
## 调用 `MediaPipe graph`
首先需要加载所有 `MediaPipe graph` 需要的资源(之前从容器中拷贝出来的 `tflite` 模型, `binary graph` 等) 可以使用 `MediaPipe` 的组件 `AndroidAssetUtil` 类:
``` kotlin
// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(activity)
```
然后添加以下代码设置 `processor`:
```
init {
setupProcess()
}
private fun setupProcess() {
processor.videoSurfaceOutput.setFlipY(FLIP_FRAMES_VERTICALLY)
// TODO
}
```
然后根据用到的 `graph` 的名称声明静态变量, 这些静态变量用于之后使用 `graph`:
```
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
companion object {
private const val BINARY_GRAPH_NAME = "handtrackinggpu.binarypb"
private const val INPUT_VIDEO_STREAM_NAME = "input_video"
private const val OUTPUT_VIDEO_STREAM_NAME = "output_video"
private const val OUTPUT_HAND_PRESENCE_STREAM_NAME = "hand_presence"
private const val OUTPUT_LANDMARKS_STREAM_NAME = "hand_landmarks"
}
}
```
现在设置一个 `FrameProcessor` 对象, 把之前 `converter` 转换好的的摄像头图像帧发送到 `MediaPipe graph` 并运行该图获得输出的图像帧, 然后更新 `previewDisplayView` 来显示输出. 添加以下代码以声明 `FrameProcessor`:
``` kotlin
// Sends camera-preview frames into a MediaPipe graph for processing, and displays the processed
// frames onto a {@link Surface}.
private var processor: FrameProcessor = FrameProcessor(
activity,
eglManager.nativeContext,
BINARY_GRAPH_NAME,
INPUT_VIDEO_STREAM_NAME,
OUTPUT_VIDEO_STREAM_NAME)
```
然后编辑 `onResume()` 通过 `converter!!.setConsumer(processor)` 设置 `convert` 把转换好的图像帧输出到 `processor`:
``` kotlin
private fun onResume() {
converter = ExternalTextureConverter(eglManager.context)
converter!!.setFlipY(FLIP_FRAMES_VERTICALLY)
converter!!.setConsumer(processor)
if (PermissionHelper.cameraPermissionsGranted(activity)) {
startCamera()
}
}
```
接着就是把 `processor` 处理后的图像帧输出到 `previewDisplayView`. 重新编辑 `setupPreviewDisplayView` 修改之前定义的 `SurfaceHolder.Callback`:
``` kotlin
private fun setupPreviewDisplayView() {
previewDisplayView.visibility = View.GONE
previewDisplayView.holder.addCallback(
object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
processor.videoSurfaceOutput.setSurface(holder.surface)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
// (Re-)Compute the ideal size of the camera-preview display (the area that the
// camera-preview frames get rendered onto, potentially with scaling and rotation)
// based on the size of the SurfaceView that contains the display.
val viewSize = Size(width, height)
val displaySize = cameraHelper!!.computeDisplaySizeFromViewSize(viewSize)
val isCameraRotated = cameraHelper!!.isCameraRotated
// Connect the converter to the camera-preview frames as its input (via
// previewFrameTexture), and configure the output width and height as the computed
// display size.
converter!!.setSurfaceTextureAndAttachToGLContext(
previewFrameTexture,
if (isCameraRotated) displaySize.height else displaySize.width,
if (isCameraRotated) displaySize.width else displaySize.height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
processor.videoSurfaceOutput.setSurface(null)
}
})
}
```
当 `SurfaceHolder` 被创建时, 摄像头图像帧就会经由 `convert` 转换后输出到 `processor`, 然后再通过 `VideoSurfaceOutput` 输出一个 `Surface`.
## 通过 `EventChannel` 实现原生组件与 `Flutter` 的通讯
之前的处理仅仅在处理图像帧, `processor` 除了处理图像之外, 还可以获取手部关键点的坐标. 通过 `EventChannel` 可以把这些数据传输给 `Flutter` 的 `Dart` 层, 进而根据这些关键点可以编写各种业务逻辑将手势信息实时转化为指令信息: 一二三四五, rock, spiderman...或者对不同手势编写不同特效. 从而为人机互动带来更自然丰富的体验.
### 在 `Android` 平台打开 `EventChannel`
首先定义一个 `EventChannel` 和一个 `EventChannel.EventSink`:
``` kotlin
private val eventChannel: EventChannel = EventChannel(r.messenger(), "$NAMESPACE/$id/landmarks")
private var eventSink: EventChannel.EventSink? = null
```
`EventChannel.EventSink` 用于之后发送消息. 然后在 `init` 方法中初始化 `eventChannel`:
``` kotlin
inti {
this.eventChannel.setStreamHandler(landMarksStreamHandler())
}
private fun landMarksStreamHandler(): EventChannel.StreamHandler {
return object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
// Log.e(TAG, "Listen Event Channel")
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
}
}
```
设置完消息通道后, 编辑之前的 `setupProcess()` 方法. 在设置 `processor` 的输出前添加代码, 实现获得手部关键点的位置并通过 `EventChannel.EventSink` 发送到之前打开的 `eventChannel`:
``` kotlin
private val uiThreadHandler: Handler = Handler(Looper.getMainLooper())
private fun setupProcess() {
processor.videoSurfaceOutput.setFlipY(FLIP_FRAMES_VERTICALLY)
processor.addPacketCallback(
OUTPUT_HAND_PRESENCE_STREAM_NAME
) { packet: Packet ->
val handPresence = PacketGetter.getBool(packet)
if (!handPresence) Log.d(TAG, "[TS:" + packet.timestamp + "] Hand presence is false, no hands detected.")
}
processor.addPacketCallback(
OUTPUT_LANDMARKS_STREAM_NAME
) { packet: Packet ->
val landmarksRaw = PacketGetter.getProtoBytes(packet)
if (eventSink == null) try {
val landmarks = LandmarkProto.NormalizedLandmarkList.parseFrom(landmarksRaw)
if (landmarks == null) {
Log.d(TAG, "[TS:" + packet.timestamp + "] No hand landmarks.")
return@addPacketCallback
}
// Note: If hand_presence is false, these landmarks are useless.
Log.d(TAG, "[TS: ${packet.timestamp}] #Landmarks for hand: ${landmarks.landmarkCount}\n ${getLandmarksString(landmarks)}")
} catch (e: InvalidProtocolBufferException) {
Log.e(TAG, "Couldn't Exception received - $e")
return@addPacketCallback
}
else uiThreadHandler.post { eventSink?.success(landmarksRaw) }
}
}
```
这里 `LandmarkProto.NormalizedLandmarkList.parseFrom()` 用来解析标记点 `byte array` 格式的数据. 因为所有标记点的数据都是使用 `protobuf` 封装的. `Protocol buffers` 是一种 `Google` 可以在各种语言使用的跨平台序列化结构数据的工具, 详情可以查看[官网](https://developers.google.com/protocol-buffers).
另外最后用到了 `uiThreadHandler` 来发送数据, 因为 `processor` 的 `callback` 会在线程中执行, 但是 `Flutter` 框架往 `eventChannel` 里发送消息需要在 `UI` 线程中, 所以使用 `uiThreadHandler` 来 `post`.
完整的 `FlutterHandTrackingPlugin.kt` 的详情可见[github](https://github.com/zhouzaihang/flutter_hand_tracking_plugin)
### 在 `Dart` 层获得 `eventChannel` 的数据
再一次打开 `lib/flutter_hand_tracking_plugin.dart`, 编辑 `HandTrackingViewController` 类. 根据 `id` 添加一个 `EventChannel`, 然后使用 `receiveBroadcastStream` 接受这个通道消息:
``` dart
class HandTrackingViewController {
final MethodChannel _methodChannel;
final EventChannel _eventChannel;
HandTrackingViewController._(int id)
: _methodChannel = MethodChannel("$NAMESPACE/$id"),
_eventChannel = EventChannel("$NAMESPACE/$id/landmarks");
Future<String> get platformVersion async =>
await _methodChannel.invokeMethod("getPlatformVersion");
Stream<NormalizedLandmarkList> get landMarksStream async* {
yield* _eventChannel
.receiveBroadcastStream()
.map((buffer) => NormalizedLandmarkList.fromBuffer(buffer));
}
}
```
之前已经介绍过了, 传输的数据格式是使用 `protobuf` 序列化的有一定结构的 `byte array`. 所以需要使用 `NormalizedLandmarkList.fromBuffer()`, 来解析. `NormalizedLandmarkList.fromBuffer()` 这个接口, 是由 `protobuf` 根据 [protos/landmark.proto](https://github.com/zhouzaihang/flutter_hand_tracking_plugin/blob/master/protos/landmark.proto) 生成的 `.dart` 文件提供.
首先打开 `pubspec.yaml` 添加 `protoc_plugin` 的依赖:
``` yaml
dependencies:
flutter:
sdk: flutter
protobuf: ^1.0.1
```
然后安装和激活插件:
``` bash
pub install
pub global activate protoc_plugin
```
再根据[protobuf 安装教程](https://github.com/google/protobuf)配置 `Protocol`
然后运行 `protoc` 命令就可以生成 `.dart` 文件了:
``` bash
protoc --dart_out=../lib/gen ./landmark.proto
```
也可以直接使用已经生成好的[flutter_hand_tracking_plugin/lib/gen/](https://github.com/zhouzaihang/flutter_hand_tracking_plugin/tree/master/lib/gen):

生成完了以后就可以通过 `NormalizedLandmarkList` 来存储收到的数据, 并且 `NormalizedLandmarkList` 对象有 `fromBuffer()`, `fromJson()` 各种方法来反序列化数据.
================================================
FILE: android/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
================================================
FILE: android/build.gradle
================================================
group 'xyz.zhzh.flutter_hand_tracking_plugin'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 29
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation fileTree(dir: 'libs', include: ['*.aar'])
// MediaPipe deps
implementation 'com.google.flogger:flogger:0.3.1'
implementation 'com.google.flogger:flogger-system-backend:0.3.1'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
// CameraX core library
def camerax_version = "1.0.0-alpha06"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.core:core-ktx:1.2.0"
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
================================================
FILE: android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
================================================
FILE: android/libs/hand_tracking_aar.aar
================================================
version https://git-lfs.github.com/spec/v1
oid sha256:e9e16b67719b7f6a53ec6295764d4c5a05f25b09ddfdefb165da2ebcfd60e9d2
size 105692902
================================================
FILE: android/settings.gradle
================================================
rootProject.name = 'flutter_hand_tracking_plugin'
================================================
FILE: android/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.zhzh.flutter_hand_tracking_plugin">
<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<!-- For MediaPipe -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
</manifest>
================================================
FILE: android/src/main/assets/hand_landmark.tflite
================================================
[File too large to display: 10.9 MB]
================================================
FILE: android/src/main/assets/handtrackinggpu.binarypb
================================================
]FlowLimiterCalculatorinput_videoFINISHED:hand_rect"throttled_input_videoj
FINISHED
tPreviousLoopbackCalculatorMAIN:throttled_input_videoLOOP:hand_presence"PREV_LOOP:prev_hand_presencej
LOOP
GateCalculatorthrottled_input_videoDISALLOW:prev_hand_presence"hand_detection_input_videoB9
3type.googleapis.com/mediapipe.GateCalculatorOptions
yHandDetectionSubgraphhand_detection_input_video"DETECTIONS:palm_detections"(NORM_RECT:hand_rect_from_palm_detections
HandLandmarkSubgraphIMAGE:throttled_input_videoNORM_RECT:hand_rect"LANDMARKS:hand_landmarks""NORM_RECT:hand_rect_from_landmarks"PRESENCE:hand_presence
PreviousLoopbackCalculatorMAIN:throttled_input_videoLOOP:hand_rect_from_landmarks"'PREV_LOOP:prev_hand_rect_from_landmarksj
LOOP
[MergeCalculatorhand_rect_from_palm_detectionsprev_hand_rect_from_landmarks" hand_rect
RendererSubgraphIMAGE:throttled_input_videoLANDMARKS:hand_landmarksNORM_RECT:hand_rectDETECTIONS:palm_detections"IMAGE:output_videoRinput_videozoutput_video
================================================
FILE: android/src/main/assets/palm_detection_labelmap.txt
================================================
Palm
================================================
FILE: android/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin/FlutterHandTrackingPlugin.kt
================================================
package xyz.zhzh.flutter_hand_tracking_plugin
import android.app.Activity
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.graphics.SurfaceTexture
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.Size
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.widget.Toast
import androidx.annotation.NonNull
import com.google.mediapipe.components.*
import com.google.mediapipe.formats.proto.LandmarkProto
import com.google.mediapipe.framework.AndroidAssetUtil
import com.google.mediapipe.framework.Packet
import com.google.mediapipe.framework.PacketGetter
import com.google.mediapipe.glutil.EglManager
import com.google.protobuf.InvalidProtocolBufferException
import io.flutter.plugin.common.EventChannel
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
import io.flutter.plugin.common.PluginRegistry.Registrar
import io.flutter.plugin.platform.PlatformView
/** FlutterHandTrackingPlugin */
class FlutterHandTrackingPlugin(r: Registrar, id: Int) : PlatformView, MethodCallHandler {
companion object {
private const val TAG = "HandTrackingPlugin"
private const val NAMESPACE = "plugins.zhzh.xyz/flutter_hand_tracking_plugin"
private const val BINARY_GRAPH_NAME = "handtrackinggpu.binarypb"
private const val INPUT_VIDEO_STREAM_NAME = "input_video"
private const val OUTPUT_VIDEO_STREAM_NAME = "output_video"
private const val OUTPUT_HAND_PRESENCE_STREAM_NAME = "hand_presence"
private const val OUTPUT_LANDMARKS_STREAM_NAME = "hand_landmarks"
private val CAMERA_FACING = CameraHelper.CameraFacing.FRONT
// Flips the camera-preview frames vertically before sending them into FrameProcessor to be
// processed in a MediaPipe graph, and flips the processed frames back when they are displayed.
// This is needed because OpenGL represents images assuming the image origin is at the bottom-left
// corner, whereas MediaPipe in general assumes the image origin is at top-left.
private const val FLIP_FRAMES_VERTICALLY = true
private fun getLandmarksString(landmarks: LandmarkProto.NormalizedLandmarkList): String {
var landmarksString = ""
for ((landmarkIndex, landmark) in landmarks.landmarkList.withIndex()) {
landmarksString += ("\t\tLandmark["
+ landmarkIndex
+ "]: ("
+ landmark.x
+ ", "
+ landmark.y
+ ", "
+ landmark.z
+ ")\n")
}
return landmarksString
}
@JvmStatic
fun registerWith(registrar: Registrar) {
registrar.platformViewRegistry().registerViewFactory(
"$NAMESPACE/view",
HandTrackingViewFactory(registrar))
}
init { // Load all native libraries needed by the app.
System.loadLibrary("mediapipe_jni")
System.loadLibrary("opencv_java3")
}
}
private val activity: Activity = r.activity()
private val methodChannel: MethodChannel = MethodChannel(r.messenger(), "$NAMESPACE/$id")
private val eventChannel: EventChannel = EventChannel(r.messenger(), "$NAMESPACE/$id/landmarks")
private var eventSink: EventChannel.EventSink? = null
private val uiThreadHandler: Handler = Handler(Looper.getMainLooper())
// {@link SurfaceTexture} where the camera-preview frames can be accessed.
private var previewFrameTexture: SurfaceTexture? = null
// {@link SurfaceView} that displays the camera-preview frames processed by a MediaPipe graph.
private var previewDisplayView: SurfaceView = SurfaceView(r.context())
// Creates and manages an {@link EGLContext}.
private var eglManager: EglManager = EglManager(null)
// Sends camera-preview frames into a MediaPipe graph for processing, and displays the processed
// frames onto a {@link Surface}.
private var processor: FrameProcessor = FrameProcessor(
activity,
eglManager.nativeContext,
BINARY_GRAPH_NAME,
INPUT_VIDEO_STREAM_NAME,
OUTPUT_VIDEO_STREAM_NAME)
// Converts the GL_TEXTURE_EXTERNAL_OES texture from Android camera into a regular texture to be
// consumed by {@link FrameProcessor} and the underlying MediaPipe graph.
private var converter: ExternalTextureConverter? = null
// Handles camera access via the {@link CameraX} Jetpack support library.
private var cameraHelper: CameraXPreviewHelper? = null
init {
r.addRequestPermissionsResultListener(CameraRequestPermissionsListener())
this.methodChannel.setMethodCallHandler(this)
this.eventChannel.setStreamHandler(landMarksStreamHandler())
setupPreviewDisplayView()
// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(activity)
setupProcess()
PermissionHelper.checkAndRequestCameraPermissions(activity)
if (PermissionHelper.cameraPermissionsGranted(activity)) onResume()
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun getView(): SurfaceView? {
return previewDisplayView
}
override fun dispose() {
converter?.close()
}
private inner class CameraRequestPermissionsListener :
PluginRegistry.RequestPermissionsResultListener {
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>?,
grantResults: IntArray?): Boolean {
return if (requestCode != 0) false
else {
for (result in grantResults!!) {
if (result == PERMISSION_GRANTED) onResume()
else Toast.makeText(activity, "请授予摄像头权限", Toast.LENGTH_LONG).show()
}
true
}
}
}
private fun onResume() {
converter = ExternalTextureConverter(eglManager.context)
converter!!.setFlipY(FLIP_FRAMES_VERTICALLY)
converter!!.setConsumer(processor)
if (PermissionHelper.cameraPermissionsGranted(activity)) {
startCamera()
}
}
private fun setupPreviewDisplayView() {
previewDisplayView.visibility = View.GONE
previewDisplayView.holder.addCallback(
object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
processor.videoSurfaceOutput.setSurface(holder.surface)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { // (Re-)Compute the ideal size of the camera-preview display (the area that the
// camera-preview frames get rendered onto, potentially with scaling and rotation)
// based on the size of the SurfaceView that contains the display.
val viewSize = Size(width, height)
val displaySize = cameraHelper!!.computeDisplaySizeFromViewSize(viewSize)
val isCameraRotated = cameraHelper!!.isCameraRotated
// Connect the converter to the camera-preview frames as its input (via
// previewFrameTexture), and configure the output width and height as the computed
// display size.
converter!!.setSurfaceTextureAndAttachToGLContext(
previewFrameTexture,
if (isCameraRotated) displaySize.height else displaySize.width,
if (isCameraRotated) displaySize.width else displaySize.height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
processor.videoSurfaceOutput.setSurface(null)
}
})
}
private fun setupProcess() {
processor.videoSurfaceOutput.setFlipY(FLIP_FRAMES_VERTICALLY)
processor.addPacketCallback(
OUTPUT_HAND_PRESENCE_STREAM_NAME
) { packet: Packet ->
val handPresence = PacketGetter.getBool(packet)
if (!handPresence)
// Toast.makeText(
// activity,
// "[TS: ${packet.timestamp}] No hands detected.",
// Toast.LENGTH_SHORT).show()
Log.d(TAG, "[TS:" + packet.timestamp + "] Hand presence is false, no hands detected.")
}
processor.addPacketCallback(
OUTPUT_LANDMARKS_STREAM_NAME
) { packet: Packet ->
val landmarksRaw = PacketGetter.getProtoBytes(packet)
if (eventSink == null) try {
val landmarks = LandmarkProto.NormalizedLandmarkList.parseFrom(landmarksRaw)
if (landmarks == null) {
Log.d(TAG, "[TS:" + packet.timestamp + "] No hand landmarks.")
return@addPacketCallback
}
// Note: If hand_presence is false, these landmarks are useless.
Log.d(TAG, "[TS: ${packet.timestamp}] #Landmarks for hand: ${landmarks.landmarkCount}\n ${getLandmarksString(landmarks)}")
} catch (e: InvalidProtocolBufferException) {
Log.e(TAG, "Couldn't Exception received - $e")
return@addPacketCallback
}
else uiThreadHandler.post { eventSink?.success(landmarksRaw) }
}
}
private fun landMarksStreamHandler(): EventChannel.StreamHandler {
return object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
// Log.e(TAG, "Listen Event Channel")
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
}
}
private fun startCamera() {
cameraHelper = CameraXPreviewHelper()
cameraHelper!!.setOnCameraStartedListener { surfaceTexture: SurfaceTexture? ->
previewFrameTexture = surfaceTexture
// Make the display view visible to start showing the preview. This triggers the
// SurfaceHolder.Callback added to (the holder of) previewDisplayView.
previewDisplayView.visibility = View.VISIBLE
}
cameraHelper!!.startCamera(activity, CAMERA_FACING, /*surfaceTexture=*/null)
}
}
================================================
FILE: android/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin/HandTrackingViewFactory.kt
================================================
package xyz.zhzh.flutter_hand_tracking_plugin
import android.content.Context
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class HandTrackingViewFactory(private val registrar: PluginRegistry.Registrar) :
PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
return FlutterHandTrackingPlugin(registrar, viewId)
}
}
================================================
FILE: example/.gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
================================================
FILE: example/.metadata
================================================
# 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: 9f5ff2306bb3e30b2b98eee79cd231b1336f41f4
channel: stable
project_type: app
================================================
FILE: example/README.md
================================================
# flutter_hand_tracking_plugin_example
Demonstrates how to use the flutter_hand_tracking_plugin 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.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
================================================
FILE: example/android/.gitignore
================================================
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
================================================
FILE: example/android/app/build.gradle
================================================
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 plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "xyz.zhzh.flutter_hand_tracking_plugin_example"
minSdkVersion 21
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.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 {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
================================================
FILE: example/android/app/src/debug/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.zhzh.flutter_hand_tracking_plugin_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>
================================================
FILE: example/android/app/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.zhzh.flutter_hand_tracking_plugin_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_hand_tracking_plugin_example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
================================================
FILE: example/android/app/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin_example/MainActivity.kt
================================================
package xyz.zhzh.flutter_hand_tracking_plugin_example
import androidx.annotation.NonNull
import android.util.Log // 导入 Android 日志工具
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
// 当 Activity 启动时,Flutter 将会配置 FlutterEngine
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
// 使用 GeneratedPluginRegistrant 注册所有 Flutter 插件
GeneratedPluginRegistrant.registerWith(flutterEngine)
// 添加日志打印,以便在配置 FlutterEngine 时进行调试
Log.d("MainActivity", "FlutterEngine 已配置")
}
}
================================================
FILE: example/android/app/src/main/res/drawable/launch_background.xml
================================================
<?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>
================================================
FILE: example/android/app/src/main/res/values/styles.xml
================================================
<?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>
================================================
FILE: example/android/app/src/profile/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.zhzh.flutter_hand_tracking_plugin_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>
================================================
FILE: example/android/build.gradle
================================================
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
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
}
================================================
FILE: example/android/gradle/wrapper/gradle-wrapper.properties
================================================
#Sun Mar 01 12:17:42 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
================================================
FILE: example/android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
================================================
FILE: example/android/settings.gradle
================================================
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
}
================================================
FILE: example/ios/.gitignore
================================================
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
================================================
FILE: example/ios/Flutter/AppFrameworkInfo.plist
================================================
<?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>$(DEVELOPMENT_LANGUAGE)</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>
================================================
FILE: example/ios/Flutter/Debug.xcconfig
================================================
#include "Generated.xcconfig"
================================================
FILE: example/ios/Flutter/Release.xcconfig
================================================
#include "Generated.xcconfig"
================================================
FILE: example/ios/Runner/AppDelegate.h
================================================
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end
================================================
FILE: example/ios/Runner/AppDelegate.m
================================================
#import "AppDelegate.h"
#import "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
================================================
FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"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"
}
}
================================================
FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
"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"
}
}
================================================
FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# 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.
================================================
FILE: example/ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
<?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>
================================================
FILE: example/ios/Runner/Base.lproj/Main.storyboard
================================================
<?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>
================================================
FILE: example/ios/Runner/Info.plist
================================================
<?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>$(DEVELOPMENT_LANGUAGE)</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_hand_tracking_plugin_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>
================================================
FILE: example/ios/Runner/main.m
================================================
#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]));
}
}
================================================
FILE: example/ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*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, ); }; };
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 = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
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 */,
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_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = 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;
SUPPORTED_PLATFORMS = 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)";
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 = xyz.zhzh.flutterHandTrackingPluginExample;
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_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = 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_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = 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;
SUPPORTED_PLATFORMS = 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 = xyz.zhzh.flutterHandTrackingPluginExample;
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 = xyz.zhzh.flutterHandTrackingPluginExample;
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 */;
}
================================================
FILE: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
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"
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"
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>
================================================
FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: example/lib/main.dart
================================================
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter_hand_tracking_plugin/HandGestureRecognition.dart';
import 'package:flutter_hand_tracking_plugin/flutter_hand_tracking_plugin.dart';
import 'package:flutter_hand_tracking_plugin/gen/landmark.pb.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
HandTrackingViewController _controller;
Gestures _gesture;
Color _selectedColor = Colors.black;
Color _pickerColor = Colors.black;
double _opacity = 1.0;
double _strokeWidth = 3.0;
double _canvasHeight = 300;
double _canvasWeight = 300;
bool _showBottomList = false;
List<DrawingPoints> _points = List();
SelectedMode _selectedMode = SelectedMode.StrokeWidth;
List<Color> _colors = [
Colors.red,
Colors.green,
Colors.blue,
Colors.amber,
Colors.black
];
void continueDraw(landmark) => setState(() => _points.add(DrawingPoints(
points: Offset(landmark.x * _canvasWeight, landmark.y * _canvasHeight),
paint: Paint()
..strokeCap = StrokeCap.butt
..isAntiAlias = true
..color = _selectedColor.withOpacity(_opacity)
..strokeWidth = _strokeWidth)));
void finishDraw() => setState(() => _points.add(null));
void _onLandMarkStream(NormalizedLandmarkList landmarkList) {
if (landmarkList.landmark != null && landmarkList.landmark.length != 0) {
setState(() => _gesture =
HandGestureRecognition.handGestureRecognition(landmarkList.landmark));
if (_gesture == Gestures.ONE)
continueDraw(landmarkList.landmark[8]);
else if (_points.length != 0) finishDraw();
} else
_gesture = null;
}
getColorList() {
List<Widget> listWidget = List();
for (Color color in _colors) {
listWidget.add(colorCircle(color));
}
Widget colorPicker = GestureDetector(
onTap: () {
showDialog(
context: context,
child: AlertDialog(
title: const Text('选择颜色'),
content: SingleChildScrollView(
child: ColorPicker(
pickerColor: _pickerColor,
onColorChanged: (color) => _pickerColor = color,
// enableLabel: true,
pickerAreaHeightPercent: 0.8,
),
),
actions: <Widget>[
FlatButton(
child: const Text('保存'),
onPressed: () {
setState(() => _selectedColor = _pickerColor);
Navigator.of(context).pop();
},
),
],
),
);
},
child: ClipOval(
child: Container(
padding: const EdgeInsets.only(bottom: 16.0),
height: 36,
width: 36,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red, Colors.green, Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)),
),
),
);
listWidget.add(colorPicker);
return listWidget;
}
Widget colorCircle(Color color) {
return GestureDetector(
onTap: () => setState(() => _selectedColor = color),
child: ClipOval(
child: Container(
padding: const EdgeInsets.only(bottom: 16.0),
height: 36,
width: 36,
color: color,
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hand Tracking Example App'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
height: 300,
child: HandTrackingView(
onViewCreated: (HandTrackingViewController c) => setState(() {
_controller = c;
if (_controller != null)
_controller.landMarksStream.listen(_onLandMarkStream);
}),
),
),
_controller == null
? Text(
"Please grant camera permissions and reopen the application.")
: Column(
children: <Widget>[
Text(_gesture == null
? "No hand landmarks."
: _gesture.toString()),
CustomPaint(
size: Size(_canvasWeight, _canvasHeight),
painter: DrawingPainter(
pointsList: _points,
),
)
],
)
],
),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
color: Colors.greenAccent),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.album),
onPressed: () => setState(() {
if (_selectedMode == SelectedMode.StrokeWidth)
_showBottomList = !_showBottomList;
_selectedMode = SelectedMode.StrokeWidth;
}),
),
IconButton(
icon: Icon(Icons.opacity),
onPressed: () => setState(() {
if (_selectedMode == SelectedMode.Opacity)
_showBottomList = !_showBottomList;
_selectedMode = SelectedMode.Opacity;
}),
),
IconButton(
icon: Icon(Icons.color_lens),
onPressed: () => setState(() {
if (_selectedMode == SelectedMode.Color)
_showBottomList = !_showBottomList;
_selectedMode = SelectedMode.Color;
}),
),
IconButton(
icon: Icon(Icons.clear),
onPressed: () => setState(() {
_showBottomList = false;
_points.clear();
}),
),
],
),
Visibility(
child: (_selectedMode == SelectedMode.Color)
? Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: getColorList(),
)
: Slider(
value: (_selectedMode == SelectedMode.StrokeWidth)
? _strokeWidth
: _opacity,
max: (_selectedMode == SelectedMode.StrokeWidth)
? 50.0
: 1.0,
min: 0.0,
onChanged: (val) {
setState(() {
if (_selectedMode == SelectedMode.StrokeWidth)
_strokeWidth = val;
else
_opacity = val;
});
}),
visible: _showBottomList,
),
],
),
),
),
),
);
}
}
class DrawingPainter extends CustomPainter {
DrawingPainter({this.pointsList});
List<DrawingPoints> pointsList;
List<Offset> offsetPoints = List();
@override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points,
pointsList[i].paint);
} else if (pointsList[i] != null && pointsList[i + 1] == null) {
offsetPoints.clear();
offsetPoints.add(pointsList[i].points);
offsetPoints.add(Offset(
pointsList[i].points.dx + 0.1, pointsList[i].points.dy + 0.1));
canvas.drawPoints(PointMode.points, offsetPoints, pointsList[i].paint);
}
}
}
@override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
class DrawingPoints {
Paint paint;
Offset points;
DrawingPoints({this.points, this.paint});
}
enum SelectedMode { StrokeWidth, Opacity, Color }
================================================
FILE: example/pubspec.yaml
================================================
name: flutter_hand_tracking_plugin_example
description: Demonstrates how to use the flutter_hand_tracking_plugin 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_hand_tracking_plugin:
path: ../
flutter_colorpicker: any
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/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.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/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.dev/custom-fonts/#from-packages
================================================
FILE: example/test/widget_test.dart
================================================
// 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_hand_tracking_plugin_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,
);
});
}
================================================
FILE: flutter_hand_tracking_plugin/android/flutter_hand_tracking_plugin.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":flutter_hand_tracking_plugin" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/../../example/android" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":flutter_hand_tracking_plugin" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" />
<option name="LAST_KNOWN_AGP_VERSION" />
</configuration>
</facet>
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/../../example/build/flutter_hand_tracking_plugin" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
================================================
FILE: flutter_hand_tracking_plugin.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Bundled Protobuf Distribution" level="application" />
</component>
</module>
================================================
FILE: ios/.gitignore
================================================
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/flutter_export_environment.sh
================================================
FILE: ios/Assets/.gitkeep
================================================
================================================
FILE: ios/Classes/FlutterHandTrackingPlugin.h
================================================
#import <Flutter/Flutter.h>
@interface FlutterHandTrackingPlugin : NSObject<FlutterPlugin>
@end
================================================
FILE: ios/Classes/FlutterHandTrackingPlugin.m
================================================
#import "FlutterHandTrackingPlugin.h"
@implementation FlutterHandTrackingPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"flutter_hand_tracking_plugin"
binaryMessenger:[registrar messenger]];
FlutterHandTrackingPlugin* instance = [[FlutterHandTrackingPlugin 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
================================================
FILE: ios/flutter_hand_tracking_plugin.podspec
================================================
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint flutter_hand_tracking_plugin.podspec' to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'flutter_hand_tracking_plugin'
s.version = '0.0.1'
s.summary = 'A new Flutter hand tracking plugin.'
s.description = <<-DESC
A new Flutter hand tracking plugin.
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.platform = :ios, '8.0'
# Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
end
================================================
FILE: lib/HandGestureRecognition.dart
================================================
import 'dart:math';
import 'package:flutter_hand_tracking_plugin/gen/landmark.pb.dart';
enum Gestures {
FIVE,
FOUR,
TREE,
TWO,
ONE,
YEAH,
ROCK,
SPIDERMAN,
FIST,
OK,
UNKNOWN
}
class HandGestureRecognition {
static bool fingerIsOpen(
double pseudoFixKeyPoint, double point1, double point2) =>
point1 < pseudoFixKeyPoint && point2 < pseudoFixKeyPoint;
static bool thumbIsOpen(List landmarks) =>
fingerIsOpen(landmarks[2].x, landmarks[3].x, landmarks[4].x);
static bool firstFingerIsOpen(List landmarks) =>
fingerIsOpen(landmarks[6].y, landmarks[7].y, landmarks[8].y);
static bool secondFingerIsOpen(List landmarks) =>
fingerIsOpen(landmarks[10].y, landmarks[11].y, landmarks[12].y);
static bool thirdFingerIsOpen(List landmarks) =>
fingerIsOpen(landmarks[14].y, landmarks[15].y, landmarks[16].y);
static bool fourthFingerIsOpen(List landmarks) =>
fingerIsOpen(landmarks[18].y, landmarks[19].y, landmarks[20].y);
static double getEuclideanDistanceAB(
double aX, double aY, double bX, double bY) =>
sqrt(pow(aX - bX, 2) + pow(aY - bY, 2));
static bool isThumbNearFirstFinger(
NormalizedLandmark point1, NormalizedLandmark point2) =>
getEuclideanDistanceAB(point1.x, point1.y, point2.x, point2.y) < 0.1;
static Gestures handGestureRecognition(List landmarks) {
if (landmarks.length == 0) return Gestures.UNKNOWN;
// finger states
bool thumbIsOpen = HandGestureRecognition.thumbIsOpen(landmarks);
bool firstFingerIsOpen =
HandGestureRecognition.firstFingerIsOpen(landmarks);
bool secondFingerIsOpen =
HandGestureRecognition.secondFingerIsOpen(landmarks);
bool thirdFingerIsOpen =
HandGestureRecognition.thirdFingerIsOpen(landmarks);
bool fourthFingerIsOpen =
HandGestureRecognition.fourthFingerIsOpen(landmarks);
if (thumbIsOpen &&
firstFingerIsOpen &&
secondFingerIsOpen &&
thirdFingerIsOpen &&
fourthFingerIsOpen)
return Gestures.FIVE;
else if (!thumbIsOpen &&
firstFingerIsOpen &&
secondFingerIsOpen &&
thirdFingerIsOpen &&
fourthFingerIsOpen)
return Gestures.FOUR;
else if (thumbIsOpen &&
firstFingerIsOpen &&
secondFingerIsOpen &&
!thirdFingerIsOpen &&
!fourthFingerIsOpen)
return Gestures.TREE;
else if (thumbIsOpen &&
firstFingerIsOpen &&
!secondFingerIsOpen &&
!thirdFingerIsOpen &&
!fourthFingerIsOpen)
return Gestures.TWO;
else if (!thumbIsOpen &&
firstFingerIsOpen &&
!secondFingerIsOpen &&
!thirdFingerIsOpen &&
!fourthFingerIsOpen)
return Gestures.ONE;
else if (!thumbIsOpen &&
firstFingerIsOpen &&
secondFingerIsOpen &&
!thirdFingerIsOpen &&
!fourthFingerIsOpen)
return Gestures.YEAH;
else if (!thumbIsOpen &&
firstFingerIsOpen &&
!secondFingerIsOpen &&
!thirdFingerIsOpen &&
fourthFingerIsOpen)
return Gestures.ROCK;
else if (thumbIsOpen &&
firstFingerIsOpen &&
!secondFingerIsOpen &&
!thirdFingerIsOpen &&
fourthFingerIsOpen)
return Gestures.SPIDERMAN;
else if (!thumbIsOpen &&
!firstFingerIsOpen &&
!secondFingerIsOpen &&
!thirdFingerIsOpen &&
!fourthFingerIsOpen)
return Gestures.FIST;
else if (!firstFingerIsOpen &&
secondFingerIsOpen &&
thirdFingerIsOpen &&
fourthFingerIsOpen &&
isThumbNearFirstFinger(landmarks[4], landmarks[8]))
return Gestures.OK;
else
return Gestures.UNKNOWN;
}
}
================================================
FILE: lib/flutter_hand_tracking_plugin.dart
================================================
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hand_tracking_plugin/gen/landmark.pb.dart';
const NAMESPACE = "plugins.zhzh.xyz/flutter_hand_tracking_plugin";
typedef void HandTrackingViewCreatedCallback(
HandTrackingViewController controller);
class HandTrackingView extends StatelessWidget {
const HandTrackingView({@required this.onViewCreated})
: assert(onViewCreated != null);
final HandTrackingViewCreatedCallback onViewCreated;
@override
Widget build(BuildContext context) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return AndroidView(
viewType: "$NAMESPACE/view",
onPlatformViewCreated: (int id) => onViewCreated == null
? null
: onViewCreated(HandTrackingViewController._(id)),
);
case TargetPlatform.fuchsia:
case TargetPlatform.iOS:
default:
throw UnsupportedError(
"Trying to use the default webview implementation for"
" $defaultTargetPlatform but there isn't a default one");
}
}
}
class HandTrackingViewController {
final MethodChannel _methodChannel;
final EventChannel _eventChannel;
HandTrackingViewController._(int id)
: _methodChannel = MethodChannel("$NAMESPACE/$id"),
_eventChannel = EventChannel("$NAMESPACE/$id/landmarks");
Future<String> get platformVersion async =>
await _methodChannel.invokeMethod("getPlatformVersion");
Stream<NormalizedLandmarkList> get landMarksStream async* {
yield* _eventChannel
.receiveBroadcastStream()
.map((buffer) => NormalizedLandmarkList.fromBuffer(buffer));
}
}
================================================
FILE: lib/gen/landmark.pb.dart
================================================
///
// Generated code. Do not modify.
// source: landmark.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class Landmark extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('Landmark', package: const $pb.PackageName('mediapipe'), createEmptyInstance: create)
..a<$core.double>(1, 'x', $pb.PbFieldType.OF)
..a<$core.double>(2, 'y', $pb.PbFieldType.OF)
..a<$core.double>(3, 'z', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
Landmark._() : super();
factory Landmark() => create();
factory Landmark.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Landmark.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
Landmark clone() => Landmark()..mergeFromMessage(this);
Landmark copyWith(void Function(Landmark) updates) => super.copyWith((message) => updates(message as Landmark));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Landmark create() => Landmark._();
Landmark createEmptyInstance() => create();
static $pb.PbList<Landmark> createRepeated() => $pb.PbList<Landmark>();
@$core.pragma('dart2js:noInline')
static Landmark getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Landmark>(create);
static Landmark _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
void clearX() => clearField(1);
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
void clearY() => clearField(2);
@$pb.TagNumber(3)
$core.double get z => $_getN(2);
@$pb.TagNumber(3)
set z($core.double v) { $_setFloat(2, v); }
@$pb.TagNumber(3)
$core.bool hasZ() => $_has(2);
@$pb.TagNumber(3)
void clearZ() => clearField(3);
}
class LandmarkList extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('LandmarkList', package: const $pb.PackageName('mediapipe'), createEmptyInstance: create)
..pc<Landmark>(1, 'landmark', $pb.PbFieldType.PM, subBuilder: Landmark.create)
..hasRequiredFields = false
;
LandmarkList._() : super();
factory LandmarkList() => create();
factory LandmarkList.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory LandmarkList.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
LandmarkList clone() => LandmarkList()..mergeFromMessage(this);
LandmarkList copyWith(void Function(LandmarkList) updates) => super.copyWith((message) => updates(message as LandmarkList));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static LandmarkList create() => LandmarkList._();
LandmarkList createEmptyInstance() => create();
static $pb.PbList<LandmarkList> createRepeated() => $pb.PbList<LandmarkList>();
@$core.pragma('dart2js:noInline')
static LandmarkList getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<LandmarkList>(create);
static LandmarkList _defaultInstance;
@$pb.TagNumber(1)
$core.List<Landmark> get landmark => $_getList(0);
}
class NormalizedLandmark extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('NormalizedLandmark', package: const $pb.PackageName('mediapipe'), createEmptyInstance: create)
..a<$core.double>(1, 'x', $pb.PbFieldType.OF)
..a<$core.double>(2, 'y', $pb.PbFieldType.OF)
..a<$core.double>(3, 'z', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
NormalizedLandmark._() : super();
factory NormalizedLandmark() => create();
factory NormalizedLandmark.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory NormalizedLandmark.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
NormalizedLandmark clone() => NormalizedLandmark()..mergeFromMessage(this);
NormalizedLandmark copyWith(void Function(NormalizedLandmark) updates) => super.copyWith((message) => updates(message as NormalizedLandmark));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static NormalizedLandmark create() => NormalizedLandmark._();
NormalizedLandmark createEmptyInstance() => create();
static $pb.PbList<NormalizedLandmark> createRepeated() => $pb.PbList<NormalizedLandmark>();
@$core.pragma('dart2js:noInline')
static NormalizedLandmark getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<NormalizedLandmark>(create);
static NormalizedLandmark _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
void clearX() => clearField(1);
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
void clearY() => clearField(2);
@$pb.TagNumber(3)
$core.double get z => $_getN(2);
@$pb.TagNumber(3)
set z($core.double v) { $_setFloat(2, v); }
@$pb.TagNumber(3)
$core.bool hasZ() => $_has(2);
@$pb.TagNumber(3)
void clearZ() => clearField(3);
}
class NormalizedLandmarkList extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('NormalizedLandmarkList', package: const $pb.PackageName('mediapipe'), createEmptyInstance: create)
..pc<NormalizedLandmark>(1, 'landmark', $pb.PbFieldType.PM, subBuilder: NormalizedLandmark.create)
..hasRequiredFields = false
;
NormalizedLandmarkList._() : super();
factory NormalizedLandmarkList() => create();
factory NormalizedLandmarkList.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory NormalizedLandmarkList.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
NormalizedLandmarkList clone() => NormalizedLandmarkList()..mergeFromMessage(this);
NormalizedLandmarkList copyWith(void Function(NormalizedLandmarkList) updates) => super.copyWith((message) => updates(message as NormalizedLandmarkList));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static NormalizedLandmarkList create() => NormalizedLandmarkList._();
NormalizedLandmarkList createEmptyInstance() => create();
static $pb.PbList<NormalizedLandmarkList> createRepeated() => $pb.PbList<NormalizedLandmarkList>();
@$core.pragma('dart2js:noInline')
static NormalizedLandmarkList getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<NormalizedLandmarkList>(create);
static NormalizedLandmarkList _defaultInstance;
@$pb.TagNumber(1)
$core.List<NormalizedLandmark> get landmark => $_getList(0);
}
================================================
FILE: lib/gen/landmark.pbenum.dart
================================================
///
// Generated code. Do not modify.
// source: landmark.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
================================================
FILE: lib/gen/landmark.pbjson.dart
================================================
///
// Generated code. Do not modify.
// source: landmark.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
const Landmark$json = const {
'1': 'Landmark',
'2': const [
const {'1': 'x', '3': 1, '4': 1, '5': 2, '10': 'x'},
const {'1': 'y', '3': 2, '4': 1, '5': 2, '10': 'y'},
const {'1': 'z', '3': 3, '4': 1, '5': 2, '10': 'z'},
],
};
const LandmarkList$json = const {
'1': 'LandmarkList',
'2': const [
const {'1': 'landmark', '3': 1, '4': 3, '5': 11, '6': '.mediapipe.Landmark', '10': 'landmark'},
],
};
const NormalizedLandmark$json = const {
'1': 'NormalizedLandmark',
'2': const [
const {'1': 'x', '3': 1, '4': 1, '5': 2, '10': 'x'},
const {'1': 'y', '3': 2, '4': 1, '5': 2, '10': 'y'},
const {'1': 'z', '3': 3, '4': 1, '5': 2, '10': 'z'},
],
};
const NormalizedLandmarkList$json = const {
'1': 'NormalizedLandmarkList',
'2': const [
const {'1': 'landmark', '3': 1, '4': 3, '5': 11, '6': '.mediapipe.NormalizedLandmark', '10': 'landmark'},
],
};
================================================
FILE: lib/gen/landmark.pbserver.dart
================================================
///
// Generated code. Do not modify.
// source: landmark.proto
//
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
export 'landmark.pb.dart';
================================================
FILE: protos/landmark.proto
================================================
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto2";
package mediapipe;
option java_package = "com.google.mediapipe.formats.proto";
option java_outer_classname = "LandmarkProto";
// A landmark that can have 1 to 3 dimensions. Use x for 1D points, (x, y) for
// 2D points and (x, y, z) for 3D points. For more dimensions, consider using
// matrix_data.proto.
message Landmark {
optional float x = 1;
optional float y = 2;
optional float z = 3;
}
// Group of Landmark protos.
message LandmarkList {
repeated Landmark landmark = 1;
}
// A normalized version of above Landmark proto. All coordiates should be within
// [0, 1].
message NormalizedLandmark {
optional float x = 1;
optional float y = 2;
optional float z = 3;
}
// Group of NormalizedLandmark protos.
message NormalizedLandmarkList {
repeated NormalizedLandmark landmark = 1;
}
================================================
FILE: protos/regenerate.md
================================================
# Generate protobuf files in Dart
1. If upgrading, delete all proto files from /home/.pub-cache/bin
1. Clone the latest dart-protoc-plugin from https://github.com/dart-lang/protobuf
1. Run `pub install` inside protobuf/protoc_plugin
1. Run `pub global activate protoc_plugin` to get .dart files into /home/.pub-cache/bin/
1. Get the latest linux protoc compiler from https://github.com/google/protobuf/releases/ (protoc-X.X.X-linux-x86_64.zip)
1. Copy /bin/protoc into /home/.pub-cache/bin/
1. Run the following commands from this project's protos folder
```protoc --dart_out=../lib/gen ./landmark.proto```
```protoc --objc_out=../ios/gen ./landmark.proto```
================================================
FILE: pubspec.yaml
================================================
name: flutter_hand_tracking_plugin
description: A new Flutter hand tracking plugin.
version: 0.0.1
author:
homepage:
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
protobuf: ^1.0.1
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/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: xyz.zhzh.flutter_hand_tracking_plugin
pluginClass: FlutterHandTrackingPlugin
# 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.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/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.dev/custom-fonts/#from-packages
gitextract_mt_x1c8l/ ├── .gitattributes ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── libs/ │ │ └── hand_tracking_aar.aar │ ├── settings.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ ├── hand_landmark.tflite │ │ ├── handtrackinggpu.binarypb │ │ ├── palm_detection.tflite │ │ └── palm_detection_labelmap.txt │ └── kotlin/ │ └── xyz/ │ └── zhzh/ │ └── flutter_hand_tracking_plugin/ │ ├── FlutterHandTrackingPlugin.kt │ └── HandTrackingViewFactory.kt ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── xyz/ │ │ │ │ │ └── zhzh/ │ │ │ │ │ └── flutter_hand_tracking_plugin_example/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ └── values/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Runner/ │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── main.m │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ └── contents.xcworkspacedata │ ├── lib/ │ │ └── main.dart │ ├── pubspec.yaml │ └── test/ │ └── widget_test.dart ├── flutter_hand_tracking_plugin/ │ └── android/ │ └── flutter_hand_tracking_plugin.iml ├── flutter_hand_tracking_plugin.iml ├── ios/ │ ├── .gitignore │ ├── Assets/ │ │ └── .gitkeep │ ├── Classes/ │ │ ├── FlutterHandTrackingPlugin.h │ │ └── FlutterHandTrackingPlugin.m │ └── flutter_hand_tracking_plugin.podspec ├── lib/ │ ├── HandGestureRecognition.dart │ ├── flutter_hand_tracking_plugin.dart │ └── gen/ │ ├── landmark.pb.dart │ ├── landmark.pbenum.dart │ ├── landmark.pbjson.dart │ └── landmark.pbserver.dart ├── protos/ │ ├── landmark.proto │ └── regenerate.md └── pubspec.yaml
SYMBOL INDEX (70 symbols across 5 files)
FILE: example/lib/main.dart
function main (line 9) | void main()
class MyApp (line 11) | class MyApp extends StatefulWidget {
method createState (line 13) | _MyAppState createState()
class _MyAppState (line 16) | class _MyAppState extends State<MyApp> {
method continueDraw (line 39) | void continueDraw(landmark)
method finishDraw (line 47) | void finishDraw()
method _onLandMarkStream (line 49) | void _onLandMarkStream(NormalizedLandmarkList landmarkList)
method colorCircle (line 109) | Widget colorCircle(Color color)
method build (line 124) | Widget build(BuildContext context)
class DrawingPainter (line 242) | class DrawingPainter extends CustomPainter {
method paint (line 249) | void paint(Canvas canvas, Size size)
method shouldRepaint (line 265) | bool shouldRepaint(DrawingPainter oldDelegate)
class DrawingPoints (line 268) | class DrawingPoints {
type SelectedMode (line 275) | enum SelectedMode { StrokeWidth, Opacity, Color }
FILE: example/test/widget_test.dart
function main (line 13) | void main()
FILE: lib/HandGestureRecognition.dart
type Gestures (line 5) | enum Gestures {
class HandGestureRecognition (line 19) | class HandGestureRecognition {
method fingerIsOpen (line 20) | bool fingerIsOpen(
method thumbIsOpen (line 24) | bool thumbIsOpen(List landmarks)
method firstFingerIsOpen (line 27) | bool firstFingerIsOpen(List landmarks)
method secondFingerIsOpen (line 30) | bool secondFingerIsOpen(List landmarks)
method thirdFingerIsOpen (line 33) | bool thirdFingerIsOpen(List landmarks)
method fourthFingerIsOpen (line 36) | bool fourthFingerIsOpen(List landmarks)
method getEuclideanDistanceAB (line 39) | double getEuclideanDistanceAB(
method isThumbNearFirstFinger (line 43) | bool isThumbNearFirstFinger(
method handGestureRecognition (line 47) | Gestures handGestureRecognition(List landmarks)
FILE: lib/flutter_hand_tracking_plugin.dart
type HandTrackingViewCreatedCallback (line 10) | typedef void HandTrackingViewCreatedCallback(
class HandTrackingView (line 13) | class HandTrackingView extends StatelessWidget {
method build (line 20) | Widget build(BuildContext context)
class HandTrackingViewController (line 39) | class HandTrackingViewController {
FILE: lib/gen/landmark.pb.dart
class Landmark (line 12) | class Landmark extends $pb.GeneratedMessage {
method clone (line 24) | Landmark clone()
method copyWith (line 25) | Landmark copyWith(void Function(Landmark) updates)
method create (line 28) | Landmark create()
method createEmptyInstance (line 29) | Landmark createEmptyInstance()
method createRepeated (line 30) | $pb.PbList<Landmark> createRepeated()
method getDefault (line 32) | Landmark getDefault()
method hasX (line 40) | $core.bool hasX()
method clearX (line 42) | void clearX()
method hasY (line 49) | $core.bool hasY()
method clearY (line 51) | void clearY()
method hasZ (line 58) | $core.bool hasZ()
method clearZ (line 60) | void clearZ()
class LandmarkList (line 63) | class LandmarkList extends $pb.GeneratedMessage {
method clone (line 73) | LandmarkList clone()
method copyWith (line 74) | LandmarkList copyWith(void Function(LandmarkList) updates)
method create (line 77) | LandmarkList create()
method createEmptyInstance (line 78) | LandmarkList createEmptyInstance()
method createRepeated (line 79) | $pb.PbList<LandmarkList> createRepeated()
method getDefault (line 81) | LandmarkList getDefault()
class NormalizedLandmark (line 88) | class NormalizedLandmark extends $pb.GeneratedMessage {
method clone (line 100) | NormalizedLandmark clone()
method copyWith (line 101) | NormalizedLandmark copyWith(void Function(NormalizedLandmark) updates)
method create (line 104) | NormalizedLandmark create()
method createEmptyInstance (line 105) | NormalizedLandmark createEmptyInstance()
method createRepeated (line 106) | $pb.PbList<NormalizedLandmark> createRepeated()
method getDefault (line 108) | NormalizedLandmark getDefault()
method hasX (line 116) | $core.bool hasX()
method clearX (line 118) | void clearX()
method hasY (line 125) | $core.bool hasY()
method clearY (line 127) | void clearY()
method hasZ (line 134) | $core.bool hasZ()
method clearZ (line 136) | void clearZ()
class NormalizedLandmarkList (line 139) | class NormalizedLandmarkList extends $pb.GeneratedMessage {
method clone (line 149) | NormalizedLandmarkList clone()
method copyWith (line 150) | NormalizedLandmarkList copyWith(void Function(NormalizedLandmarkList) ...
method create (line 153) | NormalizedLandmarkList create()
method createEmptyInstance (line 154) | NormalizedLandmarkList createEmptyInstance()
method createRepeated (line 155) | $pb.PbList<NormalizedLandmarkList> createRepeated()
method getDefault (line 157) | NormalizedLandmarkList getDefault()
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (152K chars).
[
{
"path": ".gitattributes",
"chars": 42,
"preview": "*.aar filter=lfs diff=lfs merge=lfs -text\n"
},
{
"path": ".gitignore",
"chars": 68,
"preview": ".idea/\n\n.DS_Store\n.dart_tool/\n\n.packages\n.pub/\n\nbuild/\n\npubspec.lock"
},
{
"path": ".metadata",
"chars": 308,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "CHANGELOG.md",
"chars": 44,
"preview": "## 0.0.1\n\n* TODO: Describe initial release.\n"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 28146,
"preview": "# Flutter Hand Tracking Plugin\n\n> 开发时间比较古早,支持的版本已经很老了,慎用!\n\n这个 `Flutter Hand Tracking Plugin` 是为了实现调用 `Andorid` 设备摄像头精确追踪"
},
{
"path": "android/.gitignore",
"chars": 97,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n"
},
{
"path": "android/build.gradle",
"chars": 1613,
"preview": "group 'xyz.zhzh.flutter_hand_tracking_plugin'\nversion '1.0-SNAPSHOT'\n\nbuildscript {\n ext.kotlin_version = '1.3.61'\n "
},
{
"path": "android/gradle/wrapper/gradle-wrapper.properties",
"chars": 203,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dist"
},
{
"path": "android/gradle.properties",
"chars": 104,
"preview": "org.gradle.jvmargs=-Xmx1536M\nandroid.enableR8=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n"
},
{
"path": "android/libs/hand_tracking_aar.aar",
"chars": 134,
"preview": "version https://git-lfs.github.com/spec/v1\noid sha256:e9e16b67719b7f6a53ec6295764d4c5a05f25b09ddfdefb165da2ebcfd60e9d2\ns"
},
{
"path": "android/settings.gradle",
"chars": 50,
"preview": "rootProject.name = 'flutter_hand_tracking_plugin'\n"
},
{
"path": "android/src/main/AndroidManifest.xml",
"chars": 485,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"xyz.zhzh.flutter_hand_tracking_plugin\""
},
{
"path": "android/src/main/assets/handtrackinggpu.binarypb",
"chars": 1057,
"preview": "\n]\u0012\u0015FlowLimiterCalculator\u001a\u000binput_video\u001a\u0012FINISHED:hand_rect\"\u0015throttled_input_videoj\f\n\bFINISHED\u0010\u0001\nt\u0012\u001aPreviousLoopbackCalcu"
},
{
"path": "android/src/main/assets/palm_detection_labelmap.txt",
"chars": 5,
"preview": "Palm\n"
},
{
"path": "android/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin/FlutterHandTrackingPlugin.kt",
"chars": 11314,
"preview": "package xyz.zhzh.flutter_hand_tracking_plugin\n\nimport android.app.Activity\nimport android.content.pm.PackageManager.PERM"
},
{
"path": "android/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin/HandTrackingViewFactory.kt",
"chars": 573,
"preview": "package xyz.zhzh.flutter_hand_tracking_plugin\n\nimport android.content.Context\nimport io.flutter.plugin.common.PluginRegi"
},
{
"path": "example/.gitignore",
"chars": 615,
"preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.i"
},
{
"path": "example/.metadata",
"chars": 305,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "example/README.md",
"chars": 608,
"preview": "# flutter_hand_tracking_plugin_example\n\nDemonstrates how to use the flutter_hand_tracking_plugin plugin.\n\n## Getting Sta"
},
{
"path": "example/android/.gitignore",
"chars": 110,
"preview": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n"
},
{
"path": "example/android/app/build.gradle",
"chars": 2044,
"preview": "def localProperties = new Properties()\ndef localPropertiesFile = rootProject.file('local.properties')\nif (localPropertie"
},
{
"path": "example/android/app/src/debug/AndroidManifest.xml",
"chars": 353,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"xyz.zhzh.flutter_hand_tracking_plugin_"
},
{
"path": "example/android/app/src/main/AndroidManifest.xml",
"chars": 1584,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"xyz.zhzh.flutter_hand_tracking_plugin_"
},
{
"path": "example/android/app/src/main/kotlin/xyz/zhzh/flutter_hand_tracking_plugin_example/MainActivity.kt",
"chars": 680,
"preview": "package xyz.zhzh.flutter_hand_tracking_plugin_example\n\nimport androidx.annotation.NonNull\nimport android.util.Log // 导入 "
},
{
"path": "example/android/app/src/main/res/drawable/launch_background.xml",
"chars": 434,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
},
{
"path": "example/android/app/src/main/res/values/styles.xml",
"chars": 361,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTi"
},
{
"path": "example/android/app/src/profile/AndroidManifest.xml",
"chars": 353,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"xyz.zhzh.flutter_hand_tracking_plugin_"
},
{
"path": "example/android/build.gradle",
"chars": 582,
"preview": "buildscript {\n ext.kotlin_version = '1.3.50'\n repositories {\n google()\n jcenter()\n }\n\n depende"
},
{
"path": "example/android/gradle/wrapper/gradle-wrapper.properties",
"chars": 232,
"preview": "#Sun Mar 01 12:17:42 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "example/android/gradle.properties",
"chars": 104,
"preview": "org.gradle.jvmargs=-Xmx1536M\nandroid.enableR8=true\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n"
},
{
"path": "example/android/settings.gradle",
"chars": 484,
"preview": "include ':app'\n\ndef flutterProjectRoot = rootProject.projectDir.parentFile.toPath()\n\ndef plugins = new Properties()\ndef "
},
{
"path": "example/ios/.gitignore",
"chars": 542,
"preview": "*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedDat"
},
{
"path": "example/ios/Flutter/AppFrameworkInfo.plist",
"chars": 794,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "example/ios/Flutter/Debug.xcconfig",
"chars": 30,
"preview": "#include \"Generated.xcconfig\"\n"
},
{
"path": "example/ios/Flutter/Release.xcconfig",
"chars": 30,
"preview": "#include \"Generated.xcconfig\"\n"
},
{
"path": "example/ios/Runner/AppDelegate.h",
"chars": 103,
"preview": "#import <Flutter/Flutter.h>\n#import <UIKit/UIKit.h>\n\n@interface AppDelegate : FlutterAppDelegate\n\n@end\n"
},
{
"path": "example/ios/Runner/AppDelegate.m",
"chars": 422,
"preview": "#import \"AppDelegate.h\"\n#import \"GeneratedPluginRegistrant.h\"\n\n@implementation AppDelegate\n\n- (BOOL)application:(UIAppli"
},
{
"path": "example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 2519,
"preview": "{\n \"images\" : [\n {\n \"size\" : \"20x20\",\n \"idiom\" : \"iphone\",\n \"filename\" : \"Icon-App-20x20@2x.png\",\n "
},
{
"path": "example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
"chars": 391,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"filename\" : \"LaunchImage.png\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
"chars": 336,
"preview": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in"
},
{
"path": "example/ios/Runner/Base.lproj/LaunchScreen.storyboard",
"chars": 2377,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "example/ios/Runner/Base.lproj/Main.storyboard",
"chars": 1605,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "example/ios/Runner/Info.plist",
"chars": 1555,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "example/ios/Runner/main.m",
"chars": 226,
"preview": "#import <Flutter/Flutter.h>\n#import <UIKit/UIKit.h>\n#import \"AppDelegate.h\"\n\nint main(int argc, char* argv[]) {\n @autor"
},
{
"path": "example/ios/Runner.xcodeproj/project.pbxproj",
"chars": 20885,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 152,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:Runner.xcodepr"
},
{
"path": "example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
"chars": 3291,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1020\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "example/ios/Runner.xcworkspace/contents.xcworkspacedata",
"chars": 152,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:Runner.xcodepr"
},
{
"path": "example/lib/main.dart",
"chars": 9121,
"preview": "import 'dart:ui';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_colorpicker/flutter_colorpicker.dart'"
},
{
"path": "example/pubspec.yaml",
"chars": 2010,
"preview": "name: flutter_hand_tracking_plugin_example\ndescription: Demonstrates how to use the flutter_hand_tracking_plugin plugin."
},
{
"path": "example/test/widget_test.dart",
"chars": 928,
"preview": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester"
},
{
"path": "flutter_hand_tracking_plugin/android/flutter_hand_tracking_plugin.iml",
"chars": 1254,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module external.linked.project.id=\":flutter_hand_tracking_plugin\" external.linke"
},
{
"path": "flutter_hand_tracking_plugin.iml",
"chars": 1038,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">\n <component name=\"NewModuleRootManager\" "
},
{
"path": "ios/.gitignore",
"chars": 398,
"preview": ".idea/\n.vagrant/\n.sconsign.dblite\n.svn/\n\n.DS_Store\n*.swp\nprofile\n\nDerivedData/\nbuild/\nGeneratedPluginRegistrant.h\nGenera"
},
{
"path": "ios/Assets/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "ios/Classes/FlutterHandTrackingPlugin.h",
"chars": 97,
"preview": "#import <Flutter/Flutter.h>\n\n@interface FlutterHandTrackingPlugin : NSObject<FlutterPlugin>\n@end\n"
},
{
"path": "ios/Classes/FlutterHandTrackingPlugin.m",
"chars": 767,
"preview": "#import \"FlutterHandTrackingPlugin.h\"\n\n@implementation FlutterHandTrackingPlugin\n+ (void)registerWithRegistrar:(NSObject"
},
{
"path": "ios/flutter_hand_tracking_plugin.podspec",
"chars": 968,
"preview": "#\n# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.\n# Run `pod lib lint flutter_hand_"
},
{
"path": "lib/HandGestureRecognition.dart",
"chars": 3720,
"preview": "import 'dart:math';\n\nimport 'package:flutter_hand_tracking_plugin/gen/landmark.pb.dart';\n\nenum Gestures {\n FIVE,\n FOUR"
},
{
"path": "lib/flutter_hand_tracking_plugin.dart",
"chars": 1772,
"preview": "import 'dart:async';\n\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package"
},
{
"path": "lib/gen/landmark.pb.dart",
"chars": 7473,
"preview": "///\n// Generated code. Do not modify.\n// source: landmark.proto\n//\n// @dart = 2.3\n// ignore_for_file: camel_case_types"
},
{
"path": "lib/gen/landmark.pbenum.dart",
"chars": 224,
"preview": "///\n// Generated code. Do not modify.\n// source: landmark.proto\n//\n// @dart = 2.3\n// ignore_for_file: camel_case_types"
},
{
"path": "lib/gen/landmark.pbjson.dart",
"chars": 1124,
"preview": "///\n// Generated code. Do not modify.\n// source: landmark.proto\n//\n// @dart = 2.3\n// ignore_for_file: camel_case_types"
},
{
"path": "lib/gen/landmark.pbserver.dart",
"chars": 252,
"preview": "///\n// Generated code. Do not modify.\n// source: landmark.proto\n//\n// @dart = 2.3\n// ignore_for_file: camel_case_types"
},
{
"path": "protos/landmark.proto",
"chars": 1425,
"preview": "// Copyright 2019 The MediaPipe Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you ma"
},
{
"path": "protos/regenerate.md",
"chars": 672,
"preview": "# Generate protobuf files in Dart\n1. If upgrading, delete all proto files from /home/.pub-cache/bin\n1. Clone the latest "
},
{
"path": "pubspec.yaml",
"chars": 1938,
"preview": "name: flutter_hand_tracking_plugin\ndescription: A new Flutter hand tracking plugin.\nversion: 0.0.1\nauthor:\nhomepage:\n\nen"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the zhouzaihang/flutter_hand_tracking_plugin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (11.0 MB), approximately 37.7k tokens, and a symbol index with 70 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.