😋

よくあるUIを作ってみる

2023/02/17に公開

Widgetの配置で苦戦した!

Paddingを使って数値で、Widgetを配置してましたがこれは実は良くないのを最近知りました!
端末が変わると、ズレたりするそうです!

こちらの記事が参考になりました
日本語の記事があるとありがたいですね。
https://flutter.ctrnost.com/basic/layout/axisalignment/

SNS風UIを作ってみる

Twitterやグルメアプリのようなリストで表示されるUIを作ってみました。画面を再現するだけなので、Columnの中に書いてます😅

こちらがリストを表示したページ

ソースコード

import 'package:sugary_map/service/export/global_export.dart';
import 'package:sugary_map/theme/appbar_theme.dart';
import 'package:sugary_map/theme/button_theme.dart';
import 'package:sugary_map/ui/page/user/user_nav/post_page/add_post.dart';
import 'package:sugary_map/ui/page/user/user_nav/post_page/post_detail.dart';

class PostPage extends StatefulWidget {
  const PostPage({Key? key}) : super(key: key);

  
  State<PostPage> createState() => _PostPageState();
}

class _PostPageState extends State<PostPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('口コミ'),
        backgroundColor: MyAppBar.appBar.appColor,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (() {
          context.goNamed(PostAdd.routeName);
        }),
        backgroundColor: MyButton.appButton.appColor,
        child: const Icon(Icons.add),
      ),
      // スクロールさせてOverFlowを解消する.
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            children: <Widget>[
              SizedBox(height: 50),
              // Containerをタップすると詳細ページへ画面遷移する.
              GestureDetector(
                onTap: () {
                  GoRouter.of(context).go('/post/post_detail');
                },
                child: Container(
                  // 水平方向にContainerを寄せる.
                  // 左端にスペースを20.0空ける.
                  margin: EdgeInsets.symmetric(horizontal: 20.0),
                  child: Row(
                    children: [
                      CircleAvatar(
                        backgroundColor: Colors.grey,
                      ),
                      SizedBox(width: 20),
                      Column(
                        // Textを全て左端に寄せる設定.
                        crossAxisAlignment: CrossAxisAlignment.start,
                        verticalDirection: VerticalDirection.down,
                        children: [
                          Text('上田 2023/01/04'),
                          Text('称号 甘党'),
                        ],
                      )
                    ],
                  ),
                ),
              ),
              SizedBox(height: 20),
              Container(
                width: 350,
                height: 200,
                color: Colors.grey[200],
              ),
              SizedBox(height: 10),
              Row(
                children: [
                  IconButton(
                      onPressed: () {}, icon: Icon(Icons.file_upload_outlined)),
                  IconButton(onPressed: () {}, icon: Icon(Icons.favorite)),
                  TextButton(onPressed: () {}, child: Text('12')),
                ],
              ),
              Container(
                  alignment: Alignment(-0.7, -0.7),
                  child: Text('クラシックなフランス菓子を数多く扱う名店。\nおすすめは、ガトーピレネーです。')),
              SizedBox(height: 30),
              Container(
                // 水平方向にContainerを寄せる.
                // 左端にスペースを20.0空ける.
                margin: EdgeInsets.symmetric(horizontal: 20.0),
                child: Row(
                  children: [
                    CircleAvatar(
                      backgroundColor: Colors.grey,
                    ),
                    SizedBox(width: 20),
                    Column(
                      // Textを全て左端に寄せる設定.
                      crossAxisAlignment: CrossAxisAlignment.start,
                      verticalDirection: VerticalDirection.down,
                      children: [
                        Text('minn 2023/01/04'),
                        Text('称号 お菓子ライター'),
                      ],
                    )
                  ],
                ),
              ),
              SizedBox(height: 20),
              Container(
                width: 350,
                height: 200,
                color: Colors.grey[200],
              ),
              SizedBox(height: 10),
              Row(
                children: [
                  IconButton(
                      onPressed: () {}, icon: Icon(Icons.file_upload_outlined)),
                  IconButton(onPressed: () {}, icon: Icon(Icons.favorite)),
                  TextButton(onPressed: () {}, child: Text('8')),
                ],
              ),
              Container(
                  alignment: Alignment(-0.7, -0.7),
                  child: Text('ざっくりとした食感のミルフィーユが、\n衝撃的な美味しさでした!')),
            ],
          ),
        ),
      ),
    );
  }
}

こちらが詳細ページ

ソースコード

import 'package:sugary_map/service/export/global_export.dart';
import 'package:sugary_map/theme/appbar_theme.dart';
import 'package:sugary_map/theme/button_theme.dart';
import 'package:sugary_map/ui/page/user/user_nav/post_page/add_post.dart';

class PostDetail extends StatefulWidget {
  const PostDetail({Key? key}) : super(key: key);

  
  State<PostDetail> createState() => _PostDetailState();
}

class _PostDetailState extends State<PostDetail> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('口コミ詳細'),
        backgroundColor: MyAppBar.appBar.appColor,
      ),
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            children: <Widget>[
              const SizedBox(height: 40),
              Container(
                width: 300,
                height: 150,
                color: Colors.grey[200],
              ),
              Container(
                width: 300,
                height: 50,
                child: Row(
                  children: [
                    Text('投稿者'),
                    const SizedBox(width: 20),
                    Text('上田'),
                  ],
                ),
              ),
              Container(
                alignment: const Alignment(0.0, 0.0),
                width: 300,
                height: 50,
                child: Row(
                  children: [
                    Text('いったお店'),
                    const SizedBox(width: 20),
                    Text('オーボン・ビュータン'),
                  ],
                ),
              ),
              Column(
                children: [
                  Container(
                      alignment: const Alignment(-1.0, -1.0),
                      width: 300,
                      height: 100,
                      child: Text('クラシックなフランス菓子を数多く扱う名店。\nおすすめは、ガトピレネーです。')),
                  Container(
                      alignment: const Alignment(-1.0, -1.0),
                      width: 300,
                      height: 100,
                      child: Column(
                        // テキストを左端に寄せる.
                        crossAxisAlignment: CrossAxisAlignment.start,
                        verticalDirection: VerticalDirection.down,
                        children: [
                          Text('東京都尾山台'),
                          SizedBox(height: 20),
                          Text('090-9988-3477'),
                        ],
                      )),
                ],
              ),
              Image.network(
                  'https://poi-static-map.cld.navitime.jp/02022/120998/image.png'),
            ],
          ),
        ),
      ),
    );
  }
}

まとめ

英語で座標を指定する説明が出てきたので、いくつかをまとめておきます。

  • Swiftでもあった水平か垂直か決めるコード

    • 水平 horizontality
    • 垂直 vertical
  • Containerで囲む

    • alignment: const Alignment(0.0, 0.0),// 中心に配置.
    • alignment: const Alignment(-1.0, -1.0),// 左上に配置.
    • alignment: const Alignment(-1.0, -1.0),// 右下に配置.

TextWidgetが左端によってくれないことがあった!
Columnに、CrossAxisAlignment.startとVerticalDirection.downを追加すると、左端によった!

Container(
      alignment: const Alignment(-1.0, -1.0),
      width: 300,
      height: 100,
      child: Column(
	// テキストを左端に寄せる.
	crossAxisAlignment: CrossAxisAlignment.start,
	verticalDirection: VerticalDirection.down,
	children: [
	  Text('東京都尾山台'),
	  SizedBox(height: 20),
	  Text('090-9988-3477'),
	],
      )),

Discussion