Flutter2.2でFormの値を保存してViewに反映する

前回サイドバーを取り付けてフォームにViewModelの値を初期値として設定することができました。

今回はフォームの値を変えたらViewModel保存してその値をStringからdouble型にキャストして 実際の矩形のサイズ変数に適用してView側の矩形に反映させようと思います。

まずはFormの値を変更し終えた後に発動するイベントを実装する必要があります。

TextFormField(
  decoration: const InputDecoration(labelText: 'X'),
  keyboardType: TextInputType.number,
  controller:
      TextEditingController(text: viewModel.editStatus['pos_x']),
  onFieldSubmitted: (text) {
    print(text);
  },
),

onFieldSubmittedがキモになります。

onChangedでもいいのですが、1つ文字を打つ毎に実行されてしまうので 処理負荷を考えるとエンターを押したときに実行される方が好ましいでしょう。

作るものによりけりではありますが、今回は即座に変更する必要はないので onFieldSubmitteedにしました。

引数に入力されている文字列をとるので、それをあとは該当のViewModelの変数に飛ばすだけですね。

  // サイドバーから値をもらって反映する
  void setWidgetParameter(String name, String param) {
    // 該当の名前のMapに値を挿入。
    _editStatus[name] = param;
  }

シンプルにこんな感じでいいでしょう。

中を作っていく前に現状矩形のポジション変数しかないので、構造体的に使えるウィンドウ用のクラスを作ります。

ViewModelに記載してください。

// Windowの値を保持するクラス(構造体的な使い方)
class _WindowStatus {
  String _name = "";
  String get name => _name;
  set name(String n) { _name = n; }

  Offset _pos = Offset(0,0);
  Offset get pos => _pos;
  set pos(Offset p) { _pos = p; }

  double _width = 0.0;
  double get width => _width;
  set width(double w) { _width = w; }

  double _height = 0.0;
  double get height => _height;
  set height(double h) { _height = h; }

  // コンストラクタ
  _WindowStatus(String name, Offset pos, double width, double height) {
    this._name = name;
    this._pos = pos;
    this._width = width;
    this._height = height;
  }
}

こんな感じに値をプロパティで保持できてgetterとsetterがあればいいかなと思います。

ウィンドウは追加できるようにしたいので、このクラスを保持する配列をつくっていきます。

ウィンドウを追加するのはまた今度にしたいので、今回は今ある矩形をこのクラスを使って表現します。

まずこのクラスを保持できる配列を作りましょう。

  // ウィンドウ配列
  List<_WindowStatus> windows = [
    _WindowStatus("ウィンドウ01", const Offset(0,0), 200, 100)
  ];

本番は空の配列ですがとりあえず初期値を入れておきましょう。

ここで思ったのが配列分ウィジェットを描写する方法を知らないんですよね。

とりあえずリテラルで配列0番目を呼び出して動くことを確認しましょう。

  @override
  Widget build(BuildContext context) {
    return Consumer<LayouteditViewModel>(builder: (context, viewModel, _) {
      return Stack(
        children: <Widget>[
          Positioned(
            left: viewModel.windows[0].pos.dx,
            top: viewModel.windows[0].pos.dy,
            child: Draggable(
              feedback: Container(
                width: viewModel.windows[0].width,
                height: viewModel.windows[0].height,
                color: Colors.blue[100],
              ),
              child: Container(
                width: viewModel.windows[0].width,
                height: viewModel.windows[0].height,
                color: Colors.blue,
              ),
              childWhenDragging: Container(),
              onDraggableCanceled: (view, offset) {
                _setPos(context, offset);
              },
            ),
          ),
        ],
      );
    });
  }
}

ここで気付きましたがカラーも変数化しておいたほうがよさそうですね。

ウィンドウ毎にカラーを変えられるようにしておくと識別しやすいのでユーザーフレンドリーです。

次にsetWindowPositionを刷新します。

  // ウィンドウ自由配置用のセットメソッド
  void setWindowPosition(Offset vp) {
    // 矩形の左上がどこのグリッドにいるか調べる(小数点切り捨て)
    double gx = ((vp.dx / gridSize).floor()) * gridSize;
    double gy =
        (((vp.dy - appBar.preferredSize.height) / gridSize).floor()) * gridSize;
    // ウィンドウ配列のポジションを更新
    windows[0].pos = Offset(gx, gy);
    notifyListeners();
  }

問題なく動いてくれてます。

やっとこさ本題に入れます。

矩形のポジションをサイドバーに反映させてみる

widthとheightは後回しにして一旦positionの変更をサイドバーに反映させてみましょう。

positionの値を適用する「setWindowPosition」にサイドバー用の変数を書き換えるコードを加えます。

  // ウィンドウ自由配置用のセットメソッド
  void setWindowPosition(Offset vp) {
    // 矩形の左上がどこのグリッドにいるか調べる(小数点切り捨て)
    double gx = ((vp.dx / gridSize).floor()) * gridSize;
    double gy =
        (((vp.dy - appBar.preferredSize.height) / gridSize).floor()) * gridSize;
    // ウィンドウ配列のポジションを更新
    windows[0].pos = Offset(gx, gy);
    // サイドバーの値を更新
    _editStatus['pos_x'] = vp.dx.toString();
    _editStatus['pos_y'] = (vp.dy - appBar.preferredSize.height).toString();
    // 更新通知
    notifyListeners();
    // debugPrint('dx => ${_pos.dx} |  dx => ${_pos.dy} ');
  }

キャストするとき as 型でやろうとするとエラーがでました。

プリミティブ型は基本的にはtoメソッドを使ったほうがいいみたいですね。

これでドラッグで矩形の位置を変えたらステータスもいい感じに書き換わってくれました。

y軸はAppBar分ずらすのを忘れないようにしましょう。(1敗)

今度はサイドバー側から書き換えに挑戦してみましょう。

  // サイドバーから値をもらって反映する
  void setWidgetParameter(String name, String param) {
    // 該当の名前のMapに値を挿入。
    _editStatus[name] = param;
    // 型チェックしてからdoubleに変換して値を保存する
    try {
      switch (name) {
        case "pos_x":
          windows[0].pos = Offset(double.parse(param), windows[0].pos.dy);
          break;
        case "pos_y":
          windows[0].pos = Offset(windows[0].pos.dx, double.parse(param));
          break;
        case "width":
          windows[0].width = double.parse(param);
          break;
        case "height":
          windows[0].height = double.parse(param);
          break;
      }
    } catch (exception) {
      print(name);
      print("ダブル変換エラー");
    }
  }

サイドバーは基本的に1つずつ要素を変更するので それぞれの名前と値を渡して変更するようにします。

数値から文字列に変更するのは問題ないですが、文字列から数値に変換する場合 もしかすると数値に変換できない値が含まれている場合があるので ちゃんとチェックしてやる必要があります。

キャストができなかったらエラーがでるようにしています。

今はエラー表記しか書いてませんが、数値以外の文字が入ったら現状保存している値に入れるかしたほうが良いかもしれません。

これでサイドバーから矩形の値を変更できるようになりました。

今回目的は達成しました。

現状色々問題がでています。

フォーカスがフォームに持っていかれてしまうので グリッドのショートカットが効かなくなってしまい検索窓がでてきたり。

どこにフォーカスしていても動くようにしたほうがいいかもしれませんね。

この辺はちゃんと設計しなおしたほうがよさそうなので一旦後回しにします。

一応画面の右上にアイコン置いて押したらグリッドのON/OFFを設けたりもしようと思います。

次回は現状配列リテラルで渡しているので0個目しか動かない状況です。

配列を増やして複数のウィンドウを出せるようにしてみます。