Contents
5. Add the platform-specific code
Now, you provide the platform-specific implementations for a synthesizer that makes sounds when pressing keys on the keyboard. You can think of this code as the library you will surface to Flutter. Often, when making a plugin, you already have a defined platform API that you’ll work from, as in this case.
さて、キーボードのキーを押すと音が鳴るシンセサイザーの、プラットフォーム固有の実装を提供していきましょう。このコードは、Flutterに対して提供するライブラリと考えることができます。プラグインを作る場合、今回のようにプラットフォームAPIがすでに定義されていて、それを元に作業をすることがよくあります。
You now have two separate implementations of the same functionality, one for iOS and one for Android. You need to get these compiling as part of your app so that the plugin can call into it.
同じ機能をiOS用とAndroid用の2つに分けて実装します。プラグインがこれらを呼び出せるように、アプリの一部としてこれらをコンパイルする必要があります。
Add to iOS
Add the following files to your project:
あなたのプロジェクトに下記のファイルを追加してください。
By placing these files in the ios/Classes
location, they will compile as part of the iOS build for your plugin.
これらのファイルを ios/Classes に配置することで、プラグインの iOS ビルドの一部としてコンパイルされます。
You can look at ios/plugin_codelab.podspec
to see that, by default, it uses globs to define which sources to compile. All you need to do is put the files in the right location.
ios/plugin_codelab.podspec を見ると、デフォルトでは、どのソースをコンパイルするかを定義するためにグロブを使用していることがわかります。必要なのは、ファイルを正しい場所に置くことだけです。
Add to Android
Add the following file to your project:
次のファイルをプロジェクトに追加してください。
android/src/main/java/com/example/plugin_codelab/Synth.java
By placing this Java file in the android/src/main/java/com/example
location, it will compile as part of the Android build for your plugin. You can look at the Gradle build system to see that you only need to place the file in the correct directory to get it to compile.
このJavaファイルをandroid/src/main/java/com/exampleの場所に置くことで、プラグイン用のAndroidビルドの一部としてコンパイルされます。Gradleのビルドシステムを見れば、ファイルを正しいディレクトリに置くだけでコンパイルできることがわかります。
Synthesizer interface explanation
The synthesizer interface is similar on iOS and Android, and consists of four methods:
synthesizerインターフェイスはiOSとAndroidで似ており、四つのメソッドで構成されています。
class Synthesizer {
void start();
void stop();
int keyDown(int key);
int keyUp(int key);
}
The keyUp()
and keyDown()
methods represent the events sent when a key on the musical keyboard is pressed down and released.
keyUp()
と keyDown()
メソッドはミュージカルキーボードの鍵盤が「押下された時」と「離された時」のイベントを表しています。
The key
argument represents which key is being pressed or released.
引数のkeyは、どの鍵盤が押されたか/離されたか、を表します。
It’s an enumeration of all the keys on the musical keyboard.
鍵盤にあるすべてのキーを列挙したものです。
The MIDI standard defines an enumeration for those keys, where 60 is the value for Middle Cand increments one for every black or white key ( semitone). Your plugin uses this definition.
MIDI規格では、これらのキーの列挙が定義されており、60はMiddle Cの値で、黒鍵、白鍵(半音)ごとに1ずつ増加します。プラグインはこの定義を使用しています。
6. Design the plugin API
The next step in making a plugin is thinking about what sort of information you want to send back and forth between Flutter and the host platform.
プラグインを作るための次のステップは、Flutterとホストプラットフォームの間でどのような情報をやり取りしたいかを考えることです。
If you’re trying to represent a library that already has an API defined, you can make your life easy, and mimic that interface.
すでにAPIが定義されているライブラリを表現しようとする場合、そのインターフェースを模倣することで、簡単に実現することができます。
In this codelab, we provide the synthesizer code for each platform, so you can mimic its interface in the Dart code:
このコードラボでは、各プラットフォームのシンセサイザーのコードを提供していますので、Dartのコードでそのインターフェースを模倣することができます。
lib/plugin_codelab.dart
import 'dart:async'; import 'package:flutter/services.dart'; class PluginCodelab { static const MethodChannel _channel = const MethodChannel('plugin_codelab'); static Future<String?> get platformVersion async { final String? version = await _channel.invokeMethod('getPlatformVersion'); return version; } static Future<int?> onKeyDown(int key) async { final int? numNotesOn = await _channel.invokeMethod('onKeyDown', [key]); return numNotesOn; } static Future<int?> onKeyUp(int key) async { final int? numNotesOn = await _channel.invokeMethod('onKeyUp', [key]); return numNotesOn; } }
Notice that the second parameter to invokeMethod()
lists the parameters that are sent to the method call.
invokeMethod()の第二引数のリストはmethod callに渡されるパラメーターです。
7. Implement the plugin platform code
Now you have platform-specific libraries for making sound and Dart code that controls that code, but they aren’t hooked up.
現時点では、音を出すためのプラットフォーム固有のライブラリと、そのコードを制御するDartのコードがありますが、それらが接続されていません。
If you call any of these Dart methods now, they result in “Not Implemented” exceptions because you haven’t implemented the host side in the plugin. That’s the next step.
今、これらのDartメソッドを呼び出すと、プラグインでホスト側を実装していないため、「Not Implemented」例外(exception)が発生します。これが次のステップです。
Hooking things up on iOS
First, modify the plugin to create and start a synthesizer instance:
まず、シンセサイザーのインスタンスを作成し、起動するようにプラグインを修正します。
ios/Classes/PluginCodelabPlugin.m
@implementation PluginCodelabPlugin {
int _numKeysDown;
FLRSynthRef _synth;
}
- (instancetype)init {
self = [super init];
if (self) {
_synth = FLRSynthCreate();
FLRSynthStart(_synth);
}
return self;
}
- (void)dealloc {
FLRSynthDestroy(_synth);
}
Next, start handling messages sent over the channel:
次に、channelを通じて送られてきたメッセージを処理します。
- (void)handleMethodCall:(FlutterMethodCall *)call
result:(FlutterResult)result {
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS "
stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
} else if ([@"onKeyDown" isEqualToString:call.method]) {
FLRSynthKeyDown(_synth, [call.arguments[0] intValue]);
_numKeysDown += 1;
result(@(_numKeysDown));
} else if ([@"onKeyUp" isEqualToString:call.method]) {
FLRSynthKeyUp(_synth, [call.arguments[0] intValue]);
_numKeysDown -= 1;
result(@(_numKeysDown));
} else {
result(FlutterMethodNotImplemented);
}
}
Notice that the code now looks for the onKeyDown
and onKeyUp
messages as well.
このコードでは、onKeyDownとonKeyUpのメッセージも探すようになっていることに注目してください。
In order to get the key
argument, pull it from call.arguments
.
引数 key を取得するために、call.argumentsから引数を取得しています。
The returned value is boxed as an NSNumber
(described in the Platform channels documentation), so convert it with intValue
.
返された値はNSNumberとしてボックス化されているので(Platform channelsのドキュメントに記載)、intValueで変換(キャスト)しています。
See the completed file, PluginCodelabPlugin.m.
Hooking things up on Android
First, modify the plugin to create and start a synthesizer instance:
はじめに、synthesizerインスタンスを生成するようにpluginを修正します。
android/src/main/java/com/example/plugin_codelab/PluginCodelabPlugin.java
public class PluginCodelabPlugin implements FlutterPlugin, MethodCallHandler {
private MethodChannel channel;
private Synth synth;
private static final String channelName = "plugin_codelab";
private static void setup(PluginCodelabPlugin plugin, BinaryMessenger binaryMessenger) {
plugin.channel = new MethodChannel(binaryMessenger, channelName);
plugin.channel.setMethodCallHandler(plugin);
plugin.synth = new Synth();
plugin.synth.start();
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
setup(this, flutterPluginBinding.getBinaryMessenger());
}
Next, start handling messages sent over the channel:
次に、channelを通じて送られてきたチャンネルを処理します。
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else if (call.method.equals("onKeyDown")) {
try {
ArrayList arguments = (ArrayList) call.arguments;
int numKeysDown = synth.keyDown((Integer) arguments.get(0));
result.success(numKeysDown);
} catch (Exception ex) {
result.error("1", ex.getMessage(), ex.getStackTrace());
}
} else if (call.method.equals("onKeyUp")) {
try {
ArrayList arguments = (ArrayList) call.arguments;
int numKeysDown = synth.keyUp((Integer) arguments.get(0));
result.success(numKeysDown);
} catch (Exception ex) {
result.error("1", ex.getMessage(), ex.getStackTrace());
}
} else {
result.notImplemented();
}
}
Similar to iOS, the code now looks for the onKeyDown
and onKeyUp
messages. Use arguments.get()
to extract the key
value here, as well. Make sure that, on Android, your plugin handles any exceptions that might arise.
iOSと同様に、onKeyDown
と onKeyUp
メッセージを探しています。
arguments.get()を使ってkeyの値を取得しています。Androidでは、プラグインが例外処理することを確認してください。
See the completed file, PluginCodelabPlugin.java.
8. Implement the example UI
Now that the plugin implements all of the plumbing, you probably want to see it in action. For that, you implement a simple keyboard UI example app:
さて、このプラグインのすべての配管を実装しましたので、実際に動作しているところを見たいですよね。そのために、シンプルなキーボードUIのサンプルアプリを実装します。
import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:plugin_codelab/plugin_codelab.dart'; enum _KeyType { Black, White } void main() { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeRight]) .then((_) { runApp(new MyApp()); }); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { String? _platformVersion = 'Unknown'; @override void initState() { super.initState(); initPlatformState(); } // Platform messages are asynchronous, so we initialize in an async method. Future<void> initPlatformState() async { String? platformVersion; try { platformVersion = await PluginCodelab.platformVersion; } on PlatformException { platformVersion = 'Failed to get platform version.'; } if (!mounted) return; setState(() { _platformVersion = platformVersion; }); } void _onKeyDown(int key) { print("key down:$key"); PluginCodelab.onKeyDown(key).then((value) => print(value)); } void _onKeyUp(int key) { print("key up:$key"); PluginCodelab.onKeyUp(key).then((value) => print(value)); } Widget _makeKey({@required _KeyType keyType, @required int key}) { return AnimatedContainer( height: 200, width: 44, duration: Duration(seconds: 2), curve: Curves.easeIn, child: Material( color: keyType == _KeyType.White ? Colors.white : Color.fromARGB(255, 60, 60, 80), child: InkWell( onTap: () => _onKeyUp(key), onTapDown: (details) => _onKeyDown(key), onTapCancel: () => _onKeyUp(key), ), ), ); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( backgroundColor: Color.fromARGB(255, 250, 30, 0), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text('Running on: $_platformVersion\n'), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ _makeKey(keyType: _KeyType.White, key: 60), _makeKey(keyType: _KeyType.Black, key: 61), _makeKey(keyType: _KeyType.White, key: 62), _makeKey(keyType: _KeyType.Black, key: 63), _makeKey(keyType: _KeyType.White, key: 64), _makeKey(keyType: _KeyType.White, key: 65), _makeKey(keyType: _KeyType.Black, key: 66), _makeKey(keyType: _KeyType.White, key: 67), _makeKey(keyType: _KeyType.Black, key: 68), _makeKey(keyType: _KeyType.White, key: 69), _makeKey(keyType: _KeyType.Black, key: 70), _makeKey(keyType: _KeyType.White, key: 71), ], ) ], ), ), ), ); } }
Notice the following:
以下のことに注目してください。
- You must import
'package:plugin_codelab/plugin_codelab.dart'
in order to use the plugin. The dependency of the example on the plugin is defined inexample/pubspec.yaml,
which makes this work.
このプラグインを使用するには、’package:plugin_codelab/plugin_codelab.dart’ をインポートする必要があります。プラグインに対するサンプルの依存関係は、example/pubspec.yaml で定義されているので、これで動作します。
- In
main(),
the orientation is forced to be landscape so that the whole keyboard can fit on the screen.
main()関数で、全てのキーボードが画面に表示されるように、画面の向きをlandscape(横向き)に固定しています。
- The
_onKeyDown()
and_onKeyUp()
methods are both clients of the plugin API designed in previous steps.
_onKeyDown()
と _onKeyUp()
メソッドは両方とも前のステップで実装したプラグインAPIのクライアントです。
- The code uses
InkWell
, which are just interactive rectangles, to draw the individual keys.
このコードでは、個々のキーを描画するために、インタラクティブな矩形であるInkWellを使用しています。
Run the app to see your functioning music keyboard:
あなたのミュージックキーボードが機能するか動かして確認してみましょう。
cd example flutter run
It should look like this:
9. Congratulations!
完全なサンプルコードはhttps://github.com/flutter/codelabs/tree/master/plugin_codelab
でダウンロードできます。
Next steps
- Add end-to-end testing. The Flutter team provides a library to create end-to-end integration tests called e2e.
end-to-endテストを追加してみましょう。Flutterチームはend-to-endインテグレーションテストのためのライブラリ(e2e)を提供しています。
- Publish to pub.dev. After you create a plugin, you may want to share it online so that others can use it. You can find the full documentation on publishing your plugin to pub.dev in Developing plugin packages.
pub.devに公開してみましょう。pluginを作ったら他者と共有したいと思うかもしれません。上記のドキュメントにてpluginの公開手順を説明しています。
Extend the synthesizer
For fun, if you want to play around with the synthesizer and improve it, here are some next steps you might consider:
楽しみとして、シンセサイザーで遊んでみたい、改良してみたいという方は、次のステップを考えてみてはいかがでしょうか。
今、シンセサイザーはサイン波を生成しています。ノコギリ波を生成するのはどうでしょうか?
- Did you notice the popping sounds when you press and release a key? That’s due to the oscillator abruptly turning on and off. Usually synthesizers remedy that with amplitude envelopes.
キーを押したり離したりすると、ポンと音がすることにお気づきでしょうか?それは、オシレータが突然オン・オフすることによるものです。通常、シンセサイザーはアンプリチュード・エンベロープでそれを改善します。
- Right now you can only play one key at a time. That’s called monophonic. Real pianos are polyphonic.
今は一度に一つの鍵盤しか弾けません。それをモノフォニックといいます。本物のピアノはポリフォニックです。
参考
https://codelabs.developers.google.com/codelabs/write-flutter-plugin#4