Flutter Webで複雑なカードのレイアウトをレスポンシブにやる

もうほとんどが個人用のメモになってきてる

例によってFlutter Webって書いたけど別にスマホでも動く、というかスマホの縦横対応のためにやってみた

# こういうやつ

<img src="https://i.gyazo.com/50e42878364df907f19b4d7e51fc7a61.gif" width=400 />

スマホのためにやったのでおまけでスマホのも

<img src="https://i.gyazo.com/3bcacb12c2421f69ce97bc096396ace6.gif" width=400 />


# ソース

・全体の横幅画面サイズの横幅で決まる
・左と右の列の横幅は`leftExpand`:`rightExpand`で決まる
・縦幅は、まず右の列の縦幅を`rightAspectRatio`で決めて、それに左の列の縦幅が合うように比率計算をして`leftAspectRatio`としてる

```dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Widget createLeftItems(double leftAspectRatio) {
    //列数の違うRowの位置調整をするためのcontainer
    var expandedSpaceContainer = ({int flex}) {
      return Expanded(
        flex: flex,
        child: Container(),
      );
    };

    var leftOneLine = Row(children: <Widget>[
      expandedSpaceContainer(flex: 1),
      Expanded(
        flex: 4,
        child: FittedBox(
          fit: BoxFit.fitWidth,
          child: Text(
            "Gakky is SAIKO",
          ),
        ),
      ),
      expandedSpaceContainer(flex: 1),
    ]);

    var leftTwoLine = Text(
      "She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!!She is TENSHI!! YEAH!! ",
    );

    var starsRow = ({int starCount, int fillCount}) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        children: List.generate(starCount, (index) {
          return Expanded(
            child: FittedBox(
              child: Icon(
                index < fillCount ? Icons.star : Icons.star_border,
                color: Colors.green,
              ),
            ),
          );
        }),
      );
    };

    var reviews = () {
      return FittedBox(
        child: Text(
          "170 Review",
          style: TextStyle(fontSize: 20, color: Colors.blue),
        ),
      );
    };
    var leftThreeLine = Container(
      width: double.infinity,
      height: 200,
      padding: EdgeInsets.only(bottom: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          Expanded(
            flex: 3,
            child: starsRow(starCount: 5, fillCount: 3),
          ),
          expandedSpaceContainer(flex: 1),
          Expanded(
            flex: 1,
            child: reviews(),
          ),
        ],
      ),
    );

    var icons = () {
      return [
        [Icons.kitchen, "PREP:", "25 min"],
        [Icons.timer, "COOK:", "1 hr"],
        [Icons.restaurant, "FEEDS:", "4-6"]
      ].map((data) {
        return Column(
          children: [
            Expanded(
              child: FittedBox(
                child: Icon(
                  data[0],
                  color: Colors.green[500],
                ),
              ),
            ),
            Expanded(
              child: FittedBox(
                child: Text(data[1]),
              ),
            ),
            Expanded(
              child: FittedBox(
                child: Text(data[2]),
              ),
            ),
          ],
        );
      }).toList();
    };
    var leftFourLine = DefaultTextStyle.merge(
      style: TextStyle(
          color: Colors.black,
          fontWeight: FontWeight.w800,
          fontFamily: 'Robot',
          letterSpacing: 0.5,
          fontSize: 15,
          height: 2),
      child: Container(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: icons(),
        ),
      ),
    );

    var itemChilidren = [
      Expanded(
        flex: 2,
        child: leftOneLine,
      ),
      Expanded(
        flex: 3,
        child: leftTwoLine,
      ),
      Expanded(
        flex: 2,
        child: leftThreeLine,
      ),
      Expanded(
        flex: 3,
        child: leftFourLine,
      ),
    ];

    return AspectRatio(
      aspectRatio: leftAspectRatio,
      child: Container(
        padding: EdgeInsets.only(right: 20),
        child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: itemChilidren),
      ),
    );
  }

  Widget createRightItems(double rightAspectRatio) {
    return AspectRatio(
      aspectRatio: rightAspectRatio,
      child: Image.network(
        "https://geinou-tomodachi.work/wp-content/uploads/2018/10/761835_615-e1538578576474.jpg",
        fit: BoxFit.fitWidth,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final leftExpand = 3;
    final rightExpand = 4;

    final rightAspectRatio = 4 / 3;
    var rightItems = createRightItems(rightAspectRatio);

    final leftAspectRatio = leftExpand * rightAspectRatio / rightExpand;
    var leftItems = createLeftItems(leftAspectRatio);

    var columnInsideRow = Card(
      child: Container(
        padding: EdgeInsets.all(10),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(flex: leftExpand, child: leftItems),
            Expanded(flex: rightExpand, child: rightItems),
          ],
        ),
      ),
    );

    return Scaffold(
      body: SafeArea(
        child: Container(
          color: Colors.white,
          child: Center(child: columnInsideRow),
        ),
      ),
    );
  }
}
```

# まとめ

自分で後から見返さなくてもいいようにちゃんと要点だけ書きたい