Kanzi Android framework (droidfw) での状態変化の観察とイベントの処理

状態の変化を観察する

KanziView の状態変化を監視するために KanziViewListener を実装できます。これを使用して、カスタムの初期化および非初期化ロジックを挿入します。

例:

  • KanziViewListener.onAttachedToWindow コールバックを使用してドメインにアクセスし、Kanzi Engine Java プラグインを登録するか、手動でビューのノードツリーを設定します。

    view.addListener(new KanziViewListener() {
       ...
    
       @Override
       public void onAttachedToWindow(View view, Domain domain)
       {
             // Register a plugin.
             domain.registerPlugin(new MyPlugin());
    
             // Manually setup the node tree.
             // Instantiate a node.
             ObjectRef<TextBlock2D> text = TextBlock2D.create(domain, "text");
             // Set as root of the view.
             ((KanziView) view).setRoot(text.get());
             // Set properties on a node.
             text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!");
    
    view.addListener(object : KanziViewListener {
       ...
    
       override fun onAttachedToWindow(view: View, domain: Domain) {
             // Register a plugin.
             domain.registerPlugin(MyPlugin())
    
             // Manually setup the node tree.
             // Instantiate a node.
             val text = TextBlock2D.create(domain, "text")
             // Set as root of the view.
             (view as KanziView).root = text.get()
             // Set properties on a node.
             text.get().setProperty(TextBox2D.TextProperty, "Hello Kanzi!")
    
  • 設定したスタートアッププレハブからインスタンス化されたノードツリーにアクセスして変更するには、KanziViewListener.onStartupPrefabLoaded コールバックを使用します。

    view.addListener(new KanziViewListener() {
       ...
    
       @Override
       public void onStartupPrefabLoaded(View view, Domain domain)
       {
             // Look up a child node starting from the root.
             TextBox2D text = ((KanziView)view).getRoot().lookupNode("#Text");
             // Set properties on a node.
             text.setProperty(TextBox2D.TextProperty, "Hello world!");
    
    view.addListener(object : KanziViewListener {
       ...
    
       override fun onStartupPrefabLoaded(view: View, domain: Domain) {
             // Look up a child node starting from the root.
             val text = (view as KanziView).root.lookupNode<TextBox2D>("#Text")
             // Set properties on a node.
             text.setProperty(TextBox2D.TextProperty, "Hello world!")
    

Kanzi Engine Java プラグインについて詳しく知るには、Kanzi Engine Java プラグインの作成 を参照してください。

入力の処理

デフォルトで、KanziSurfaceView と KanziTextureView は入力イベントを処理します。Kanzi Engine が KanziViewAdapter を使用する任意のビューのイベントを処理する場合、そのような入力イベントを手動で転送する必要があります。これを行うには、次のインターフェースを使用します。

すべての Kanzi ビューは、デフォルトで focusableInTouchMode を True に設定します。これは、プログラムによって、またはレイアウト属性を通じて別途設定されない限り、クリックされたときにフォーカスを取得し保持するようにします。詳細は、Androidタッチモード を参照してください。

Android Automotiveのロータリーコントローラーからの入力を処理する

デフォルトで、KanziSurfaceView と KanziTextureView はロータリーコントローラー入力を処理します。

ロータリーアクション

Kanzi がアクションをどのように処理するか

../../../_images/rotary-input-nudge.png
  • 上に微調整

  • 下に微調整

  • 左に微調整

  • 右に微調整

Kanzi Android framework (droidfw) は、これらのアクションを対応する矢印キーイベントとして処理します。

Kanzi は対角方向の微調整からの入力をサポートせず、無視します。

../../../_images/rotary-input-center.png

中央ボタン

Kanzi Android framework (droidfw) は、フォーカスされた Kanzi ビューの直接操作モードを有効にしようとします。Kanzi Android framework (droidfw) が直接操作モードを有効にできない場合、キーイベントを Kanzi Engine に転送します。Kanzi Engine は、ロータリーセンターボタンのキーコード KeyEvent.KEYCODE_DPAD_CENTER を Enter 論理キーに変換します。Direct manipulation mode を参照してください。

../../../_images/rotary-input-back.png

戻るボタン

Kanzi Android framework (droidfw) はキーイベントを Kanzi Engine に転送します。Kanzi Engine がイベントを消費しない場合、Kanzi Android framework (droidfw) は Kanzi ビューの直接操作モードを無効にします。

../../../_images/rotary-input-rotate.png
  • 反時計回りに回転

  • 時計回りに回転

Androidは、回転の方向と押された回数を含む一般的なモーションイベントを送信します。

Kanzi ビューは、すべての一般的なモーションイベントを KanziViewAdapter.handleGenericMotionEvent に転送します。

KanziViewAdapter は、イベントがロータリーコントローラーから来ているかどうかを判断し、Kanzi ビューが直接操作モードにあるかどうかを確認します。Kanzi ビューが直接操作モードにある場合、Kanzi ビューは回転情報を Kanzi Engine に転送します。Kanzi Engine は翻訳します:

  • 時計回りの回転を Tab キーイベントに

  • BackTab キーイベントへの反時計回りの回転

デフォルトでのロータリーコントローラー入力処理を無効にするには、次のいずれかのアプローチを使用します。

  • レイアウトファイルで、Kanzi ビュー要素に対して、rotaryHandlingEnabled 属性を false に設定します。

    <com.rightware.kanzi.KanziSurfaceView
        android:id="@+id/rotaryview"
        app:name="Rotary"
        app:kzbPathList="rotary.kzb"
        app:startupPrefabUrl="kzb://rotary/StartupPrefab"
        app:rotaryHandlingEnabled="false"/>
    
  • アクティビティコードで、false を KanziViewAdapter.setRotaryHandlingEnabled 関数呼び出しに渡します。

    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.activity_main);
        mView = findViewById(R.id.rotaryview);
        mView.getAdapter().setRotaryHandlingEnabled(false);
    }
    

向きの変更を処理する

画面の向きが変わると、Androidアプリケーションはデフォルトでアクティビティを破棄して再作成します。これにより、アプリケーションは縦向きと横向きの画面に対して別のレイアウトを使用できますが、内部のすべての Kanzi 状態が失われることも意味します。

この動作を上書きするには、AndroidManifest.xml ファイルに android:configChanges="orientation" プロパティを追加します。このオプションを使用すると、アクティビティは方向変更時に破棄されず、代わりにアクティビティ上で onConfigurationChanged() が呼び出されます。このイベントを手動で KanziView.handleOrientationChange に転送してください。このオプションにより、Kanzi コントロールの再作成を避けることができ、したがってすべての内部状態が保持されます。Android の構成変更 を参照してください。

ビューがサーフェスを持つ前、またはビューがサーフェスを失った後にイベントをフォワードすることができます。その場合、Kanzi はイベントを無視します。

このイベントは Kanzi コンテンツを回転させません。Kanzi コンテンツの向きを設定するには、スクリーン (Screen) ノード の Orientation プロパティを使用します。

Android ライフサイクルの処理

KanziSurfaceView と KanziTextureView はデフォルトでウィンドウとサーフェスのイベントを処理しますが、KanziViewAdapter を使用する任意のビューは、Kanzi が処理するウィンドウとサーフェスのイベントを、KanziViewAdapter.handleAttachedToWindow、KanziViewAdapter.handleSurfaceCreated、KanziViewAdapter.handleSurfaceChanged、KanziViewAdapter.handleSurfaceDestroyed、および KanziViewAdapter.handleDetachedFromWindow インターフェースを通じて手動で転送する必要があります。

ビューのユーザー可視性の処理

Kanzi Android framework (droidfw) は、接続されている各ビューがユーザーに表示されているかどうかを自動的かつ継続的に追跡し、非表示のビューのレンダリングと入力処理を停止します。

ビューがユーザーに表示されているかどうかを確認するために、Kanzi Android framework (droidfw) は以下を考慮します。

  • ビューの可視状態

  • ビューのすべての祖先の可視状態

  • ビューのアルファ

  • View.getGlobalVisibleRect() を通じて計算されたビューの実際に見える部分

KanziViewAdapter を使用する任意のビューは、Kanzi が KanziViewAdapter.handleVisibilityChange インターフェースを通じて処理する可視性イベントを手動で転送する必要があります。

アイドル状態

Kanzi Android framework (droidfw) は、処理すべき作業がなくなると不要な処理を自動的に一時停止します。少なくとも 1 つの Kanzi ビューがアクティブである間は、作業が発生すると Kanzi Android framework (droidfw) が起動します。アクティブなビューがない間は、Kanzi Android framework (droidfw) はフレームを実行しません。アプリケーションアイドル状態 と アクティブなビューがない間のタスクの送信 を参照してください。

一時停止中に Kanzi メインループの外部からの変更を検出するために、Kanzi Android framework (droidfw) は次のいずれかを実行できます。

  • Androidの MessageQueue.IdleHandler イベントをサブスクライブする。

  • Android Choreographerで軽量なタスクを実行する。

デフォルトでは、Kanzi Android framework (droidfw) はAndroidのアイドルイベントをサブスクライブし、Androidアプリケーションがアイドル状態になったときに外部の変更をチェックします。このモードでは、一時停止中の Kanzi のCPU使用率は完全にゼロになります。

KanziRuntime.enableSuspendedChangeTrackingTask を呼び出すと、Kanzi がバックグラウンドタスクを使用するように切り替えることができます。このモードでは、Kanzi は KanziRuntime.setSuspendedChangeCheckRate で設定したレートでAndroid Choreographer上の軽量なタスクを実行します。このレートを変更することで、Kanzi が外部の変更をチェックする頻度を制御できます。デフォルト値の0では、Kanzi はディスプレイのvsyncレートで外部の変更を追跡します。このレートを下げて関連するCPU消費をさらに抑えることもできますが、その代わりに作業が発生したときの再開が遅れます。このレートは、フレームレート制限によって制御されるレンダリングおよびアニメーションのレートとは無関係です。

アクティブなビューがない間のタスクの送信

Kanzi Android framework (droidfw) は、少なくとも 1 つの Kanzi ビューがアクティブである間のみ Kanzi メインループを実行します。Kanzi ビューは、サーフェスを持ち、その可視性が VISIBLE である間アクティブです。KanziTextureView は、そのアクティビティが停止したときにもアクティブではなくなります。ユーザーに対する可視性のみを理由に Kanzi Android framework (droidfw) が非表示にしたビュー (たとえば、別のビューに覆われているビュー) は、アクティブなままです。ビューのユーザーに対する可視性の処理 を参照してください。

アクティブなビューがない間 (たとえば、アプリケーションがバックグラウンドにある間)、Kanzi はフレームを実行しません。この間に TaskDispatcher に送信したタスクは待機状態になります。Kanzi は、ビューが再びアクティブになった後の最初のフレームで、それらのタスクをすべて実行します。アプリケーションがタスクを高い頻度で送信し続けると (たとえば、データソースが更新されるたびに送信する場合)、これらのタスクは蓄積されます。その結果、ビューの最初のフレームは、それらすべてのタスクの実行にかかる時間だけ遅延します。

アクティブなビューがない間は、更新ごとにタスクを送信しないでください。代わりに、各データアイテムの最新の値を保持し、値を適用するタスクが待機していない場合にのみタスクを送信します。こうすることで、アクティブなビューがない状態がどれだけ続いても、待機するタスクは最大 1 つになります。次に例を示します。

private final AtomicReference<String> mLatestValue = new AtomicReference<>();
private final AtomicBoolean mTaskPending = new AtomicBoolean(false);

// Set this to domain.getTaskDispatcher(), for example in
// KanziViewListener.onAttachedToWindow. Start the updates only after that.
private volatile TaskDispatcher mTaskDispatcher;

// Called from any thread for each update of the data source.
void onDataChanged(String value)
{
    mLatestValue.set(value);
    if (mTaskPending.compareAndSet(false, true))
    {
        mTaskDispatcher.submit(() -> {
            mTaskPending.set(false);
            // Set the latest value to a Kanzi object, for example a data object.
            applyValue(mLatestValue.get());
        });
    }
}
private val latestValue = AtomicReference<String>()
private val taskPending = AtomicBoolean(false)

// Set this to domain.taskDispatcher, for example in
// KanziViewListener.onAttachedToWindow. Start the updates only after that.
@Volatile
private lateinit var taskDispatcher: TaskDispatcher

// Called from any thread for each update of the data source.
fun onDataChanged(value: String) {
    latestValue.set(value)
    if (taskPending.compareAndSet(false, true)) {
        taskDispatcher.submit {
            taskPending.set(false)
            // Set the latest value to a Kanzi object, for example a data object.
            applyValue(latestValue.get())
        }
    }
}