APLコマンド


APLコマンド

Alexa Presentation Language(APL)のコマンドとは、画面上のコンテンツの視覚表現やオーディオ表現を変更するメッセージです。開発者は、発話への応答として、Alexa.Presentation.APL.ExecuteCommandsディレクティブを使用してコマンドをデバイスに送信します。ボタン押下への応答など、APLドキュメントのイベントハンドラーを使って、コマンドを直接トリガーします。

個別のコマンドについては、APL標準コマンドを参照してください。

コマンドと画面アクション

コマンドがサポートするシーンでのアクションには、次のタイプがあります。

  • シーン内をナビゲートする
    • ScrollViewまたはSequenceをスクロールする
    • スクロールしてコンポーネントを表示する
    • Pagerで表示されるページを変更する
  • 既存シーン内でコンポーネントを変更する
  • 入力コントロールを更新して新しい状態を反映する
  • 既存コンポーネントの表示を変更する
  • 既存のシーン内でビデオクリップを再生/一時停止する
  • 音声
    • 単一コンポーネントのオーディオコンテンツを読む
    • 2つ以上のコンポーネントのオーディオコンテンツを読む
  • APL Extensionにコマンドを送信する

コマンド概要

以下の各セクションでは、イベントハンドラーまたは外部で生成されたコマンドによって開始されるコマンドを実行するアルゴリズムについて説明します。

コマンド配列は順に実行されることを想定しています。これらのコマンドは、通常モードまたは高速モードで実行されます。

通常モードの評価

通常モードで実行するコマンドには、名前付きのシーケンサーが必要です。

デフォルトでは、コマンドはMAINシーケンサーで実行されます。ただし、以下に該当する場合は除きます。

  • コマンドのsequencerプロパティに値が含まれている。
  • コマンドが、別のsequencerを持つコマンドのサブコマンドである。

配列の各コマンドについて、Alexaにより以下の手順が実行されます。

  1. コマンドのwhenプロパティを評価します。whenfalseに評価された場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。

  2. コマンドのdelayプロパティの値をチェックします。delayが0より大きい場合は、指定された時間(ミリ秒単位)だけ一時停止します。

  3. コマンドのsequencerプロパティの値をチェックします。sequencerの値が現在のシーケンサーと異なる場合は、このコマンドを指定されたシーケンサーに渡し、配列内の次のコマンドに進みます。

  4. コマンドのtypeプロパティを評価します。認識できない値がtypeに含まれている場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。

    有効なtype値には、次のようなものがあります。

    • SendEventなどのビルトインコマンド。すべての必須コマンドプロパティに値がある場合、コマンドを実行します。一部のコマンドはただちに返されます。このコマンドの実行が終了するまで、ほかのコマンドはシーケンサーを一時停止します。
    • ユーザー定義コマンド。ユーザー定義コマンドがインフレートしてコマンドの配列になります。通常モードの評価規則に従ってこれらのコマンドを実行し、すべてのコマンドが完了したら続行します。
    • Extensionコマンド。コマンドを実行します。一部のextensionコマンドはすぐに返されます。このコマンドの実行が終了するまで、ほかのコマンドはシーケンサーを一時停止します。

高速モードの評価

高速モードで実行されるコマンドには、名前付きのsequencerはありません。配列の各コマンドについて、以下が順に実行されます。

  1. コマンドのwhenプロパティを評価します。whenfalseに評価された場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。

  2. コマンドのsequencerプロパティの値をチェックします。sequencerに値が含まれている場合は、このコマンドを適切なシーケンサーに渡し、配列内の次のコマンドに進みます。渡されたコマンドは、高速モードではなく通常モードで実行されます。

  3. コマンドのtypeプロパティを評価します。認識できない値がtypeに含まれている場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。

    有効なtype値には、次のようなものがあります。

    • SetValueなど、高速モードをサポートするビルトインコマンド。すべての必須コマンドプロパティに値が含まれる場合、コマンドを実行します。高速モードをサポートするコマンドについては、高速モードのコマンド一覧を参照してください。
    • SendEventなど、高速モードをサポートしないビルトインコマンド。コマンドをスキップします。
    • ユーザー定義コマンド。ユーザー定義コマンドがインフレートしてコマンドの配列になります。高速モードの評価規則に従ってこれらのコマンドを実行し、すべてのコマンドが完了したら続行します。
    • 高速モードをサポートするextensionコマンド。コマンドを実行します。
    • 高速モードをサポートしないextensionコマンド。コマンドをスキップします。

コマンドの評価

コマンドの個々のプロパティはデータバインディングをサポートします。コマンドのデータバインディングコンテキストには、コマンドが定義されているコンテキストが含まれ、さらにeventプロパティが追加されます。このプロパティには、コマンドがトリガーされた状況に関する情報と、コマンドのターゲットとなったコンポーネントが含まれます。

イベントの内容

コマンドは、そのソースのデータバインディングコンテキストで評価されます。

  • ドキュメントのデータバインディングコンテキスト - viewportenvironment、ドキュメントのペイロードが含まれます。名前付きリソースとドキュメントのデータバインディングにアクセスできます。
  • コンポーネントのデータバインディングコンテキスト - コンポーネント固有のデータバインディングコンテキストです。ドキュメントのデータバインディングコンテキストに加えて、コンポーネント内のすべてのデータバインディングと、コンポーネントのすべての祖先にアクセスできます。

ExecuteCommandsディレクティブによってデバイスに送信されたコマンド、またはドキュメントのイベントハンドラーによって実行されたコマンドは、ドキュメントのデータバインディングコンテキストで評価されます。APLのイベント(画面タップなど)に応答して発行されたコマンドは、そのコマンドが定義されているコンポーネントのデータバインディングコンテキストで評価されす。

たとえば、次のようなTouchWrapperがあるとします。

{
  "type": "TouchWrapper",
  "bind": [
    "name": "myRandomData",
    "value": 24.3
  ],
  "onPress": {
    "type": "SendEvent",
    "arguments": [ "値は${myRandomData}です" ]
  }
}

ユーザーがTouchWrapperをタップすると、デバイスはスキルにUserEventを送信します。このリクエストのarguments配列には、「値は24.3です」という文字列が含まれます。

eventプロパティ

コマンドが評価されると、ソースのデータバインディングコンテキストにイベントデータが追加されます。このイベントデータには、eventプロパティを通じてアクセスします。

eventプロパティには、event.sourceevent.targetという2つのサブプロパティがあります。

  • event.sourceプロパティには、イベントを発生させたコンポーネントに関してシステムから提供される情報が含まれます。
  • event.targetプロパティには、イベントを受け取るコンポーネントに関してシステムから提供される情報が含まれます(該当する場合)。

すべてのコマンドにevent.targetプロパティがあるとは限りません。たとえば、SendEventコマンドにはevent.targetプロパティがありません。event.sourceプロパティはすべてのコマンドに存在します。event.sourceの詳細はそれぞれ異なる可能性があり、コンポーネントを表すとは限りません。たとえば、ソースがコンポーネントではなく、extensionの場合もあります。

データバインディングコンテキストに追加されるeventプロパティの形式は次のとおりです。

"event": {
  "source": {                   // 常に存在します
    "type": "COMPONENT_TYPE",     // コンポーネントまたは"Document"
    "handler": "EVENT_HANDLER",   // イベントハンドラー名前コマンドのメモを参照してください)
    "id": "SOURCE_ID",            // ソースコンポーネントID
    "uid": "SOURCE_UID",          // ソースコンポーネントUID
    ...                         // 追加のソースコンポーネントプロパティ
  },
  "target": {                   // コマンドコンポーネントターゲットある場合にのみ存在します
    "type": "COMPONENT_TYPE",     // ターゲットコンポーネント
    "id": "TARGET_ID",            // ターゲットコンポーネントID
    "uid": "TARGET_UID",          // ターゲットコンポーネントUID
    ...                         // 追加のターゲットコンポーネントプロパティ
  },
  ...   // コマンド固有のプロパティ
}

event.sourceプロパティにはhandlerプロパティがありますが、event.targetプロパティにはありません。

以下の表は、さまざまなコンポーネントからレポートされる標準のevent.sourceプロパティとevent.targetプロパティの一覧です。特定のコンポーネントのプロパティに関するドキュメント全文については、各コンポーネントのドキュメントを参照してください。

プロパティ 説明 レポート元

bind

マップ

データバインディングコンテキスト

コンポーネント

checked

ブール値

チェックの状態

コンポーネント

color

現在の色

Text

currentTime

整数

現在の再生位置

Video

disabled

ブール値

無効にされた状態

コンポーネント

duration

整数

現在のビデオの長さ(ミリ秒)

Video

ended

ブール値

ビデオが終了している場合はtrue

Video

focused

ブール値

フォーカス状態

コンポーネント

height

数値

高さ(dp)

コンポーネント

id

文字列

コンポーネントID

コンポーネント

layoutDirection

文字列

現在のlayoutDirectionLTRまたはRTL

コンポーネント

muted

ブール値

ビデオの現在のミュート状態

Video

opacity

数値

ローカル不透明度(累積値ではない)

コンポーネント

page

整数

現在表示されているページ

Pager

paused

ブール値

ビデオが一時停止している場合はtrue

Video

position

数値

スクロール距離の割合

pressed

ブール値

押された状態

コンポーネント

text

文字列

表示されるテキスト

Text

trackCount

整数

トラック数

Video

trackIndex

整数

現在のトラックのインデックス

Video

type

文字列

コンポーネントタイプ("Frame"など)

コンポーネント

uid

文字列

ランタイムで生成された固有ID

コンポーネント

url

文字列

ソースURL

width

数値

幅(dp)

コンポーネント

bindプロパティは、コンポーネントのデータバインディングコンテキストへのアクセスを提供します。次の例は、バインドされた値へのアクセス方法を示しています。IDがMyTextのコンポーネントが、SetValueコマンドのターゲットです。したがって、event.target.bindプロパティにはMyTextコンポーネントにバインドされたデータが含まれ、コマンドはその値を使用できます。

[
  {
    "type": "TouchWrapper",
    "onPress": {
      "type": "SetValue",
      "componentId": "MyText",
      "property": "text",
      "value": "今日の単語は${event.target.bind.WordOfTheDay}です"
    },
    "items": [
      {
        "type": "Text",
        "text": "クリックして"
      }
    ]
  },
  {
    "type": "Text",
    "bind": [
      {
        "name": "WordOfTheDay",
        "value": "熊"
      }
    ],
    "id": "MyText"
  }
]

event.source

eventevent.sourceプロパティには、イベントをトリガーした状況に関するメタ情報が含まれます。event.sourceはAPLランタイムによって生成され、ドキュメントでこの情報を使用できます。event.sourceプロパティには、すべての標準プロパティと以下のプロパティが含まれます。

プロパティ 説明
handler 文字列 このメッセージを開始したイベントハンドラーの名前です。たとえば、PressCheckedです。
value 任意 このメッセージを開始したコンポーネントの値です。

event.source.valueプロパティは、コンポーネントによって異なります。たとえば、TouchWrapperの場合、event.source.valueにはコンポーネントのチェック状態が含まれます。ScrollViewの場合、このプロパティにはスクロール位置が含まれます。提供される特定のevent.source.valueプロパティについては、個々のコンポーネントの定義を参照してください。

APLの以前のバージョンとの下位互換性のために、event.sourceプロパティにはsourceサブプロパティも含まれています。これはtypeプロパティと同じです。つまり、${event.source.source == event.source.type}となります。event.source.sourceプロパティは廃止されました。このプロパティは使用しないでください。

event.target

event.targetプロパティは、イベントを受け取るコンポーネントについての状態情報を提供します。targetの値は、コンポーネントによって異なります。想定される特定のtargetプロパティについては、個々のコンポーネントの定義を参照してください。

APLの以前のバージョンとの下位互換性のために、イベントのtargetプロパティはsourceサブプロパティもレポートします。これは、ImageVectorGraphicVideoの各コンポーネントのurlプロパティと同じです。つまり、${event.target.source == event.target.url}となります。event.target.sourceプロパティは廃止されました。このプロパティは使用しないでください。

評価に関する注意

  • ほとんどのコマンドは、ターゲットとしてcomponentIdを受け取ります。自身をターゲットとするコマンドではcomponentIdを省略できます。
  • ExecuteCommandsディレクティブを使用してデバイスにコマンドを送信する場合は、常にcomponentIdを指定する必要があります。
  • コマンドにcomponentIdプロパティが含まれている場合、コマンドはツリー階層のルートからすべてのコンポーネントを深さ優先検索で検索し、値と一致する識別子を持つ最初のコンポーネントをターゲットにします。コマンドが正しいコンポーネントをターゲットにしていることを保証するには、以下のいずれかを実行します。
    • idプロパティを持つターゲットコンポーネントに一意の値を割り当て、コマンドのcomponentIdをその値に設定します。
    • 視覚コンテキストで提供される、システムが生成したuidプロパティ値を使用します。
  • コマンドのデータバインディング式は、コマンドを定義するときではなく、コマンドを実行するときに評価されます。たとえば、ExecuteCommandsディレクティブに、グローバルデータバインディングコンテキストを参照するデータバインディング式が含まれていることがあります。これらの式は、デバイスでコマンドが実行されたときに評価されます。コマンドがクラウドで作成されたときではありません。
  • イベントハンドラーとExecuteCommandsディレクティブは、実行するコマンドの配列を受け取ります。この配列は、repeatCountが0のSequentialコマンドのように機能します。

コマンドのシーケンス実行

APLランタイム環境でコマンドが実行されます。ユーザーと環境のアクションが、実行するコマンドをトリガーします。ParallelコマンドとSequentialコマンドも、実行するコマンドのセットをトリガーします。コマンドのシーケンス実行ルールによって、複数のコマンドが同時に実行される場合の動作が決まります。これらのルールは、コマンドが相互に競合することを防止します。コマンドの定義方法に基づいて、シーケンス実行の動作の一部を制御できます。

以下の用語を使用します。

  • リソース – 特定のコマンドによって使用され、ほかのコマンドとは共有できないもの。たとえば、テキストを読み上げるコマンドは"speech"リソースを使用します。特定のコンポーネントに対して一定時間作用するコマンド(AnimateItemなど)では、そのコンポーネントがリソースと見なされます。
  • 通常モード – コマンドを実行する標準の方法です。ほとんどのコマンドは、通常モードで実行されます。通常モードのコマンドは、実行に時間がかかる場合があります。
  • 高速モード – コマンドを実行するもう1つの方法です。実行に時間のかかるコマンドが適切でない場合に使用します。高速モードでコマンドを実行する場合、コマンドはすべての遅延を無視し、実行に時間がかかるコマンドをスキップします。onScrollなどのイベントハンドラーは、高速モードを使用します。これらのイベントハンドラーは、1秒間に複数回起動することがあります。
  • シーケンサー – 一度に1つのコマンドを実行できる名前付きのエンティティです。通常モードの各コマンドには、名前付きのシーケンサーがあります。シーケンサーを指定するには、sequencerプロパティを設定します。
  • サブコマンド – 別のコマンド内に含まれるコマンドです。SequentialParallelOpenURLSelectなど、サブコマンドを含めることができるコマンドは複数あります。サブコマンドの階層は、「コマンドツリー」と呼ばれることもあります。

コマンドのシーケンス実行ルール

APLランタイムは、コマンドをシーケンス実行する際、次のルールを使用します。

  1. 通常モードのコマンドはすべて、単一のシーケンサーで実行されます。そのシーケンサーが既にコマンドを実行している場合、ランタイムは実行中のコマンドを停止し、新しいコマンドを開始します。
  2. 高速モードのコマンドはシーケンサーを使用しません。
  3. シーケンサーを明示的に指定するコマンドは、いずれも通常モードで実行されます。
  4. サブコマンドは、サブコマンドが明示的にシーケンサーを指定しない限り、親コマンドと同じシーケンサー上で実行されます。サブコマンドがシーケンサーを指定している場合、ランタイムはそのコマンドをシーケンサーに引き渡して、親コマンド内でそのコマンドを完了とマークします。
  5. デフォルトのシーケンサーはMAINです。サブコマンド以外の通常モードコマンドでシーケンサーが明示的に指定されていない場合、そのコマンドはMAINシーケンサーで実行されます。通常モードのサブコマンドは、ルール4に従います。
  6. 画面タッチやキーボード入力など、ユーザーが何らかの物理的なデバイス操作を行うと、MAIN sequencerで実行されているコマンドはすべて停止します。このルールは、音声対話には適用されません。コマンドシーケンスの実行中にユーザーがウェイクワードを発して割り込んでも、MAINシーケンサーは続行され、停止しません。
  7. コマンドの実行が開始されると、既に実行されているコマンドで使用中のリソースがコマンドに必要な場合、ランタイムは実行中のコマンドを停止します。
  8. 設定変更ハンドラーが実行されると、以前の設定で実行されているコマンドはすべて停止します。

たとえば、ユーザーが画面上のボタンをタッチしたとします。この操作によって、MAINシーケンサー上で次のような一連のコマンドが開始されます。

  1. 画面上の変化をアニメーション化します。
  2. 特定の位置までリストを下にスクロールします。
  3. 項目を読み上げます。

ユーザーは、画面をもう一度タッチすることで、この一連のコマンドをいつでも中断できます。ユーザーが画面をタッチすると、MAINシーケンサーは現在実行中のコマンドを停止します。

デフォルトのシーケンス実行動作をオーバーライドするには、シーケンサーを指定します。この手法を使うと、コマンドを並行して実行できます。シーケンス実行動作を変更するシナリオには、次のような場合が考えられます。

  1. 通常は高速モードで実行されるイベントハンドラーを使って、通常モードのコマンドを実行したい場合。たとえば、ScrollViewコンポーネントのonScrollイベントハンドラーで、SendEventコマンドをトリガーしたいとします。通常、onScrollハンドラーは、通常モードのコマンドを無視します。SendEventにシーケンサー名を指定すると、onScrollハンドラーは通常モードでコマンドを実行するため、SendEventが実行されます。
  2. 通常のユーザー対話動作を上書きして、ユーザーが画面をタッチしてもコマンドの実行を続けたい場合。たとえば、子ども向けのゲームで、AnimateItemコマンドを使用して画面上のコンポーネントをアニメーション化しているときに、画面がタッチされてもアニメーションを停止したくない場合があります。
  3. 複数コンポーネントのアニメーションがトリガーされた場合、新しいコンポーネントを表示して古いコンポーネントを非表示にします。

リソース

各コマンドは、実行の際に1つ以上のシステムリソースを使用できます。シーケンサールールで定義されているように、特定のリソースを使用するコマンドは1回に1つしか実行できません。そのリソースを必要とする新しいコマンドが実行を開始すると、古いコマンドは停止します。

以下は、リソースと、そのリソースを使用するコマンドの一覧です。

  • フォアグラウンドオーディオ再生
    • SpeakItem
    • SpeakList
    • PlayMedia(フォアグラウンドのオーディオトラックを使用する場合)
    • ControlMedia(フォアグラウンドのオーディオトラックでplayコマンドを実行する場合)
  • バックグラウンドオーディオ再生
    • PlayMedia(バックグラウンドのオーディオトラックを使用する場合)
    • ControlMedia(バックグラウンドのオーディオトラックでplayコマンドを実行する場合)
  • スクロール位置(スクロールコンポーネント、つまりScrollViewまたはSequence
  • ページャー位置(Pagerコンポーネント)
  • コンポーネントアニメーション(コンポーネントプロパティのアニメーション化)

SpeakItemSpeakListは、複数のリソースを使用する可能性があります。

通常モード

APLランタイムは、次のシナリオではコマンドを通常モードで実行します。

  • ドキュメントが最初に読み込まれるとき(ドキュメントのonMountおよびコンポーネントのonMount)。
  • ユーザーがTouchWrapperをタッチまたは選択したとき(onPress)。
  • ユーザーがPager上でスワイプし、表示ページを変更したとき(onPageChanged)。
  • Videoコンポーネントがビデオの再生を終了するか、トラックを変更したとき(onEndonPauseonPlayonTrackUpdate)。
  • ExecuteCommandsディレクティブやExtensionイベントハンドラーなどの外部ソースから新しいコマンドセットを受信したとき。
  • ユーザーがキーボードのキーを押したとき、または解放したとき(handleKeyDownおよびhandleKeyUp)。

デフォルトでは、すべての通常モードコマンドはMAINシーケンサーを使用します。

次の例では、onPressイベントハンドラーにコマンドの配列があります。この配列は、シーケンス実行される配列として扱われます。そのため、SpeakItemが最初に実行されます。読み上げが完了すると、Scrollコマンドが実行されます。Scrollが完了すると、SendEventコマンドが実行され、スキルにUserEventが送信されます。これらのコマンドはすべて、MAINシーケンサーで実行されます。

{
  "type": "TouchWrapper",
  "items": {
    "type": "Text",
    "id": "myText",
    "speech": "読み上げる値"
  },
  "onPress": [
    {
      "type": "SpeakItem",
      "componentId": "myText"
    },
    {
      "type": "Scroll",
      "componentId": "myScrollRegion",
      "distance": 2
    },
    {
      "type": "SendEvent",
      "arguments": [
        "ボタンが押されたので読み上げました"
      ]
    }
  ]
}

上の例では、Scrollコマンドを実行するコンポーネントがスクロールイベントを生成し、コンポーネントのonScrollイベントハンドラーで定義されたコマンドが呼び出される可能性があります。これらのコマンドは高速モードで実行されるため、現在実行中のコマンドシーケンスに影響しません。

ユーザーがTouchWrapperを複数回タップすると、コマンドシーケンスは終了し、タップごとに再開します。onPressハンドラーを設定してTouchWrapperを無効にすることもできますが、これは問題の解決にはなりません。ユーザーが画面をタップすると、MAINシーケンサー上のコマンドは必ず停止します。

代わりに、コマンドに別のシーケンサーを割り当てることで、コマンドのシーケンス実行が完了することを保証できます。次の例では、TouchWrapperを無効にして、シーケンスが終了するまでユーザーがシーケンスを再開できないようにするとともに、別のシーケンサーを使用して、画面をタッチしてもコマンドがキャンセルされないようにします。

{
  "type": "TouchWrapper",
  "items": {
    "type": "Text",
    "id": "myText",
    "speech": "読み上げる値"
  },
  "onPress": [
    {
      "type": "Sequential",
      "sequencer": "MySequencer",
      "commands": [
        {
          "type": "SetValue",
          "property": "disabled",
          "value": true
        },
        {
          "type": "SpeakItem",
          "componentId": "myText"
        },
        {
          "type": "Scroll",
          "componentId": "myScrollRegion",
          "distance": 2
        },
        {
          "type": "SendEvent",
          "arguments": [
            "ボタンが押されたので読み上げました"
          ]
        }
      ],
      "finally": {
        "type": "SetValue",
        "property": "disabled",
        "value": false
      }
    }
  ]
}

カスタムの「MySequencer」はMAINシーケンサーではないため、画面をもう一度タップしてもコマンドはキャンセルされません。この例では、最後のSetValueコマンドがfinallyブロックで実行されることに注意してください。何らかの理由でSequentialが停止しても、finallyで指定されたコマンドは実行されるため、TouchWrapperが再び有効になります。

上の例は、すべてのコマンドがSequentialのサブコマンドであるため、一連のコマンドとして機能します。シーケンサールールでは、サブコマンドは独自のシーケンサーを指定しない限り、親と同じシーケンサー上で実行されます。

一方、onPressに割り当てられた以下のコマンド配列は機能しません。ユーザーがこのTouchWrapperをタップしても、何も起こりません。

{
  "type": "TouchWrapper",
  "items": {
    "type": "Text",
    "id": "myText",
    "speech": "読み上げる値"
  },
  "onPress": [
    {
      "type": "SetValue",
      "property": "disabled",
      "value": true,
      "sequencer": "BadIdea"
    },
    {
      "type": "SpeakItem",
      "componentId": "myText",
      "sequencer": "BadIdea"
    },
    {
      "type": "Scroll",
      "componentId": "myScrollRegion",
      "distance": 2,
      "sequencer": "BadIdea"
    },
    {
      "type": "SendEvent",
      "arguments": [
        "ボタンが押されたので読み上げました"
      ],
      "sequencer": "BadIdea"
    },
    {
      "type": "SetValue",
      "property": "disabled",
      "value": false,
      "sequencer": "BadIdea"
    }
  ]
}

onPressコマンドはデフォルトでMAINシーケンサーで実行されるため、この例は失敗します。別のシーケンサーを指定すると、ハンドラーはコマンドを別のシーケンサーに引き渡して、配列内の「次の」コマンドを実行します。シーケンサーが新しいコマンドを受け取ると、既存のコマンドはすべて停止し、新しいコマンドで置き換えられます。

失敗する例のイベントシーケンスは、次のようになります。

  1. onPressハンドラーは、SetValueBadIdeaシーケンサーに引き渡します。
  2. onPressハンドラーはSpeakItemに進み、すぐにSpeakItemBadIdeaに引き渡します。
  3. BadIdeaシーケンサーは、SetValueをキャンセルしてSpeakItemを開始します。
  4. onPressハンドラーは、SendEventBadIdeaに引き渡します。
  5. BadIdeaシーケンサーは、SpeakItemをキャンセルしてSendEventを開始します。
  6. onPressハンドラーは、SetValueBadIdeaに引き渡します。
  7. BadIdeaシーケンサーは、SendEventをキャンセルしてSetValueを開始します。
  8. SetValueコマンドは正常に実行されますが、TouchWrapperは既に有効になっているため効果はありません。

シーケンサーを使って、コマンドを実行するかしないかを切り替えることもできます。次の例では、最初のTouchWrapperAnimateItemコマンドを実行して、ボールの動きをアニメーション化します。2つ目のTouchWrapperは、AnimateItemコマンドをキャンセルする新しいコマンドを同じシーケンサーに送信して、アニメーションを強制的に停止させます。


2つ目のTouchWrapperonPressハンドラーは、BallSequencer上でIdleコマンドを実行してAnimateItemを停止します。Idleコマンドは何もしませんが、シーケンサー上で現在実行中のコマンドをすべて停止します。ほかのコマンドをBallSequencerに送信した場合も同じ結果になります。

高速モード

イベントハンドラーの中には、1秒間に何回もトリガーできるものもあります。たとえば、ScrollViewonScrollハンドラーは、スクロール位置が移動するたびに実行されます。同時に、実行に時間がかかるコマンドもあります。たとえば、画面上のリストのスクロールやテキストの読み上げは実行に時間がかかります。

頻繁に起動されるイベントハンドラーから時間のかかるコマンドを実行することによる問題を回避するため、こうしたハンドラーはコマンドを「高速モード」で実行します。

フレームレートで実行されるイベントハンドラーからトリガーされるコマンドのセットはすべて、「高速」モードを使用します。それ以外のコマンドのシーケンスは、すべて通常モードで実行されます。高速モードでは、ハンドラーはコマンドのすべてのdelay設定を無視し、実行に時間がかかるコマンドをスキップします。イベントハンドラーは次のように動作します。

イベントハンドラー 動作
アクション可能なコンポーネントのhandleKeyDown 通常モード
アクション可能なコンポーネントのhandleKeyUp 通常モード
アクション可能なコンポーネントのonBlur 高速モード
アクション可能なコンポーネントのonFocus 高速モード
コンポーネントのhandleTick 高速モード
コンポーネントのonCursorEnter 高速モード
コンポーネントのonCursorExit 高速モード
コンポーネントのonMount 通常モード
コンポーネントのonSpeechMark 高速モード
ドキュメントのhandleTick 高速モード
ドキュメントのonConfigChange 高速モード
ドキュメントのonDisplayStateChange 高速モード
ドキュメントのonMount 通常モード
PagerのonPageChanged 通常モード/高速モード
ScrollViewのonScroll 高速モード
SequenceのonScroll 高速モード
タッチ可能なコンポーネントのonDown 高速モード
タッチ可能なコンポーネントのonMove 高速モード
タッチ可能なコンポーネントのonPress 通常モード
タッチ可能なコンポーネントのonUp 高速モード
VideoのonEnd 通常モード/高速モード
VideoのonPause 通常モード/高速モード
VideoのonPlay 通常モード/高速モード
VideoのonTimeUpdate 高速モード
VideoのonTrackUpdate 通常モード/高速モード

ユーザーの操作(TouchWrapperのタップなど)によって発生する1回限りのイベントは、常に通常モードで実行されます。スクロールなどのイベントは、高速モードで実行されます。いずれのモードでも実行できるイベントは、通常のアクション(ビデオトラックの終了など)、または最終的に高速モードでトリガーされたコマンド(スクロールイベントによるビデオの一時停止など)によってトリガーされます。外部コマンドおよびextensionコマンドは通常モードで実行されます。

各コマンドの説明には、高速モードでの動作が記載されています。次の表は、高速モードの動作をまとめたものです。

コマンド 高速モードの動作
AnimateItem 終了状態にジャンプします。
AutoPage 無視されます。
ClearFocus 実行されます。
ControlMedia command="play"の場合は無視され、それ以外の場合は実行されます。
Finish 実行されます。
Idle 無視されます。
InsertItem 実行されます。
Log 実行されます。
OpenUrl 無視されます。
Parallel 実行されます。
PlayMedia 無視されます。
Reinflate 実行されます。
RemoveItem 実行されます。
Scroll 無視されます。
ScrollToComponent 無視されます。
ScrollToIndex 無視されます。
Select 実行されます。
SendEvent 無視されます。
Sequential 実行されます。
SetFocus 実行されます。
SetPage 無視されます。
SetValue 実行されます。
SpeakItem 無視されます。
SpeakList 無視されます。

通常は高速モードで実行されるコマンドのsequencerプロパティに値が含まれている場合、そのコマンドは、指定されたシーケンサーで通常モードで実行されます。この機能を使って、高速モードのイベントハンドラーから通常モードのコマンドを実行します。

以下の例は、スクロール表示の位置が移動するたびにその位置をチェックし、スクロール位置が最上部に来たらイベントを送信します。イベントを送信した時刻を記録することでレート制限を適用し、少なくとも1秒経過してから次のイベントが発生するようにします。sequencerプロパティがない場合、SendEventは実行されません。

{
  "type": "ScrollView",
  "bind": [
    {
      "name": "LastEventSentTime",
      "value": 0
    }
  ],
  "onScroll": [
    {
      "type": "Sequential",
      "when": "${event.source.value == 0 && utcTime > LastEventSentTime + 1000}",
      "commands": [
        {
          "type": "SetValue",
          "property": "LastEventSentTime",
          "value": "${utcTime}"
        },
        {
          "type": "SendEvent",
          "arguments": [
            "スクロールが最上部に到達しました"
          ],
          "sequencer": "ScrollSender"
        }
      ]
    }
  ]
}

コマンドツリー

コマンドツリーとは、実行される一連のコマンドのことです。コマンドツリーが発生するのは、コマンドが相互にネストできる場合や、あるコマンドによって新しいイベントハンドラーが呼び出される場合です。次のようなプリミティブコマンドに、ネストされたコマンドが含まれます。

  • OpenURL
  • Parallel
  • Select
  • Sequential

ユーザー定義のコマンドもコマンドのネストをサポートします。

プリミティブコマンドは、イベントハンドラーを呼び出すこともあります。たとえば、ScrollScrollToComponentSpeakListの各コマンドは、いずれもonScrollイベントハンドラーを呼び出す可能性があります。

コマンドツリーは最後まで実行することも、途中で停止することもできます。ソースがコマンドツリーの実行を開始します。その後、途中で停止されない限り、コマンドツリーは最後まで実行されます。コマンドツリーが停止されると、APLはツリーのすべてのコマンドの実行を即座に停止します。

次の例は、ScrollSpeakItemPlayVideoの各コマンドを含むExecuteCommandsディレクティブのコマンドツリーを示しています。

ExecuteCommand
  + Scroll (distance=-10000)           // 上方向にスクロールする
    + onScroll                         // 一番上までスクロールするたびに呼び出される
      + SetValue (name="opacity", value=event.source.value * 10) // 不透明度を変更する
  + SpeakItem (id)                     // 項目をスクロールして表示させ、カラオケ状態にする
    + onScroll                         // スクロールするたびに呼び出される
      + SetValue (name="opacity"....)
  + PlayVideo (synchronously)
    + onStart                          // 1回呼び出される
    + onTrackUpdate                    // 新しいトラックが表示されるたびに呼び出される
      + SetValue (name="progress"...)  // 進捗バーの表示を更新する
    + onStop                           // 1回呼び出される

この一連のコマンドは、画面の最上部までスクロールし、項目の1つを読み上げた後、ビデオを再生します。再生中にユーザーが画面をタップすると、実行中の会話、スクロール、ビデオの再生が停止します。

一連のコマンド内の個々のコマンドが、別のsequencerを指定することもあります。そのコマンドに到達すると、適切なシーケンサーに引き渡され、すぐにシーケンス内の次のコマンドが実行されます。sequencerプロパティが明示的に指定されていないコマンドは、現在のシーケンサー上で実行されます。コマンドが次のシーケンサーで実行されるのは、コマンドの遅延が処理された後に限られます。たとえば、メインのシーケンサー上で実行される次の一連のコマンドを考えてみましょう。

+ Sequential
  + AnimateItem A (delay=100, duration=1000)
  + AnimateItem B (delay=200, sequencer="other", duration=2000)
  + Parallel (delay=200)
    + AnimateItem C (duration=1000)
    + AnimateItem D (sequencer="other", duration=2000)
  + AnimateItem E (delay=100, duration=1000)

次の表は、このコマンドツリーで発生するアクションのタイムラインをまとめたものです。

時間 アクション

0

Sequentialコマンドが開始します

100

AnimateItem Aが開始します

1100

AnimateItem Aが終了します

1300

otherシーケンサー上でAnimateItem Bが開始します

1500

Parallelコマンドが開始します

AnimateItem Cが開始します

otherシーケンサー上でAnimateItem Dが開始します。AnimateItem Bが停止します。

2500

AnimateItem Cが終了します

Parallelコマンドが終了します

2600

AnimateItem Eが開始します

3500

otherシーケンサー上でAnimateItem Dが終了します

3600

AnimateItem Eが終了します

Sequentialが終了します

コマンドツリーが停止すると、APLは次のように想定して、デバイスを一貫した状態にします。

  • スクロールを停止します。
  • ページめくりをキャンセルして、元のページか次のページのどちらか近い方に移動します。
  • 会話を直ちに中止します。
  • シーンの変更やレイアウトの構造的な変更の場合は、最終的な位置に「ジャンプ」します。

セレクター

いくつかのコマンドには、コマンドのターゲットコンポーネントを指定するcomponentIdプロパティがあります。コマンドのターゲットとは、コマンドの実行対象となるコンポーネントです。次のコマンドには、それぞれcomponentIdプロパティがあります。

componentIdプロパティは、セレクターの文法に従って解析される文字列セレクターです。セレクターの文法は次のとおりです。

componentId  ::=  element? modifier*
element      ::=  uid | id | ":source" | ":root"
modifier     ::=  modifierType "(" arg? ")"
modifierType ::=  ":parent" | ":child" | ":find" | ":next" | ":previous"
arg          ::=  number | "id=" id | "type=" type
uid          ::=  ":" [0-9]*
id           ::=  [_a-zA-Z][_a-zA-Z0-9]*
number       ::=  "0" | "-"? [1-9][0-9]*
type         ::=  STRING

セレクター文字列では、elementと各modifierの間の空白は無視されます。modifierには空白を含めないでください。

有効なcomponentId式の例をいくつか以下に示します。

FOO                         # id=FOOのコンポーネント
:1003                       # 一意のIDが「:1003」のコンポーネント
:source                     # コマンドを発行したコンポーネント
:root                       # コンポーネント階層の最上位
:source:child(2)            # ソースコンポーネントの3番目の子
:child(2)                   # ソースコンポーネントの3番目の子(:sourceは暗黙的)
FOO:child(-1)               # FOOコンポーネントの最後の子
FOO:parent(1)               # FOOの親
FOO:child(id=BAR)           # FOOの直接の子のうち、id=BARに最初に一致するもの
FOO:find(id=BAR)            # FOOの子孫のうち、id=BARに最初に一致するもの
FOO:child(type=Text)        # FOOの直接の子のうち、type=Textに最初に一致するもの
FOO:parent():child(id=BAR)  # FOOの兄弟のうち、id=BARに最初に一致するもの
FOO:next(id=BAR)            # FOOの後続の兄弟のうち、id=BARに最初に一致するもの
FOO:parent(2)               # FOOの親の親
FOO:next(1)                 # FOOの直後の兄弟
FOO:previous(2)             # FOOの2つ前の兄弟

componentId値は、コマンドを受け取るコンポーネントを定義します。componentIdがコンポーネントに一致しない場合、コマンドは実行されません。componentIdは、省略可能なelementと、その後に続く0個以上のmodifier式で構成されます。

APLはセレクター式を順番に評価し、elementから始めて各modifierを処理します。式に一致する有効なコンポーネントがまったくない場合、式はnullのcomponentIdを返し、そのセレクターを使用するコマンドは実行されません。たとえば、:root:parent():find(id=FOO)というセレクターは、階層内にid=FOOのコンポーネントがあったとしても、コンポーネントを返すことはありません。これは、ルートコンポーネントには親が定義されていないためです。

開始要素

elementは、uidid:sourceという文字列、:rootという文字列のいずれかです。コンポーネントの場合、elementのデフォルトは:sourceです。このため、コンポーネントのスコープに定義されているイベントハンドラーではelementを省略できます。ドキュメントレベルのイベントハンドラーでは、elementは省略しないでください。

開始要素:uid

uid要素は、一致する内部uidを持つコンポーネントを返します。APLランタイムは、コンポーネントの作成時に、各コンポーネントに固有の文字列IDを割り当てます。この内部IDはドキュメントのスコープ内で一意であり、割り当てられたどのidとも競合しません。イベントハンドラーでuidにアクセスして、後で使用するためにバインド変数に保存できます。コンポーネントに割り当てられるuid値については、特定の値を想定しないでください。ランタイムが異なると、割り当てられる値も異なる可能性があります。

uidはAPLが割り当てるため、コマンドのターゲットを直接指定するために使用されることはほとんどありません。イベントハンドラーでコンポーネントのuid値を保存しておくと、後でコンポーネントを変更するときに保存した値を使用できます。以下の例では、TouchWrapperコンポーネントのリストを表示するSequenceを定義します。これらのコンポーネントのいずれかをタップすると、そのコンポーネントのuidLAST_PRESSEDというバインド変数に格納されます。ユーザーがリストをスクロールすると、Sequenceのイベントハンドラーは、LAST_PRESSEDに格納されているuidを使用してそのコンポーネントをターゲットにし、リストに表示されているテキストを更新します。


開始要素:id

id要素は、階層内でComponent idプロパティが一致する最初のコンポーネントを返します。id値は開発者が設定するため、APLはその一意性を保証できません。検索順序は深さ優先ですが、非表示のコンポーネントがどれだけインフレートされているかによって変わります。idの値が一意であることがわかっている場合は、id要素を使用してコマンドのターゲットを指定できます。

以下の例では、ContainerTouchWrapperコンポーネントのリストを表示します。各コンポーネントには、data配列で指定された色から計算されたidが設定されています。たとえば、最初のコンポーネントのidText_Redです。ユーザーがコンポーネントをタップすると、SetValueコマンドによってTouchWrapperのテキストと色が変更され、隠されていた色が表示されます。

この例が機能するのは、リスト内のそれぞれの色が一意であり、各コンポーネントのidも一意になるためです。いずれかの色が繰り返されるようにdata配列を更新すると、SetValueコマンドはそのidの最初のインスタンスだけを更新し、後のインスタンスは更新しません。


開始要素::source

:source要素は、コマンドを発行したコンポーネントを選択します。:source要素はオプションです。コンポーネントのコンテキストで定義されたイベントハンドラーでは、コンポーネントのターゲットはデフォルトでコンポーネント自体になります。Extensionから発行されるコマンドとドキュメントから発行されるコマンドには、ソースコンポーネントがないことに注意してください。したがって、これらのコマンドでは、ターゲットコンポーネントか:rootのいずれかを指定する必要があります。:sourceは、目的のターゲットを明示する場合に使用します。

開始要素::root

:root要素は、インフレートされたコンポーネント階層の最上位のコンポーネントを選択します。ドキュメントにアタッチされたバインド値を変更するには、:rootを使用します。

以下は、mainTemplateレベルでbindが定義されているAPLドキュメントの例です。onMountイベントハンドラーは、ドキュメントのインフレート時にこの最上位のbind変数を更新します。ドキュメントを再インフレートするには、ページを更新します。


このコマンドは、有効なcomponentIdがないと失敗することに注意してください。これは、ドキュメントレベルのコマンドハンドラーはすべてターゲットコンポーネントを指定する必要があるためです。

モディファイアー

modifier式は、elementで識別されるコンポーネントと関係のあるターゲットを選択します。modifierにより、親、兄弟、子を選択したり、コンポーネントの子階層を検索したりできます。モディファイアーを指定することで、一致するコンポーネントを検索する方法が定義されます。

modifierは、一致条件を指定するオプションのargを受け取ります。この引数には、整数またはキーと値のペアを指定できます。有効なキーはidtypeです。

モディファイアー::parent

:parent()モディファイアーは、コンポーネントの親を選択します。:parent()モディファイアーには、より上位の祖先を選択する数値引数を渡すことができます。この引数は正の整数である必要があります。引数を指定しない場合の結果は:parent(1)と同じです。次に例を示します。

FOO:parent(1) == FOO:parent()            # FOOの親
FOO:parent(2) == FOO:parent():parent()   # FOOの親の親

idtypeのパターンは、一致する最初の祖先にマッチします。

たとえば、次のようなドキュメント階層があるとします。

TouchWrapper  (id=MyButton)
  Frame       (id=OuterFrame)
    Frame     (id=InnerFrame)
      Text    (id=FOO)

この階層に基づいた式の例を以下に示します。

FOO:parent(1)              # InnerFrame
FOO:parent(2)              # OuterFrame
FOO:parent(id=MyButton)    # TouchWrapper
FOO:parent(type=Frame)     # InnerFrame
FOO:parent(id=OuterFrame)  # OuterFrame

モディファイアー::child

:child()モディファイアーは、コンポーネントの直接の子のうち、式に一致する最初の子を選択します。

数値引数を指定すると、コンポーネントのN番目の子が返されます。N=0は最初の子、N=1は2番目の子のようになります。負の数値を指定すると、子リストの末尾から選択されます。N=-1は最後の子、N=-2は最後から2番目の子のようになります。引数が空の場合は:child(0)と同じです。式の例を以下に示します。

FOO:child()             # FOOの最初の子
FOO:child(0)            # FOOの最初の子
FOO:child(2)            # FOOの3番目の子
FOO:child(-1)           # FOOの最後の子

名前付き引数(idまたはtype)は、一致する最初の子を選択します。たとえば、次のようなドキュメント階層があるとします。ここではid値が一意ではありません。

Sequence    (id=FOO)
  Container
    Text    (id=TEXT)
    Image   (id=IMAGE)
  Container
    Text    (id=TEXT)
    Image   (id=IMAGE)

この階層に基づいた式の例を以下に示します。

FOO:child(0):child(0)             # 最初のContainer内のTextコンポーネント
FOO:child(id=TEXT)                # Null(間接的ではない直接の子だけが対象)
FOO:child(0):child(id=TEXT)       # 最初のContainer内のTextコンポーネント
FOO:child(1):child(type=Image)    # 2番目のContainer内のImageコンポーネント

モディファイアー::find

:find()モディファイアーは、コンポーネントの子孫のうち、式に一致する最初の子孫を選択します。

数値引数を指定すると、深さ優先検索によってN番目のコンポーネントが返されます。正の数値である必要があります。引数に負の数値または0を指定すると、最初の子が返されます。名前付き引数(idまたはtype)は、一致する最初の子を選択します。たとえば、次のようなドキュメント階層があるとします。

Sequence    (id=FOO)
  Container
    Text    (id=TEXT)
    Image   (id=IMAGE)
  Container
    Text    (id=TEXT)
    Image   (id=IMAGE)

この階層に基づいた式の例を以下に示します。

FOO:find(3)             # 最初のContainer内のImageコンポーネント
FOO:find(5)             # 2番目のContainer内のTextコンポーネント
FOO:find(id=TEXT)       # 最初のContainer内のTextコンポーネント
FOO:find(type=Image)    # 最初のContainer内のImageコンポーネント

モディファイアー::next

:next()モディファイアーは、コンポーネントの兄弟を現在のコンポーネントから前方に検索し、式に一致する最初の兄弟を選択します。数値引数を指定すると、コンポーネントのN番目の兄弟が返されます。N=1は最初の兄弟、N=2は2番目の兄弟のようになります。引数が空の場合は:next(1)と同じです。名前付き引数(idまたはtype)は、一致する最初の兄弟を選択します。

たとえば、次のような兄弟コンポーネントのリストがあるとします。

TouchWrapper (id=MyButton)
Container    (id=FOO)
Frame
Image        (id=ImageA)
Video        (id=VideoA)
Video        (id=VideoB)

このリストに基づいた式の例を以下に示します。

FOO:next()              # Frame
FOO:next(2)             # Image
FOO:next(9)             # Null(兄弟リストの末尾を超過)
FOO:next(id=MyButton)   # Null(検索方向が逆)
FOO:next(type=Video)    # VideoA
FOO:next(id=VideoB)     # VideoB

モディファイアー::previous

:previous()モディファイアーは、コンポーネントの兄弟を現在のコンポーネントから後方に検索し、式に一致する最初の兄弟を選択します。数値引数を指定すると、コンポーネントのN番目の兄弟が返されます。N=1は最初の兄弟、N=2は2番目の兄弟のようになります。引数が空の場合は:previous(1)と同じです。名前付き引数(idまたはtype)は、一致する最初の兄弟を選択します。

たとえば、次のような兄弟コンポーネントのリストがあるとします。

TouchWrapper (id=MyButton)
Frame
Image        (id=ImageA)
Video        (id=VideoA)
Container    (id=FOO)
Video        (id=VideoB)

このリストに基づいた式の例を以下に示します。

FOO:previous()              # VideoA
FOO:previous(2)             # Image
FOO:previous(9)             # Null(兄弟リストの末尾を超過)
FOO:previous(id=MyButton)   # TouchWrapper
FOO:previous(type=Frame)    # Frame

型による一致

コンポーネントがレイアウトの一部である場合、そのコンポーネントは複数の型を持つことがあります。このような状況では、レイアウト名と基盤のコンポーネント型の両方に一致する可能性があります。

以下は、MyTextという名前のレイアウトを含むドキュメントの例です。このレイアウトは、Textコンポーネントを1つ表示します。mainTemplateは、MyTextレイアウトのインスタンスを2つ表示します。

onMountハンドラーは2つのコマンドを実行します。1つは:root:find(type=MyText)をターゲットとし、もう1つは:root:find(type=Text)をターゲットとします。これらのコマンドが実行されると、「Doctor Jane Doe」という値を持つ同じTextコンポーネントが更新されます。 コンポーネントの色がまず青に変わり、次に紫に変わります。ドキュメントを再読み込みして変化を確認するには、ページを更新するか、2つのTextコンポーネントの下にあるTouchWrapperをクリックしてReinflateコマンドを実行します。



このページは役に立ちましたか?

最終更新日: 2025 年 10 月 09 日