まえがき
最近、かなり大きなプロジェクトをQt(C ++)からAndroid(Java)に移植しました。作業の過程で、しばしば動的オブジェクトバインディングを使用する必要がありました。 問題は、JavaのQtの通常のシグナルやスロットとは異なり、バインディングがリスナーを介して実装され、このメソッドが同等であり、シグナルを使用する場合と同じ利便性があると自分自身に納得させようとしなかったことですスロットに到達できませんでした。
たとえば、スライダー(QtのQSliderまたはAndroidのSeekBar)を何らかのアクションに関連付け、少なくとも最初のスライダーの後に素直に移動する別のスライダーをアタッチする必要があります。 Qtでは、同様の操作は次のとおりです。
例1
// QSlider *primary = new QSlider(this); QSlider *secondary = new QSlider(this); // , // ... // connect(primary, SIGNAL(valueChanged(int)), secondary, SLOT(setValue(int)));
その結果、 プライマリ スライダーの valueChanged()信号とセカンダリ スライダーのsetValue()スロットの接続を取得します。 Androidでも同じこと:
例2
// SeekBar firstBar = (SeekBar)findViewById(R.id.firstSeekBar); SeekBar secondBar = (SeekBar)findViewById(R.id.secondSeekBar); // // ... // firstBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override // ........ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { secondBar.setProgress(progress); } });
そして結果として、同じこと、つまりfirstBarとsecondBarの動きを関連付けます。
ここで何が起こるか見てみましょう。 firstBarの腸のどこかにOnSeekBarChangeListener型の変数があり、移動が発生するとnullにチェックされ、突然ゼロ以外になった場合、 onProgressChanged()メソッドが適切なパラメーターで呼び出され、その結果 、 secondBar.setProgress(progress)が呼び出されます2番目のスライダーの値を設定します。
多少面倒ですが、すべてが非常に明確で理解しやすいものです。 この場合のQtはより簡潔ですが、 MOC(Meta Object Compiler)を使用してプロジェクトをビルドするプロセスでコードを生成することで動的リンクを実装することはC ++の範囲を超えています。 簡潔さの代価を支払う必要があります。これは、デバッグの過程で事前に生成されたコードに気づいたときに明らかになります。 しかし、幸いなことに、最も単純なルールに従えば、これは非常にまれです。
しかし、Androidに戻ります。 Android APIのすべてのクラスには、利便性を確保するのに十分なライセンサーのセットがありますが、信号とスロットで動作するコードの大きな配列がある場合はどうでしょうか? グーグルでは、Javaでのシグナルとスロットの実装をいくつか見つけましたが、最も価値があるのは、当然のことながら、QtのJavaでの不当に忘れられた実装であるQt Jambiライブラリです。 しかし、優れた実装はいくつかの理由で私には向いていませんでした。その最も重要な理由は、構文が元の構文と一致しないため、C ++とJavaのライブラリの同じテクノロジーが非常に異なって実装されていることです。
その結果、Java用のAndroid用の信号とスロットを独自に実装するというアイデアが生まれました。
挑戦する
Android APIを使用して、可能な限りQt C ++構文に近いJavaでシグナルおよびスロットメカニズムを実装します。
実装
どうした
何度か試行した結果、Java コネクタクラスは600行未満で記述されました。これには、いくつかの静的メソッドと、信号とスロットの静的マップ(Map)があります。 すべての機能は、4つの静的メソッドに囲まれています。
- ブール接続(オブジェクト送信側、ストリング信号、オブジェクト受信側、ストリングスロット、ConnectionTypeタイプ)
- void disconnect(オブジェクト送信者、文字列信号、オブジェクト受信者、文字列スロット)
- void emit(オブジェクト送信者、文字列signalName、オブジェクト... params)
- オブジェクト送信者()
- connect()を使用すると、信号とスロットを接続できます。このコンテキストでは、Qtと同様に値DirectConnectionとQueuedConnectionを取得できる列挙インスタンスを入力します。 デフォルト値のDirectConnectionは、例1のライセンサーと同じように機能します。QueuedConnectionは、Android APIのハンドラーを使用して、スロットへの非同期呼び出しを実装します。 残りのタイプの接続は実装されていません。 リスト2は、ポータブルプロジェクトで発生した100%のケースをカバーしています。
- disconnect()は 、接続の逆の操作です。 スロットから信号を切断します。 4、3、2、および1パラメーターのオプションがあります。 特定のスロットとの信号の接続、受信機(受信機)のすべてのスロットとの信号、すべてのスロットとの信号、およびすべてのスロットとの送信機(送信機)の接続をそれぞれ切断します。
- emit()を使用すると、プログラムの任意の場所からシグナルを送信できます。 最初のパラメーターには、この信号が送信するオブジェクト(送信者)への参照(通常はthis )が含まれます。 この場合、他の実装とは異なり、メソッドまたは変数として信号を宣言する必要はありません。信号は、 connect()メソッドで渡される文字列と必ず一致する必要がある任意の文字列です。 さらに、コンマの後に、任意のタイプの任意の数のパラメーターを続けることができます。それらのすべて、または少なくとも最初のパラメーターは、スロットのパラメーターと一致する必要があります
- sender()は 、スロットの本体から呼び出され、シグナルを送信したオブジェクトへのポインター(Javaでは、Object型のリンク)を返す、非常に便利なメソッドです。
ランナーの例では、接続は次のようになります。
例3
// Android API private static class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { private Object mSender = null; public SeekBarChangeListener(Object sender) { mSender = sender; } @Override // ........ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { Connector.emit(mSender, "progressChanged", progress); } } // SeekBar firstBar = (SeekBar)findViewById(R.id.firstSeekBar); SeekBar secondBar = (SeekBar)findViewById(R.id.secondSeekBar); // // ... // firsBar firstBar.setOnSeekBarChangeListener(new SeekBarChangeListener(firstBar)); // Connector.connect(firstBar, "SIGNAL(progressChanged(int))" , secondBar, "SLOT(setProgress(int))");
一見、 Connectorを使用した実装はlysenerを使用するよりも煩雑に思えるかもしれませんが、 SeekBarクラスは、他のすべての標準Android APIクラスと同様に、lysenerの使用によってシャープ化されることを忘れないでください。したがって、ラッパーを使用する必要があります。 クラスを開発するときにコネクタを使用するか、QtプロジェクトをAndroidに移植することにより、さらに多くのメリットを得ることができます。
- ライゼナー用のインターフェースを作成する必要はありません
- 変数を作成する必要はありません
- ライザーのセッターメソッドは不要
より複雑な例、実装の詳細については、次の記事をご覧ください。
(続く)