Hi 🙋‍♀️about me 😎posts 📚

Web でアレやる時に Flutter だとコレやるリスト

intro

Web 開発者が Flutter を触るときに、「web のアレを flutter でやる時はどうするんだっけ」リストを作る。

思いついたものを一旦書き出し、今後も追記していく予定。

基本的には下記のリンク先を見れば解決すると思うが、学習も兼ねてサンプルを貼り付けてみる。

https://flutter.dev/docs/development/ui/widgets

<p>,<span> が欲しい

https://flutter.dev/docs/development/ui/widgets/text

Text, RichText

基本的に TextNode が欲しい場合は Text Widget を使えば ok。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      'Hello, How are you?',
    );
  }
}

連続した文章(web で言えば inline 方向に対して連続な)文字列の中で、特定の文字列に対してスタイルを当てたいという場合は RichText を使う。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RichText(
      text: TextSpan(
        text: 'Hello ',
        style: DefaultTextStyle.of(context).style,
        children: <TextSpan>[
          TextSpan(text: 'This '),
          TextSpan(text: 'is', style: TextStyle(fontWeight: FontWeight.bold)),
          TextSpan(text: ' DOG!!'),
        ],
      ),
    );
  }
}

CSS かましたい

Widget 単位でかましたい場合、基本的には各 Widget に備わっている style プロパティに対して、各 Style Object を渡してやるような感じだ。

基本的には型情報を参考にしてオブジェクトを組み立ていく。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello, How are you?',
        style: TextStyle(
            background: Paint()..color = Colors.red, color: Colors.white));
  }
}

全体的なスタイルに関する設定はこの辺を見ると良いと思う。

https://flutter.dev/docs/development/ui/widgets/styling

<img> が欲しい

Image

local の画像を読み込む方法と、ネットワーク越しの画像を読み込む方法がある。

local

対象の dir から画像を読み込む。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Image.asset('assets/images/placeholder_image.png');
  }
}

対象の dir は pubspec.yaml に記述する。

# The following section is specific to Flutter.
flutter:
  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/images/
$ ls -G ./assets/images/placeholder_image.png
./assets/images/placeholder_image.png

ネットワーク

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Image(
      image: NetworkImage(
          'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
    );
  }
}

アイコンつけたい

Icon

<input> が欲しい

基本ここ見れば ok

https://flutter.dev/docs/development/ui/widgets/input

<input> に placeholder つけたい

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox(
        width: 300,
        child: TextFormField(
          decoration: const InputDecoration(
            hintText: 'What do people call you?',
          ),
        ),
      ),
    );
  }
}

ラベルやアイコンなども簡単に付けられる。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox(
        width: 300,
        child: TextFormField(
          decoration: const InputDecoration(
            icon: Icon(Icons.person),
            hintText: 'What do people call you?',
            labelText: 'Name *',
          ),
        ),
      ),
    );
  }
}

focus すると色が変わって placeholder がつく。

<button> が欲しい

ボタンの種類に応じていくつか Widget が用意されている。この中から望ましいものを選んで使うと良い。

https://flutter.dev/docs/development/ui/widgets/material

border-radius したい

Container + BoxDecoration

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 300.0,
      height: 300.0,
      decoration: BoxDecoration(
        color: const Color(0xff7c94b6),
        border: Border.all(
          color: Colors.black,
          width: 8,
        ),
        borderRadius: BorderRadius.circular(12),
      ),
    );
  }
}

画像をクリップ(clip)したい / 画像に border-radius したい

ClipRRect

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(color: Colors.redAccent),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(20),
        child: Image(
          image:
              NetworkImage('https://www.tutorialkart.com/img/hummingbird.png'),
        ),
      ),
    );
  }
}

background-image が欲しい

Container + BoxDecoration + DecorationImage

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 300,
      height: 300,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        image: const DecorationImage(
          image:
              NetworkImage('https://www.tutorialkart.com/img/hummingbird.png'),
        ),
        border: Border.all(
          color: Colors.black,
          width: 8,
        ),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Text(
        'feawffeafwefwaefawdogd',
        style: TextStyle(color: Colors.red),
      ),
    );
  }
}

<div> が欲しい

Container でほぼ web の <div> を読み替えることができる。

flex コンテナー作りたい

flex-direction の向きに応じて 2 つの weiget を使い分ける。

Row

水平方向に対して子要素を配置していく。flex-direction: row したいときはこっち。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
          decoration: BoxDecoration(
            color: Colors.blueAccent,
          ),
          padding: const EdgeInsets.all(30.0),
          child: const Text('DOG', textAlign: TextAlign.center),
        ),
        Container(
          padding: const EdgeInsets.all(30.0),
          decoration: BoxDecoration(
            color: Colors.greenAccent,
          ),
          child: const Text('DOG', textAlign: TextAlign.center),
        ),
        Container(
          padding: const EdgeInsets.all(30.0),
          decoration: BoxDecoration(
            color: Colors.redAccent,
          ),
          child: const Text('DOG', textAlign: TextAlign.center),
        ),
      ],
    );
  }
}

justify-contentalign-content は、それぞれ mainAxisAlignmentcrossAxisAlignment に置き換えることができる。

Flutter 側の API 側の方が、主軸と交差軸という情報が API 名に顕現しているので好みである。(web の方はどっちがどっちかたまにわからなくなる)

Row の場合、水平方向が主軸となり、垂直方向が交差軸となる。

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      ...
    ),

とりうる値は、補完でだいたい分かる。だいたい web と同じなのでそのまま読み替えて良いと思う。

Column

垂直方向に対して子要素を配置していく。flex-direction: column したいときはこっち。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          decoration: BoxDecoration(
            color: Colors.blueAccent,
          ),
          padding: const EdgeInsets.all(30.0),
          child: const Text('DOG', textAlign: TextAlign.center),
        ),
        Container(
          padding: const EdgeInsets.all(30.0),
          decoration: BoxDecoration(
            color: Colors.greenAccent,
          ),
          child: const Text('DOG', textAlign: TextAlign.center),
        ),
        Container(
          padding: const EdgeInsets.all(30.0),
          decoration: BoxDecoration(
            color: Colors.redAccent,
          ),
          child: const Text('DOG', textAlign: TextAlign.center),
        ),
      ],
    );
  }
}

Column は Row の direction が交差した vresion なので割愛。

padding 効かせたい

やり方は主に 2 種類存在する。

Container

div みたいなやつに持たせるパターン

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        border: Border.all(
          color: Colors.indigoAccent,
          width: 7,
        ),
      ),
      padding: const EdgeInsets.all(8.0),
      child: const Text('DOG', textAlign: TextAlign.center),
    );
  }
}

Padding

Padding というクラスで囲ってしまうパターン

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        border: Border.all(
          color: Colors.indigoAccent,
          width: 7,
        ),
      ),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: const Text('DOG', textAlign: TextAlign.center),
      ),
    );
  }
}

どちらも便利なので、うまく使い分けたい。

margin 効かせたい

やり方は色々ある。

Container

div みたいなやつに持たせるパターン

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        border: Border.all(
          color: Colors.indigoAccent,
          width: 7,
        ),
      ),
      child: Container(
        margin: const EdgeInsets.all(8.0),
        decoration: BoxDecoration(
          border: Border.all(
            color: Colors.red,
            width: 7,
          ),
        ),
        child: const Text('DOG', textAlign: TextAlign.center),
      ),
    );
  }
}

SizedBox

本来は width/height を pixel perfect に決めたい要素に対して wrap して利用する widget だが、空間確保にも利用することができる。

正確には margin クラスなどではないが、概念的には margin に近いのでこちらも紹介したい。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.red,
              width: 15,
            ),
          ),
        ),
        SizedBox(
          height: 70.0,
        ),
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.indigoAccent,
              width: 15,
            ),
          ),
        ),
        SizedBox(
          height: 190.0,
        ),
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.greenAccent,
              width: 15,
            ),
          ),
        ),
      ],
    );
  }
}

Spacer

連続した要素(web で言えば ul,ol など)に対し、それらの余白を均等割りした空間をそれぞれ係数付きで付与できる便利なクラスだ。flex における justify-content: space-between の上位互換みたいなものに近い。

正確には margin クラスなどではないが、概念的には margin に近いのでこちらも紹介したい。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.red,
              width: 15,
            ),
          ),
        ),
        Spacer(flex: 1),
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.indigoAccent,
              width: 15,
            ),
          ),
        ),
        Spacer(), // default flex = 1
      ],
    );
  }
}

比率を変更したい場合、引数の flex の値を変更してやる。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.red,
              width: 15,
            ),
          ),
        ),
        Spacer(flex: 1),
        Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.indigoAccent,
              width: 15,
            ),
          ),
        ),
        Spacer(
          flex: 2,
        ),
      ],
    );
  }
}

background-color がほしい

Container + BoxDecoration

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.cyanAccent,
      ),
      child: const Text('DOG', textAlign: TextAlign.center),
    );
  }
}

border かましたい

Container + BoxDecoration

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.cyanAccent,
      ),
      child: const Text('DOG', textAlign: TextAlign.center),
    );
  }
}

z-index かましたい

重ね合わせを表現するときは Stack widget を使う。

Stack

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(children: buildBoxs());
  }

  List<Container> buildBoxs() {
    return [
      Container(
        width: 300.0,
        height: 300.0,
        decoration: BoxDecoration(
          color: Colors.blueAccent,
        ),
        child: Align(
          child: const Text('DOG'),
          alignment: Alignment.bottomCenter,
        ),
      ),
      Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: Colors.greenAccent,
        ),
        child: Align(
          child: const Text('DOG'),
          alignment: Alignment.bottomCenter,
        ),
      ),
      Container(
        width: 100.0,
        height: 100.0,
        decoration: BoxDecoration(
          color: Colors.redAccent,
        ),
        child: Align(
          child: const Text('DOG'),
          alignment: Alignment.bottomCenter,
        ),
      ),
    ];
  }
}

Stack の alignment も便利なので、使いこなせると良い。

    return Stack(
      children: buildBoxs(),
      alignment: AlignmentDirectional.center,
    );

block 要素を inline 要素にしたい

WidgetSpan

画面幅いっぱいの要素が欲しい

こんな感じのやつ。主に 2 通りある。

Container + w/h MediaQuery

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        decoration: BoxDecoration(
          color: Colors.blueAccent,
        ),
        child: const Text('DOG'));
  }
}

SizedBox.expand

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: Container(
          decoration: BoxDecoration(
            color: Colors.blueAccent,
          ),
          child: const Text('DOG')
      ),
    );
  }
}

上下/左右/中央の幅寄せしたい

色々ある。

Row/Column を使っている場合

mainAxisAlignment: MainAxisAlignment.center & crossAxisAlignment: CrossAxisAlignment.center

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var column = Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: buildBoxs(),
    );
    return fullScreenContainer(context, column);
  }

  List<Container> buildBoxs() {
    return [
      Container(
        decoration: BoxDecoration(
          color: Colors.blueAccent,
        ),
        padding: const EdgeInsets.all(30.0),
        child: const Text('DOG', textAlign: TextAlign.center),
      ),
      Container(
        padding: const EdgeInsets.all(30.0),
        decoration: BoxDecoration(
          color: Colors.greenAccent,
        ),
        child: const Text('DOG', textAlign: TextAlign.center),
      ),
      Container(
        padding: const EdgeInsets.all(30.0),
        decoration: BoxDecoration(
          color: Colors.redAccent,
        ),
        child: const Text('DOG', textAlign: TextAlign.center),
      ),
    ];
  }

  Container fullScreenContainer(BuildContext context, Widget column) {
    return Container(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(
        color: Colors.black,
      ),
      child: column,
    );
  }
}

Align + alignment: Alignment.center

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var column = Align(child: Text('DOG'), alignment: Alignment.center);
    return fullScreenContainer(context, column);
  }

  Container fullScreenContainer(BuildContext context, Widget column) {
    return Container(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(
        color: Colors.blueAccent,
      ),
      child: column,
    );
  }
}

Align は便利で、他にもいろんな場所に揃えることができる。

Container + alignment: Alignment.center

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var column = Container(child: Text('DOG'), alignment: Alignment.center);
    return fullScreenContainer(context, column);
  }

  Container fullScreenContainer(BuildContext context, Widget column) {
    return Container(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(
        color: Colors.blueAccent,
      ),
      child: column,
    );
  }
}

Center

その名の通りの要素が用意されている。

https://api.flutter.dev/flutter/widgets/Center-class.html

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var column = Center(child: Text('DOG'));
    return fullScreenContainer(context, column);
  }

  Container fullScreenContainer(BuildContext context, Widget column) {
    return Container(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(
        color: Colors.blueAccent,
      ),
      child: column,
    );
  }
}

position:relative, position:absolute が欲しい

Positioned

Stack と合わせて利用する。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(children: buildBoxs());
  }

  List<Widget> buildBoxs() {
    return [
      Container(
        width: 300.0,
        height: 300.0,
        decoration: BoxDecoration(
          color: Colors.blueAccent,
        ),
        child: Align(
          child: const Text('DOG'),
          alignment: Alignment.bottomCenter,
        ),
      ),
      Positioned(
        bottom: 20,
        left: 80,
        child: Container(
            width: 200.0,
            height: 200.0,
            decoration: BoxDecoration(
              color: Colors.greenAccent,
            ),
            child: const Text('DOG')),
      ),
      Positioned(
        top: 120,
        right: 150,
        child: Container(
          width: 100.0,
          height: 100.0,
          decoration: BoxDecoration(
            color: Colors.redAccent,
          ),
          child: Align(
            child: const Text('DOG'),
            alignment: Alignment.bottomCenter,
          ),
        ),
      ),
    ];
  }
}

Flutter 独自系

レイアウトに関して全体的に知りたい

https://flutter.dev/docs/development/ui/widgets/layout

白紙のページどうやって作るんだっけ

Scaffold

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

画面端で見切れちゃう

SafeArea

例えばこんな感じの widget があったとすると、

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Text('feaowigoeha'));
  }
}

こんな感じで左上が時刻の表示と被ってしまう。

SafeArea で囲うことで、OS 毎にもたらされる領域をよしなに padding で埋めてくれるという便利 widget だ。

class Tmp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: SafeArea(child: Text('feaowigoeha')));
  }
}