在有 pigeon | Dart Package (flutter-io.cn) 之前,想写一个原生交互,是相当麻烦的一件事情。项目里面用 pigeon 来写了几个原生插件,用下来感觉还是很方便的,但是在开始使用的时候,我发现这方面的文章几乎没有,单端选手(几乎都是经常安卓端)很多,而且常常只是介绍 Flutter 端到原生端的通信,而原生端到 Flutter 端的几乎看不到介绍。
做项目的时候遇到用 RepaintBoundary 无法给 Webview 截屏的问题,查了下,是个已知的问题。
RepaintBoundary Cannot take Screenshot of Platform Views
然后上 Dart packages (flutter-io.cn) 试用了几个,不能说不能用,而是完全不能用。
FlutterView/FlutterRenderer 去获取的 getBitmap,结果当然还是失败。Software rendering doesn't support hardware bitmaps。 Google 下要用 PixelCopy 。android - java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps - Stack Overflow
flutter_boost ,导致插件获取 Controller 失败。最后还是决定自己写好了。
flutter create -i objc -a java --org com.fluttercandies.plugins --template plugin --platforms ios,android ff_native_screenshot
dev_dependencies:
pigeon: ^3.1.0
@HostApi() 标记的,是用于 Flutter 调用原生的方法。@FlutterApi() 标记的,是用于原生调用 Flutter 的方法。@async 如果原生的方法,是异步回调那种,你就可以使用这个标记dart 的基础类型/// Flutter call Native
@HostApi()
abstract class ScreenshotHostApi {
@async
Uint8List? takeScreenshot();
void startListeningScreenshot();
void stopListeningScreenshot();
}
/// Native call Flutter
@FlutterApi()
abstract class ScreenshotFlutterApi {
void onTakeScreenshot(Uint8List? data);
}
我习惯放在脚本里面执行,免得每次都要手打。
执行 ./pigeon.sh 脚本内容如下:
flutter pub run pigeon \
--input pigeons/ff_native_screenshot.dart \
--dart_out lib/src/ff_native_screenshot.dart \
--objc_header_out ios/Classes/ScreenshotApi.h \
--objc_source_out ios/Classes/ScreenshotApi.m \
--objc_prefix FLT \
--java_out android/src/main/java/com/fluttercandies/plugins/ff_native_screenshot/ScreenshotApi.java \
--java_package "com.fluttercandies.plugins.ff_native_screenshot"
修改之前:
FfNativeScreenshotPlugin.h
#import <Flutter/Flutter.h>
**@interface** FfNativeScreenshotPlugin : NSObject<FlutterPlugin>
**@end**
FfNativeScreenshotPlugin.m
#import "FfNativeScreenshotPlugin.h"
**@implementation** FfNativeScreenshotPlugin
+ (**void**)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"ff_native_screenshot"
binaryMessenger:[registrar messenger]];
FfNativeScreenshotPlugin* instance = [[FfNativeScreenshotPlugin 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**
FfNativeScreenshotPlugin.h 增加继承 FLTScreenshotHostApi
#import <Flutter/Flutter.h>
#import "ScreenshotApi.h""
**@interface** FfNativeScreenshotPlugin : NSObject<FlutterPlugin,FLTScreenshotHostApi>
**@end**
FfNativeScreenshotPlugin.m
#import "FfNativeScreenshotPlugin.h"
**@implementation** FfNativeScreenshotPlugin
+ (**void**)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FfNativeScreenshotPlugin* instance = [[FfNativeScreenshotPlugin alloc] init];
FLTScreenshotHostApiSetup(registrar.messenger,instance);
}
- (**void**)startListeningScreenshotWithError:(FlutterError * **_Nullable** **__autoreleasing** * **_Nonnull**)error {
}
- (**void**)stopListeningScreenshotWithError:(FlutterError * **_Nullable** **__autoreleasing** * **_Nonnull**)error {
}
- (**void**)takeScreenshotWithCompletion:(**nonnull** **void** (^)(FlutterStandardTypedData * **_Nullable**, FlutterError * **_Nullable**))completion {
}
**@end**
我们需要注册并且保存 ScreenshotFlutterApi,用于原生给 Flutter 发送消息
FfNativeScreenshotPlugin.m
#import "FfNativeScreenshotPlugin.h"
**static** FLTScreenshotFlutterApi *screenshotFlutterApi;
**@implementation** FfNativeScreenshotPlugin
+ (**void**)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
screenshotFlutterApi = [[FLTScreenshotFlutterApi alloc] initWithBinaryMessenger: registrar.messenger ];
}
**@end**
修改之前:
public class FfNativeScreenshotPlugin implements FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private MethodChannel channel;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "ff_native_screenshot");
channel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
}
我们需要将 MethodCallHandler 替换成 ScreenshotHostApi, 移除无用代码
public class FfNativeScreenshotPlugin implements FlutterPlugin, ScreenshotApi.ScreenshotHostApi {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
ScreenshotApi.ScreenshotHostApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
ScreenshotApi.ScreenshotHostApi.setup(binding.getBinaryMessenger(), null);
}
@Override
public void takeScreenshot(ScreenshotApi.Result<byte[]> result) {
}
@Override
public void startListeningScreenshot() {
}
@Override
public void stopListeningScreenshot() {
}
}
我们需要注册并且保存 ScreenshotFlutterApi,用于原生给 Flutter 发送消息
private ScreenshotApi.ScreenshotFlutterApi screenshotFlutterApi;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
screenshotFlutterApi = new ScreenshotApi.ScreenshotFlutterApi(flutterPluginBinding.getBinaryMessenger());
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
screenshotFlutterApi = null;
}
剩下的工作就是对这些方法进行实现,感谢 google 和 pub.dev上的各个插件大佬, 让我能学习(白嫖)到原生知识,也感谢群里大佬们对原生部分代码的检查和建议。
我们可以做一些封装,将 lib/ff_native_screenshot.dart 修改为如下:
library ff_native_screenshot;
import 'dart:typed_data';
import 'src/ff_native_screenshot.dart';
export 'src/ff_native_screenshot.dart';
/// The util of NativeScreenshot
class FfNativeScreenshot {
factory FfNativeScreenshot() => _ffNativeScreenshot;
FfNativeScreenshot._();
static final FfNativeScreenshot _ffNativeScreenshot = FfNativeScreenshot._();
final ScreenshotHostApi _flutterScreenshotApi = ScreenshotHostApi();
/// take screenshot by native
Future<Uint8List?> takeScreenshot() => _flutterScreenshotApi.takeScreenshot();
/// ScreenshotFlutterApi setup
void setup(ScreenshotFlutterApi api) => ScreenshotFlutterApi.setup(api);
bool _listening = false;
/// whether is listening Screenshot
bool get listening => _listening;
/// start listening Screenshot
void startListeningScreenshot() {
_listening = true;
_flutterScreenshotApi.startListeningScreenshot();
}
/// stop listening Screenshot
void stopListeningScreenshot() {
_listening = false;
_flutterScreenshotApi.stopListeningScreenshot();
}
}
在 pubspec.yaml 中加入引入
dependencies:
ff_native_screenshot: any
Uint8List? data = await FfNativeScreenshot().takeScreenshot();
我们需要去实现 ScreenshotFlutterApi,在 onTakeScreenshot 方法中获取到系统截图时候返回的字节流。
@override
void initState() {
super.initState();
FfNativeScreenshot().setup(ScreenshotFlutterApiImplements());
FfNativeScreenshot().startListeningScreenshot();
}
@override
void dispose() {
FfNativeScreenshot().stopListeningScreenshot();
super.dispose();
}
class ScreenshotFlutterApiImplements extends ScreenshotFlutterApi {
ScreenshotFlutterApiImplements();
@override
Future<void> onTakeScreenshot(Uint8List? data) async {
// if it has something error
// you can call takeScreenshot
data ??= await FfNativeScreenshot().takeScreenshot();
}
}
实际上,插件只要你第一次双端实现好了,之后想加方法,改方法,都是很方便的事情。比起来需要手写 if else, 我更喜欢pigeon 的多端接口一致性,对于不太懂原生的开发,还是很友好的。
当然,写一个 Flutter 的原生插件,你将需要掌握:
别问为什么这么多,你在 google 上面搜索答案的时候就会发现,大佬们的答案往往是各种语言都有 o(╯□╰)o。
一次开发,5倍快乐,快来一起写 Flutter 原生插件吧!
ff_native_screenshot | Flutter Package (flutter-io.cn) 有需求的童鞋自取。
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的rubyyaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir