#自分用メモ FlutterのStack + AnimatedPositionedで詰まったこと

#StackでAnimatedPositionedを使う時に気を付けなければいけないこと
いろいろ詰まった

##Stack + AnimatedPositionedって良いよね
まずStackって何かというと、指定の位置に子Widgetを配置出来るWidgetです
アイテムを重ねて配置したいときにはまず第一に候補に上がるWidget。

```dart:stackTest.dart
class StackTest extends StatelseeWidget{
  @override
  Widget build(BuildContext context) => Stack(
      children: <Widget>[
        Positioned(
          top: 0,
          left: 0,
          bottom: 60,
          right: 50,
          child: Container(
            color: Colors.blue,
          ),
        ),
        Positioned(
           right: 0,
          bottom: 120,
          child: Card(
            child: Text("これはStackのテストだよ"),
          ),
        )
      ],
  )
}
```
**結果**
![Screenshot_1585197000 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520648/7a4fb765-6b9b-b300-98e3-8edcda7df2cf.png)

###Stackの子はすべてPositioned
StackにWidgetを配置するときは、Positionedでラップします。
Positionedが子の位置とサイズを決めます。
>位置パラメータ
>>top: 親の上端からの距離
bottom: 親の下端からの距離
left: 親の左端からの距離
right: 親の右端からの距離

>サイズパラメータ
>>width: 子の幅
height: 子の高さ

位置パラメータのうち**topとbottomの両方に**値がセットされていると、子の高さを強制的にそこに合わせます。
**leftとright**でも同様。

位置のうち一つしか入力されていなかった場合、位置はalignmentパラメータから決定されます。
alignment.centerなら中心に、alignment.topLeftなら左上に。

ちなみに描画はchildrenの上から順にされます。（つまり一番最後のWidgetが一番上に描画される)

##AnimatedPositionedって？
AnimatedPositionedは**Positioned**の派生。
子のサイズや色や位置を変更すると、その間を線形補間でアニメーションさせてくれる便利なやつ。
**Stack以外では機能しない**。

Positionedとの違いはcurveとdurationをパラメータとして与えられること。
curve:で変化の仕方を、duration:で変化に掛ける時間を指定します。
onEnd:でアニメーションが終了した時の処理も書けます。

```dart:animatedPositioned.dart
AnimatedPositioned(
  right: 0,
  bottom: 120,
  curve: Curves.fastOutSlowIn,
  duration: Duration(milliseconds: 400),
  onEnd: () => print("アニメーション終了"),
  child: Card(
    child: Text("これはStack\nのテストだよ",textScaleFactor: 3,),
  ),
)
```

##AnimatedPositionedで詰まったこと
###子の識別
Stackは基本、childrenの順番を使って同じWidgetかどうかを判断しています。
つまり変更前のchildren[0]と変更後のchildren[0]を比較し、その違いをアニメーションにしているわけです。
入れ替えで別のオブジェクトを入れても問題なくアニメーションするのはそのためです。
ただし、keyを設定していた場合はそれも考慮されます。
つまりchildrenの同じ位置にあるオブジェクトでも、keyが違えば同じものだとは認識されません。
また、同じkeyを設定していた場合でも、children内の位置が違えばアニメーションはしません。

###パラメーターの設定
AnimatedPositionedは実際に描画された位置ではなくパラメータを使って線形補間をしているようです。
つまり位置Aのオブジェクトと位置Bのオブジェクトで使っているパラメータが同じである必要があるってこと。

```dart:

var item_A = AnimatedPositioned(
  //rightとbottomを指定
  right: 0,
  bottom: 120,
  curve: Curves.fastOutSlowIn,
  duration: Duration(milliseconds: 400),
  onEnd: () => print("アニメーション終了"),
  child: Card(
    child: Text("これはちゃんとアニメーションしないよ",),
  ),
);

var item_B = AnimatedPositioned(
  top: 120,
  left:20
  //rightとbottomを指定していない。なのでこれを設定しても動かない
  curve: Curves.fastOutSlowIn,
  duration: Duration(milliseconds: 400),
  onEnd: () => print("アニメーション終了"),
  child: Card(
    child: Text("動かないよ～",),
  ),
)

```

位置パラメータが一つしか入力されていない場合、Stackは位置をalignmentを用いて決めます。
例えばAlignment.centerならStackの中心に子を配置し、そこから入力されているパラメータ分位置をずらすわけです。
しかしそこからの移動先が例えばtop : 20, bottom:40,だった場合、AnimatedPositionedは**アニメーションをしません**。最悪エラーを吐いてクラッシュします。

前回の位置と新しい位置を比較計算して補間アニメーションを作るのではなく、**単に入力されたパラメータ同士を比較して**アニメーションを作っていると思われます。

意外と融通が利かない……

というわけで、必ず移動前に入力していたパラメーターは移動先でも入力しましょう。

###意外と使いにくい……
触ってみた感じ、予想していたよりだいぶ使いにくいです。
 [GlobalKeyを使ってStackのサイズや位置を取得する方法](https://qiita.com/kemmowner/items/4ef1af413de9f951f25d)を使えばある程度は解決しますが、結局スパゲティコード化してしまう……

より柔軟性のあるFlowを使うのもありかもしれない