[Alexa]APLでローディングアニメーションを実装する

## 前提
本記事は、以下の方を対象としています。

- Alexaスキル開発について、ある程度理解している。
- AlexaスキルでAPLを描写する方法について、ある程度理解している。

※本記事ではAPLテンプレート部のjsonのみを記載しています。AlexaスキルにてAPLを描写(addDirective)する部分については記載していませんのでご注意ください。

## 概要
画面付きAlexaスキルでは、画面タッチ等の画面操作でスキルを進行する場面があると思います。  
このとき、「画面遷移しようとしている感じ」を出す演出として、ローディングアニメーションを表示するという方法があります。  
今回は、APLでローディングアニメーションを実装してみようと思います。
以下のようなものを作ってみようと思います。

- Alexaスキルにて表示する、ボタン付きの画面
- ボタン押下したら、ローディングアニメーションが表示される

こんな感じになります。
![loading.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354808/2e7f54e9-8815-7b1a-bf09-1d941c2486dd.gif)

なお、このドットのアニメーションは、APL1.5から利用可能になった、AlexaProgressDotsを使用しています。

## APLテンプレート部
APLテンプレート部のjsonのソースは以下のようになります。
なお、jsonを確認いただけば分かる通り、以下の例ではデータソース部の参照は行っていませんので、データソース部のjsonは適当で大丈夫です。

``` aplTemplate.json
{
    "type": "APL",
    "version": "1.5",
    "settings": {},
    "theme": "dark",
    "import": [
        {
            "name": "alexa-layouts",
            "version": "1.2.0"
        }
    ],
    "resources": [],
    "styles": {},
    "onMount": [],
    "graphics": {},
    "commands": {},
    "layouts": {},
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "type": "Container",
                "id":"base",
                "height": "100%",
                "width": "100%",
                "item": [
                    {
                        "type": "Container",
                        "height": "100%",
                        "width": "100%",
                        "items": [
                            {
                                "type": "AlexaBackground",
                                "backgroundColor": "#7694c3"
                            },
                            {
                                "type": "Container",
                                "width": "100%",
                                "height": "70%",
                                "items": [
                                    {
                                        "type": "Frame",
                                        "width": "80%",
                                        "height": "80%",
                                        "backgroundColor": "#4657a8",
                                        "borderRadius": "20dp",
                                        "items": [
                                            {
                                                "type": "Container",
                                                "width": "100%",
                                                "height": "100%",
                                                "alignItems": "center",
                                                "justifyContent": "center",
                                                "items": [
                                                    {
                                                        "type": "Text",
                                                        "text": "タイトルっぽい文字列とか"
                                                    },
                                                    {
                                                        "type": "Text",
                                                        "text": "サブタイトル的なやつ",
                                                        "fontSize": "30dp"
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ],
                                "justifyContent": "center",
                                "alignItems": "center"
                            },
                            {
                                "type": "Container",
                                "width": "100%",
                                "height": "30%",
                                "items": [
                                    {
                                        "type": "Container",
                                        "items": [
                                            {
                                                "type": "AlexaButton",
                                                "id":"myButton",
                                                "buttonText": "押す",
                                                "onPress":[
                                                    {
                                                        "type":"SetValue",
                                                        "componentId":"loadLayer",
                                                        "property":"opacity",
                                                        "value":0.5
                                                    },
                                                    {
                                                        "type":"SetValue",
                                                        "componentId":"myButton",
                                                        "property":"disabled",
                                                        "value":true
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ],
                                "justifyContent": "center",
                                "alignItems": "center"
                            }
                        ]
                    },
                    {
                        "type": "Container",
                        "width":"100%",
                        "height":"100%",
                        "id":"loadLayer",
                        "opacity":0,
                        "alignItems": "center",
                        "justifyContent": "center",
                        "position": "absolute",
                        "items": [
                            {
                                "type": "Frame",
                                "width":"100%",
                                "height":"100%",
                                "backgroundColor":"#000000"
                            },
                            {
                                "type": "AlexaProgressDots",
                                "position":"absolute"
                            }
                        ]
                    }
                ]
            }
        ]
    }
}
``` 

## 解説
簡単な解説をします。

### alexa-layouts
importの部分でalexa-layoutsをインポートしています。
lexa-layoutsをインポートすることで、今回使用しているテンプレートだと、以下が利用可能になります。

- [AlexaBackground](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-alexa-background-layout.html)
- [AlexaButton](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-alexa-button-layout.html)
- [AlexaProgressDots](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-alexa-progress-dots-layout.html)

AlexaBackgroundは、文字通り背景の設定をしています。ここでは、背景色をセットしています。

AlexaButtonは、基本的なボタン機能(押下時に色が変わる、等)を備えたコンポーネントになります。
あまり小回りはきかないですが、ボタンをTouchWrapperで独自実装するのがめんどくさい、という方にはおすすめです。  
 
AlexaProgressDotsは、今回の肝となるアニメーションの部分になります。
3個のドットがうねうね動くアニメーションなので、表示させるだけでローディング感が出ます。
後述の表示出しわけを使ってローディング中のみ表示させるようにします。

## ローディング部分をレイヤー的に配置する
上記jsonのコンポーネントIDでいうところの"loadLayer"がローディングアニメーション部分になります。
これは、背景色が黒のFrameとAlexaProgressDotsを重ねたものを一つのContainerにまとめています。
そして、このContainerのopacityを0にすることで、通常は表示しないようにしています。

なお、Containerはpostionを"absolute"に設定することで重ねることができますが、最上位階層ではそれができないので、最上位階層として一つContainerを作成(上記jsonでいうと、idが"base"のContainer)し、その次の階層からContainerを重ねるようにしていく必要があります。

### ボタン押下時にローディング部分を表示させる
AlexaButton押下時の処理(onPressの中)にて、ローディング部分のレイヤー"loadLayer"のopacityの値を変えてあげるようにします。1( = 不透明度100%)まであげると真っ黒になってしまうので、0.5にしています。
こうすることで、ボタン押下時にローディングアニメーション部分が表示されるようになります。  
また、実機の場合、ボタンの上にコンポーネントを重ねて表示しても、ボタンへのタッチは引き続き可能な状態が続きます。
そのため、一度ボタンを押下した後、ユーザーがさらに続けてボタン押下できないようにボタンのコンポーネントを無効化するコマンドを実行しています。

## 宣伝

上記ではAlexaProgressDotsを利用してローディング画面を利用していますが、
私が開発したAlexaスキル「[ペンギンのおつかい](https://www.amazon.co.jp/dp/B08K42SNB9/)」では、自作のローディングアニメーションを使って読み込み中を表現しています。
(当時、APL1.5が公開されていなかったため)
![広告.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354808/6f969aa7-f8de-b604-1845-8af346abe2bb.png)

「ペンギンのおつかい」では、ローディングアニメーション以外にも、以下のようなAPL技術が活用されています。

- AnimateItemコマンドを利用し、声で画面内のキャラクターを操作
- Videoコンポーネントを利用してスキル内でBGMを再生
- 「はい」「いいえ」のダイアログの表示
- 横長の画面をスライドさせていくことで、紙芝居風のチュートリアルを実現
- 画面フリックでキャラクターを操作

以下の説明記事(ProtoPedia)やYoutube動画を見ると概要が理解できると思います。
また、実際にスキルを起動して遊んでいただけると私が超喜びます。

[Alexaスキル "ペンギンのおつかい"(ProtoPedia)](https://protopedia.net/prototype/2017)
[Alexaスキル「ペンギンのおつかい」(ヒーローズ・リーグ2020応募スキル)](https://www.youtube.com/watch?v=nuVi6wYADAg)

APLをうまく活用することで、音声と画面表現を組み合わせた面白いコンテンツを提供することができますので、リファレンスや既存のAlexaスキルを見ながらいろいろ試していくと様々な学びを得られると思います。

## 参考URL

- [APL1.5の新機能 | Alexa Skills Kit(※2020/12/11現在。URLが"apl-latest-version.html"なので、現在の最新verの説明ページになると思います)](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-latest-version.html)
- [レスポンシブ対応コンポーネントとテンプレート | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-layouts-overview.html)
- [Background | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-alexa-background-layout.html)
- [Button | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-alexa-button-layout.html)
- [Progress Dots | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-alexa-progress-dots-layout.html)