APLコマンド
Alexa Presentation Language(APL)のコマンドとは、画面上のコンテンツの視覚表現やオーディオ表現を変更するメッセージです。開発者は、発話への応答として、Alexa.Presentation.APL.ExecuteCommandsディレクティブを使用してコマンドをデバイスに送信します。ボタン押下への応答など、APLドキュメントのイベントハンドラーを使って、コマンドを直接トリガーします。
個別のコマンドについては、APL標準コマンドを参照してください。
コマンドと画面アクション
コマンドがサポートするシーンでのアクションには、次のタイプがあります。
- シーン内をナビゲートする
    - ScrollViewまたは- Sequenceをスクロールする
- スクロールしてコンポーネントを表示する
- Pagerで表示されるページを変更する
 
- 既存シーン内でコンポーネントを変更する
- 入力コントロールを更新して新しい状態を反映する
- 既存コンポーネントの表示を変更する
- 既存のシーン内でビデオクリップを再生/一時停止する
- 音声
    - 単一コンポーネントのオーディオコンテンツを読む
- 2つ以上のコンポーネントのオーディオコンテンツを読む
 
- APL Extensionにコマンドを送信する
コマンド概要
以下の各セクションでは、イベントハンドラーまたは外部で生成されたコマンドによって開始されるコマンドを実行するアルゴリズムについて説明します。
コマンド配列は順に実行されることを想定しています。これらのコマンドは、通常モードまたは高速モードで実行されます。
通常モードの評価
通常モードで実行するコマンドには、名前付きのシーケンサーが必要です。
デフォルトでは、コマンドはMAINシーケンサーで実行されます。ただし、以下に該当する場合は除きます。
- コマンドのsequencerプロパティに値が含まれている。
- コマンドが、別のsequencerを持つコマンドのサブコマンドである。
配列の各コマンドについて、Alexaにより以下の手順が実行されます。
- 
    コマンドの whenプロパティを評価します。whenがfalseに評価された場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。
- 
    コマンドの delayプロパティの値をチェックします。delayが0より大きい場合は、指定された時間(ミリ秒単位)だけ一時停止します。
- 
    コマンドの sequencerプロパティの値をチェックします。sequencerの値が現在のシーケンサーと異なる場合は、このコマンドを指定されたシーケンサーに渡し、配列内の次のコマンドに進みます。
- 
    コマンドの typeプロパティを評価します。認識できない値がtypeに含まれている場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。有効な type値には、次のようなものがあります。- SendEventなどのビルトインコマンド。すべての必須コマンドプロパティに値がある場合、コマンドを実行します。一部のコマンドはただちに返されます。このコマンドの実行が終了するまで、ほかのコマンドはシーケンサーを一時停止します。
- ユーザー定義コマンド。ユーザー定義コマンドがインフレートしてコマンドの配列になります。通常モードの評価規則に従ってこれらのコマンドを実行し、すべてのコマンドが完了したら続行します。
- Extensionコマンド。コマンドを実行します。一部のextensionコマンドはすぐに返されます。このコマンドの実行が終了するまで、ほかのコマンドはシーケンサーを一時停止します。
 
高速モードの評価
高速モードで実行されるコマンドには、名前付きのsequencerはありません。配列の各コマンドについて、以下が順に実行されます。
- 
    コマンドの whenプロパティを評価します。whenがfalseに評価された場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。
- 
    コマンドの sequencerプロパティの値をチェックします。sequencerに値が含まれている場合は、このコマンドを適切なシーケンサーに渡し、配列内の次のコマンドに進みます。渡されたコマンドは、高速モードではなく通常モードで実行されます。
- 
    コマンドの typeプロパティを評価します。認識できない値がtypeに含まれている場合は、そのコマンドをスキップし、配列内の次のコマンドに進みます。有効な type値には、次のようなものがあります。- SetValueなど、高速モードをサポートするビルトインコマンド。すべての必須コマンドプロパティに値が含まれる場合、コマンドを実行します。高速モードをサポートするコマンドについては、高速モードのコマンド一覧を参照してください。
- SendEventなど、高速モードをサポートしないビルトインコマンド。コマンドをスキップします。
- ユーザー定義コマンド。ユーザー定義コマンドがインフレートしてコマンドの配列になります。高速モードの評価規則に従ってこれらのコマンドを実行し、すべてのコマンドが完了したら続行します。
- 高速モードをサポートするextensionコマンド。コマンドを実行します。
- 高速モードをサポートしないextensionコマンド。コマンドをスキップします。
 
コマンドの評価
コマンドの個々のプロパティはデータバインディングをサポートします。コマンドのデータバインディングコンテキストには、コマンドが定義されているコンテキストが含まれ、さらにeventプロパティが追加されます。このプロパティには、コマンドがトリガーされた状況に関する情報と、コマンドのターゲットとなったコンポーネントが含まれます。
イベントの内容
コマンドは、そのソースのデータバインディングコンテキストで評価されます。
- ドキュメントのデータバインディングコンテキスト - viewport、environment、ドキュメントのペイロードが含まれます。名前付きリソースとドキュメントのデータバインディングにアクセスできます。
- コンポーネントのデータバインディングコンテキスト - コンポーネント固有のデータバインディングコンテキストです。ドキュメントのデータバインディングコンテキストに加えて、コンポーネント内のすべてのデータバインディングと、コンポーネントのすべての祖先にアクセスできます。
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.sourceとevent.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プロパティの一覧です。特定のコンポーネントのプロパティに関するドキュメント全文については、各コンポーネントのドキュメントを参照してください。
| プロパティ | 型 | 説明 | レポート元 | 
|---|---|---|---|
| 
 | マップ | データバインディングコンテキスト | |
| 
 | ブール値 | チェックの状態 | |
| 
 | 色 | 現在の色 | |
| 
 | 整数 | 現在の再生位置 | |
| 
 | ブール値 | 無効にされた状態 | |
| 
 | 整数 | 現在のビデオの長さ(ミリ秒) | |
| 
 | ブール値 | ビデオが終了している場合はtrue | |
| 
 | ブール値 | フォーカス状態 | |
| 
 | 数値 | 高さ(dp) | |
| 
 | 文字列 | コンポーネントID | |
| 
 | 文字列 | 現在の | |
| 
 | ブール値 | ビデオの現在のミュート状態 | |
| 
 | 数値 | ローカル不透明度(累積値ではない) | |
| 
 | 整数 | 現在表示されているページ | |
| 
 | ブール値 | ビデオが一時停止している場合はtrue | |
| 
 | 数値 | スクロール距離の割合 | |
| 
 | ブール値 | 押された状態 | |
| 
 | 文字列 | 表示されるテキスト | |
| 
 | 整数 | トラック数 | |
| 
 | 整数 | 現在のトラックのインデックス | |
| 
 | 文字列 | コンポーネントタイプ("Frame"など) | |
| 
 | 文字列 | ランタイムで生成された固有ID | |
| 
 | 文字列 | ソースURL | |
| 
 | 数値 | 幅(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
eventのevent.sourceプロパティには、イベントをトリガーした状況に関するメタ情報が含まれます。event.sourceはAPLランタイムによって生成され、ドキュメントでこの情報を使用できます。event.sourceプロパティには、すべての標準プロパティと以下のプロパティが含まれます。
| プロパティ | 型 | 説明 | 
|---|---|---|
| handler | 文字列 | このメッセージを開始したイベントハンドラーの名前です。たとえば、 PressやCheckedです。 | 
| value | 任意 | このメッセージを開始したコンポーネントの値です。 | 
event.source.valueプロパティは、コンポーネントによって異なります。たとえば、TouchWrapperの場合、event.source.valueにはコンポーネントのチェック状態が含まれます。ScrollViewの場合、このプロパティにはスクロール位置が含まれます。提供される特定のevent.source.valueプロパティについては、個々のコンポーネントの定義を参照してください。
event.source.valueプロパティは廃止されているため、使用しないでください。代わりに、各コンポーネントに直接ドキュメント化されたプロパティを使用してください。たとえば、TouchWrapperのチェック状態については、event.source.valueの代わりにevent.source.checkedを使用します。APLの以前のバージョンとの下位互換性のために、event.sourceプロパティにはsourceサブプロパティも含まれています。これはtypeプロパティと同じです。つまり、${event.source.source == event.source.type}となります。event.source.sourceプロパティは廃止されました。このプロパティは使用しないでください。
event.target
event.targetプロパティは、イベントを受け取るコンポーネントについての状態情報を提供します。targetの値は、コンポーネントによって異なります。想定される特定のtargetプロパティについては、個々のコンポーネントの定義を参照してください。
APLの以前のバージョンとの下位互換性のために、イベントのtargetプロパティはsourceサブプロパティもレポートします。これは、Image、VectorGraphic、Videoの各コンポーネントの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プロパティを設定します。
- サブコマンド – 別のコマンド内に含まれるコマンドです。Sequential、Parallel、OpenURL、Selectなど、サブコマンドを含めることができるコマンドは複数あります。サブコマンドの階層は、「コマンドツリー」と呼ばれることもあります。
コマンドのシーケンス実行ルール
APLランタイムは、コマンドをシーケンス実行する際、次のルールを使用します。
- 通常モードのコマンドはすべて、単一のシーケンサーで実行されます。そのシーケンサーが既にコマンドを実行している場合、ランタイムは実行中のコマンドを停止し、新しいコマンドを開始します。
- 高速モードのコマンドはシーケンサーを使用しません。
- シーケンサーを明示的に指定するコマンドは、いずれも通常モードで実行されます。
- サブコマンドは、サブコマンドが明示的にシーケンサーを指定しない限り、親コマンドと同じシーケンサー上で実行されます。サブコマンドがシーケンサーを指定している場合、ランタイムはそのコマンドをシーケンサーに引き渡して、親コマンド内でそのコマンドを完了とマークします。
- デフォルトのシーケンサーはMAINです。サブコマンド以外の通常モードコマンドでシーケンサーが明示的に指定されていない場合、そのコマンドはMAINシーケンサーで実行されます。通常モードのサブコマンドは、ルール4に従います。
- 画面タッチやキーボード入力など、ユーザーが何らかの物理的なデバイス操作を行うと、MAINsequencerで実行されているコマンドはすべて停止します。このルールは、音声対話には適用されません。コマンドシーケンスの実行中にユーザーがウェイクワードを発して割り込んでも、MAINシーケンサーは続行され、停止しません。
- コマンドの実行が開始されると、既に実行されているコマンドで使用中のリソースがコマンドに必要な場合、ランタイムは実行中のコマンドを停止します。
- 設定変更ハンドラーが実行されると、以前の設定で実行されているコマンドはすべて停止します。
たとえば、ユーザーが画面上のボタンをタッチしたとします。この操作によって、MAINシーケンサー上で次のような一連のコマンドが開始されます。
- 画面上の変化をアニメーション化します。
- 特定の位置までリストを下にスクロールします。
- 項目を読み上げます。
ユーザーは、画面をもう一度タッチすることで、この一連のコマンドをいつでも中断できます。ユーザーが画面をタッチすると、MAINシーケンサーは現在実行中のコマンドを停止します。
デフォルトのシーケンス実行動作をオーバーライドするには、シーケンサーを指定します。この手法を使うと、コマンドを並行して実行できます。シーケンス実行動作を変更するシナリオには、次のような場合が考えられます。
- 通常は高速モードで実行されるイベントハンドラーを使って、通常モードのコマンドを実行したい場合。たとえば、ScrollViewコンポーネントのonScrollイベントハンドラーで、SendEventコマンドをトリガーしたいとします。通常、onScrollハンドラーは、通常モードのコマンドを無視します。SendEventにシーケンサー名を指定すると、onScrollハンドラーは通常モードでコマンドを実行するため、SendEventが実行されます。
- 通常のユーザー対話動作を上書きして、ユーザーが画面をタッチしてもコマンドの実行を続けたい場合。たとえば、子ども向けのゲームで、AnimateItemコマンドを使用して画面上のコンポーネントをアニメーション化しているときに、画面がタッチされてもアニメーションを停止したくない場合があります。
- 複数コンポーネントのアニメーションがトリガーされた場合、新しいコンポーネントを表示して古いコンポーネントを非表示にします。
リソース
各コマンドは、実行の際に1つ以上のシステムリソースを使用できます。シーケンサールールで定義されているように、特定のリソースを使用するコマンドは1回に1つしか実行できません。そのリソースを必要とする新しいコマンドが実行を開始すると、古いコマンドは停止します。
以下は、リソースと、そのリソースを使用するコマンドの一覧です。
- フォアグラウンドオーディオ再生
    - SpeakItem
- SpeakList
- PlayMedia(フォアグラウンドのオーディオトラックを使用する場合)
- ControlMedia(フォアグラウンドのオーディオトラックで- playコマンドを実行する場合)
 
- バックグラウンドオーディオ再生
    - PlayMedia(バックグラウンドのオーディオトラックを使用する場合)
- ControlMedia(バックグラウンドのオーディオトラックで- playコマンドを実行する場合)
 
- スクロール位置(スクロールコンポーネント、つまりScrollViewまたはSequence)
- ページャー位置(Pagerコンポーネント)
- コンポーネントアニメーション(コンポーネントプロパティのアニメーション化)
SpeakItemとSpeakListは、複数のリソースを使用する可能性があります。
通常モード
APLランタイムは、次のシナリオではコマンドを通常モードで実行します。
- ドキュメントが最初に読み込まれるとき(ドキュメントのonMountおよびコンポーネントのonMount)。
- ユーザーがTouchWrapperをタッチまたは選択したとき(onPress)。
- ユーザーがPager上でスワイプし、表示ページを変更したとき(onPageChanged)。
- Videoコンポーネントがビデオの再生を終了するか、トラックを変更したとき(onEnd、onPause、onPlay、onTrackUpdate)。
- 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シーケンサーで実行されるため、この例は失敗します。別のシーケンサーを指定すると、ハンドラーはコマンドを別のシーケンサーに引き渡して、配列内の「次の」コマンドを実行します。シーケンサーが新しいコマンドを受け取ると、既存のコマンドはすべて停止し、新しいコマンドで置き換えられます。
失敗する例のイベントシーケンスは、次のようになります。
- onPressハンドラーは、- SetValueを- BadIdeaシーケンサーに引き渡します。
- onPressハンドラーは- SpeakItemに進み、すぐに- SpeakItemを- BadIdeaに引き渡します。
- BadIdeaシーケンサーは、- SetValueをキャンセルして- SpeakItemを開始します。
- onPressハンドラーは、- SendEventを- BadIdeaに引き渡します。
- BadIdeaシーケンサーは、- SpeakItemをキャンセルして- SendEventを開始します。
- onPressハンドラーは、- SetValueを- BadIdeaに引き渡します。
- BadIdeaシーケンサーは、- SendEventをキャンセルして- SetValueを開始します。
- SetValueコマンドは正常に実行されますが、- TouchWrapperは既に有効になっているため効果はありません。
Sequentialコマンド内に配置し、Sequentialコマンドに新しいシーケンサーを割り当てます。シーケンサーを使って、コマンドを実行するかしないかを切り替えることもできます。次の例では、最初のTouchWrapperがAnimateItemコマンドを実行して、ボールの動きをアニメーション化します。2つ目のTouchWrapperは、AnimateItemコマンドをキャンセルする新しいコマンドを同じシーケンサーに送信して、アニメーションを強制的に停止させます。
2つ目のTouchWrapperのonPressハンドラーは、BallSequencer上でIdleコマンドを実行してAnimateItemを停止します。Idleコマンドは何もしませんが、シーケンサー上で現在実行中のコマンドをすべて停止します。ほかのコマンドをBallSequencerに送信した場合も同じ結果になります。
高速モード
イベントハンドラーの中には、1秒間に何回もトリガーできるものもあります。たとえば、ScrollViewのonScrollハンドラーは、スクロール位置が移動するたびに実行されます。同時に、実行に時間がかかるコマンドもあります。たとえば、画面上のリストのスクロールやテキストの読み上げは実行に時間がかかります。
頻繁に起動されるイベントハンドラーから時間のかかるコマンドを実行することによる問題を回避するため、こうしたハンドラーはコマンドを「高速モード」で実行します。
フレームレートで実行されるイベントハンドラーからトリガーされるコマンドのセットはすべて、「高速」モードを使用します。それ以外のコマンドのシーケンスは、すべて通常モードで実行されます。高速モードでは、ハンドラーはコマンドのすべての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
ユーザー定義のコマンドもコマンドのネストをサポートします。
プリミティブコマンドは、イベントハンドラーを呼び出すこともあります。たとえば、Scroll、ScrollToComponent、SpeakListの各コマンドは、いずれもonScrollイベントハンドラーを呼び出す可能性があります。
コマンドツリーは最後まで実行することも、途中で停止することもできます。ソースがコマンドツリーの実行を開始します。その後、途中で停止されない限り、コマンドツリーは最後まで実行されます。コマンドツリーが停止されると、APLはツリーのすべてのコマンドの実行を即座に停止します。
次の例は、Scroll、SpeakItem、PlayVideoの各コマンドを含む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 | 
 | 
| 1500 | Parallelコマンドが開始します | 
| AnimateItem Cが開始します | |
| 
 | |
| 2500 | AnimateItem Cが終了します | 
| Parallelコマンドが終了します | |
| 2600 | AnimateItem Eが開始します | 
| 3500 | 
 | 
| 3600 | AnimateItem Eが終了します | 
| Sequentialが終了します | 
コマンドツリーが停止すると、APLは次のように想定して、デバイスを一貫した状態にします。
- スクロールを停止します。
- ページめくりをキャンセルして、元のページか次のページのどちらか近い方に移動します。
- 会話を直ちに中止します。
- シーンの変更やレイアウトの構造的な変更の場合は、最終的な位置に「ジャンプ」します。
セレクター
いくつかのコマンドには、コマンドのターゲットコンポーネントを指定するcomponentIdプロパティがあります。コマンドのターゲットとは、コマンドの実行対象となるコンポーネントです。次のコマンドには、それぞれcomponentIdプロパティがあります。
- AnimateItem
- AutoPage
- ControlMedia
- InsertItem
- PlayMedia
- RemoveItem
- Scroll
- ScrollToComponent
- ScrollToIndex
- SendEvent
- SetFocus
- SetPage
- SetValue
- SpeakItem
- SpeakList
- componentIdを使用するすべてのAPL Extension
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は、uid、id、:sourceという文字列、:rootという文字列のいずれかです。コンポーネントの場合、elementのデフォルトは:sourceです。このため、コンポーネントのスコープに定義されているイベントハンドラーではelementを省略できます。ドキュメントレベルのイベントハンドラーでは、elementは省略しないでください。
開始要素:uid
uid要素は、一致する内部uidを持つコンポーネントを返します。APLランタイムは、コンポーネントの作成時に、各コンポーネントに固有の文字列IDを割り当てます。この内部IDはドキュメントのスコープ内で一意であり、割り当てられたどのidとも競合しません。イベントハンドラーでuidにアクセスして、後で使用するためにバインド変数に保存できます。コンポーネントに割り当てられるuid値については、特定の値を想定しないでください。ランタイムが異なると、割り当てられる値も異なる可能性があります。
uidはAPLが割り当てるため、コマンドのターゲットを直接指定するために使用されることはほとんどありません。イベントハンドラーでコンポーネントのuid値を保存しておくと、後でコンポーネントを変更するときに保存した値を使用できます。以下の例では、TouchWrapperコンポーネントのリストを表示するSequenceを定義します。これらのコンポーネントのいずれかをタップすると、そのコンポーネントのuidがLAST_PRESSEDというバインド変数に格納されます。ユーザーがリストをスクロールすると、Sequenceのイベントハンドラーは、LAST_PRESSEDに格納されているuidを使用してそのコンポーネントをターゲットにし、リストに表示されているテキストを更新します。
開始要素:id
id要素は、階層内でComponent idプロパティが一致する最初のコンポーネントを返します。id値は開発者が設定するため、APLはその一意性を保証できません。検索順序は深さ優先ですが、非表示のコンポーネントがどれだけインフレートされているかによって変わります。idの値が一意であることがわかっている場合は、id要素を使用してコマンドのターゲットを指定できます。
以下の例では、ContainerにTouchWrapperコンポーネントのリストを表示します。各コンポーネントには、data配列で指定された色から計算されたidが設定されています。たとえば、最初のコンポーネントのidはText_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を受け取ります。この引数には、整数またはキーと値のペアを指定できます。有効なキーはidとtypeです。
モディファイアー::parent
:parent()モディファイアーは、コンポーネントの親を選択します。:parent()モディファイアーには、より上位の祖先を選択する数値引数を渡すことができます。この引数は正の整数である必要があります。引数を指定しない場合の結果は:parent(1)と同じです。次に例を示します。
FOO:parent(1) == FOO:parent()            # FOOの親
FOO:parent(2) == FOO:parent():parent()   # FOOの親の親
idとtypeのパターンは、一致する最初の祖先にマッチします。
たとえば、次のようなドキュメント階層があるとします。
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 日