Flutter2.2でサイドバーを作る

グリッド関連が一旦実装できたので次はサイドメニューをつけようと思います。

普通のドロワーメニューだと黒背景透過のオーバーレイがかかってメイン部分を触れなくなってしまうので

とりあえず同じファイルに記載して後から使い回せるようにファイル分けをしたいですね。

layout_view_modelにサイドバー用の配列を作ります。

  // サイドバー表示用のステータス保持配列
  final Map<String, String> _editStatus = <String, String>{
    'pos_x': "0.0",
    'pos_y': "0.0",
    'width': "100.0",
    'height': "100.0"
  };
  Map<String, String> get editStatus => _editStatus;

フォーカスされたらそのウィジェットのステータスが入るといった感じですね。

値をdouble型にしようと思いましたが結局Stringにしないとフォームに表示できないのでString型にしました。

ウィジェットに渡すときはキャストして渡す感じになります。

なのでフォームのバリデーションに数値以外のものを渡さないようにしておいたほうがいいですね。

バリデーションに関してはまた後日やります。

サイドバーに表示してそこが変化すればウィジェットにも反映されるといった感じにしたいです。

Map型動的配列のように新たな要素を挿入ができないらしいので サイドバーは要素数が最初に決まった数から増えないので丁度いいですね。

とりあえずウィンドウとして必要な縦横位置の高さ幅を保持する物を作りました。

必要に慣れば後から変数に追加していけばOKです。

次はこれを参照するためのウィジェットを作りましょう。

Drawerはおそらくプラグインがあるのでしょうがサイドバーはそんな大層な動きとか要らないので 単純にウィジェットを設置してその上にフォーム要素を付け加えていく形にします。

表示非表示も一旦アニメーション抜きで実装しましょう。

まずは親Widgetに直書きしてみます。

class LayouteditPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (_) => LayouteditViewModel()),
        ],
        child: Scaffold(
            appBar: LayouteditViewModel().appBar,
            body: SafeArea(
                child: Row(children: [
              Expanded(
                flex: 5,
                child: LayouteditPageBody(),
              ),
              Expanded(
                  child: Column(children: [
                TextFormField(
                  decoration: const InputDecoration(labelText: 'Window Name'),
                  keyboardType: TextInputType.number,
                ),
                TextFormField(
                  decoration: const InputDecoration(labelText: 'X'),
                  keyboardType: TextInputType.number,
                ),
                TextFormField(
                  decoration: const InputDecoration(labelText: 'Y'),
                  keyboardType: TextInputType.number,
                ),
                TextFormField(
                  decoration: const InputDecoration(labelText: 'Width'),
                  keyboardType: TextInputType.number,
                ),
                TextFormField(
                  decoration: const InputDecoration(labelText: 'Height'),
                  keyboardType: TextInputType.number,
                ),
              ])),
            ]))));
  }
}

まずBodyの部分をセーフエリアで囲みました。

そしてRowで複数ウィジェットをおけるようにし、 元々あったLayouteditPageBodyとサイドバーを設置しています。

Expandedウィジェットを使うと隙間なく横並びにしてくれます。

ここはお好みです。

サイドバーを右にしたいので最初にメインカラムの方を設置して 続いてサイドバーという形になっています。

Expandedでflexというパラメーターを入れるとサイズを指定できます。

いじってみて好きな感じのレイアウトにしてみてください。

そしてサイドバーの部分なんですが、現状ウィジェット直打ちになっているので クラスとして分けてしまいましょう。

// サイドメニュー
class _propertyWindow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<LayouteditViewModel>(builder: (context, viewModel, _) {
      return Column(children: [
        TextFormField(
          decoration: const InputDecoration(labelText: 'Window Name'),
          keyboardType: TextInputType.number,
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'X'),
          keyboardType: TextInputType.number,
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'Y'),
          keyboardType: TextInputType.number,
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'Width'),
          keyboardType: TextInputType.number,
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'Height'),
          keyboardType: TextInputType.number,
        ),
      ]);
    });
  }
}

こんな感じのクラスにしました。

これでViewModelから値をとってくることができますね。

早速置き換えてみましょう。

// サイドメニュー
class _propertyWindow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<LayouteditViewModel>(builder: (context, viewModel, _) {
      return Column(children: [
        TextFormField(
          decoration: const InputDecoration(labelText: 'Window Name'),
          keyboardType: TextInputType.number,
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'X'),
          keyboardType: TextInputType.number,
          controller:
              TextEditingController(text: viewModel.editStatus['pos_x']),
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'Y'),
          keyboardType: TextInputType.number,
          controller:
              TextEditingController(text: viewModel.editStatus['pos_y']),
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'Width'),
          keyboardType: TextInputType.number,
          controller:
              TextEditingController(text: viewModel.editStatus['width']),
        ),
        TextFormField(
          decoration: const InputDecoration(labelText: 'Height'),
          keyboardType: TextInputType.number,
          controller:
              TextEditingController(text: viewModel.editStatus['height']),
        ),
      ]);
    });
  }
}

ViewModelから値をとってくるのももう慣れましたね。

レイアウトを整える

サイドバー全体にフォームが広がっているので少し余白を取りたいです。

あとメインカラムとサイドカラムで色が同じなのでサイドバー感がないため サイドカラムの方を少し灰色にしてしまってもいいかもしれません。

メイン側はグリッド線があるので白のほうが良い気がしました。

とりあえず背景色と余白を作りましょう。

// サイドメニュー
class _propertyWindow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<LayouteditViewModel>(builder: (context, viewModel, _) {
      return Container(
          padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
          color: const Color.fromARGB(255, 240, 240, 240),
          child: Column(children: [
            TextFormField(
              decoration: const InputDecoration(labelText: 'Window Name'),
              keyboardType: TextInputType.number,
            ),
            TextFormField(
              decoration: const InputDecoration(labelText: 'X'),
              keyboardType: TextInputType.number,
              controller:
                  TextEditingController(text: viewModel.editStatus['pos_x']),
            ),
            TextFormField(
              decoration: const InputDecoration(labelText: 'Y'),
              keyboardType: TextInputType.number,
              controller:
                  TextEditingController(text: viewModel.editStatus['pos_y']),
            ),
            TextFormField(
              decoration: const InputDecoration(labelText: 'Width'),
              keyboardType: TextInputType.number,
              controller:
                  TextEditingController(text: viewModel.editStatus['width']),
            ),
            TextFormField(
              decoration: const InputDecoration(labelText: 'Height'),
              keyboardType: TextInputType.number,
              controller:
                  TextEditingController(text: viewModel.editStatus['height']),
            ),
          ]));
    });
  }
}

ColumnをContainerで囲ってchildに渡しただけですね。

ContainerのColorで背景を設定できます。

余白はpaddingで設定するのですが、CSSのようにそのまま数値を設定することはできません。

EdgeInsets.fromLTRBという関数を使って設定することになります。

上下左右の余白を決められるので好きなようにしてください。

私は20pxを設定しました。

これでいい感じにサイドバーが出来上がりました。

Drawerを使うとメインカラムの方を触りにくかったり元々の処理が邪魔するので ステータスなんかを常に表示したいサイドバーがほしいときは自力で導入したほうがいいでしょう。

ちなみに「Window Name」という部分は選択しているウィンドウの名前を表示しようとしてるのですが 一旦無視で進めています。