😎

やってみた01 Flutter 模写 X(Twitter)編

2024/07/08に公開

概要

ウィジェットツリーについて知識を深めるためにX(Twitter)の検索画面を模写してみた。

模写ページ

以下の検索ページを模写してみる。

ウィジェットツリー

これをウィジェットツリーを作成するとこんな感じ
ちょっと多いから複数に分ける

Appbar


Body


FloatingActionButton


BottomNavigationBar


模写結果

・使用時間:3時間
・よかった点:フレームワークは自分の知識で考えることができた。
       Stackの知識があまりなかったので、勉強になった。
       自分がどこまでコーディングできるか確認できた。
・悪かった点:細かいところの設定方法はググらないとわからなかった。
       よくみると所々差異がある。

以下が作成した画面とコードになる。
画像やアイコンはないものがあったため、そこは類似品で代用した。

import 'package:flutter/material.dart';

void main() async {
  // WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      initialIndex: 0,
      length: 5,
      child: Scaffold(
        backgroundColor: const Color(0xFF171F29),
        appBar: AppBar(
            backgroundColor: const Color(0xFF171F29),
            leading: Container(
              margin: const EdgeInsets.all(4),
              decoration: const BoxDecoration(
                  shape: BoxShape.circle,
                  image: DecorationImage(
                      fit: BoxFit.contain,
                      image: AssetImage("images/default_profile.png"))),
            ),
            title: const SizedBox(
              height: 40,
              width: double.infinity,
              child: TextField(
                style: TextStyle(
                  color: Colors.white, // 文字の色を指定
                  fontSize: 13.0,
                ),
                decoration: InputDecoration(
                  iconColor: Color(0xFF2A333F),
                  hintText: '検索',
                  hintStyle: TextStyle(color: Colors.grey),
                  filled: true,
                  fillColor: Color(0xFF2A333F),
                  contentPadding: EdgeInsets.zero, // 内側のパディング
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.all(Radius.circular(50.0)),
                    borderSide: BorderSide.none,
                  ),
                  prefixIcon: Icon(Icons.search,
                      color: Colors.grey), // hintTextにアイコンを含める
                ),
              ),
            ),
            actions: [
              IconButton(
                  onPressed: () {},
                  icon: const Icon(
                    Icons.settings_outlined,
                    color: Colors.white,
                  ))
            ],
            bottom: const PreferredSize(
              preferredSize: Size.fromHeight(33.0),
              child: TabBar(
                indicatorColor: Colors.blue,
                labelColor: Colors.white,
                unselectedLabelColor: Colors.grey,
                isScrollable: true,
                tabs: <Widget>[
                  Tab(text: 'おすすめ'),
                  Tab(text: 'トレンド'),
                  Tab(text: 'ニュース'),
                  Tab(text: 'スポーツ'),
                  Tab(text: 'エンターテインメント'),
                ],
              ),
            )),
        body: SingleChildScrollView(
          child: Column(
            children: [
              Stack(
                alignment: AlignmentDirectional.topCenter,
                children: [
                  Container(
                      height: 220,
                      width: double.infinity,
                      child: Image.asset(
                        'images/yakei.jpg',
                        fit: BoxFit.cover,
                      )),
                  Positioned(
                    top: 10,
                    left: 360,
                    child: GestureDetector(
                      onTap: () {},
                      child: Container(
                        width: 20,
                        height: 20,
                        decoration: const BoxDecoration(
                          shape: BoxShape.circle,
                          color: Colors.black,
                        ),
                        child: const Icon(
                          Icons.more_horiz,
                          size: 16,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ),
                  const Positioned(
                    top: 130,
                    left: 5,
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          '#七夕の夜は水玉ドローンショー',
                          style: TextStyle(
                            fontSize: 20,
                            color: Colors.white,
                            fontWeight: FontWeight.w700,
                          ),
                        ),
                        Text(
                          '今夜7/7 19:00〜 Youtube生配信',
                          style: TextStyle(
                            fontSize: 15,
                            color: Colors.white,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                        Text(
                          '「カルピス"水玉通信"」によるプロモーション',
                          style: TextStyle(
                            fontSize: 13,
                            color: Colors.white,
                          ),
                        ),
                      ],
                    ),
                  )
                ],
              ),
              const Divider(
                thickness: 0.09,
                color: Colors.grey,
                height: 10,
              ),
              Container(
                margin: const EdgeInsets.only(top: 10, bottom: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            'かずみん結婚',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '日本のトレンド',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.grey,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text.rich(TextSpan(children: [
                            TextSpan(
                                text: 'トレンドトピック:',
                                style: TextStyle(color: Colors.grey)),
                            TextSpan(
                                text: '都知事選の投票、ふくらP',
                                style: TextStyle(color: Colors.white)),
                          ])),
                        ),
                      ],
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(
                        Icons.more_horiz,
                        color: Colors.grey,
                        size: 20,
                      ),
                    ),
                  ],
                ),
              ),
              const Divider(
                thickness: 0.09,
                color: Colors.grey,
                height: 19,
              ),
              Container(
                margin: const EdgeInsets.only(top: 10, bottom: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '京セラドーム',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '2,679 posts',
                            style: TextStyle(
                              color: Colors.grey,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '人気の画像・トレンド',
                            style: TextStyle(
                              color: Colors.grey,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ],
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(
                        Icons.more_horiz,
                        color: Colors.grey,
                        size: 20,
                      ),
                    ),
                  ],
                ),
              ),
              const Divider(
                thickness: 0.09,
                color: Colors.grey,
                height: 19,
              ),
              Container(
                margin: const EdgeInsets.only(top: 10, bottom: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '#京都大作戦2024',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '人気の画像・トレンド',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.grey,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text.rich(TextSpan(children: [
                            TextSpan(
                                text: 'トレンドトピック:',
                                style: TextStyle(color: Colors.grey)),
                            TextSpan(
                                text: 'ENTH',
                                style: TextStyle(color: Colors.white)),
                          ])),
                        ),
                      ],
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(
                        Icons.more_horiz,
                        color: Colors.grey,
                        size: 20,
                      ),
                    ),
                  ],
                ),
              ),
              const Divider(
                thickness: 0.09,
                color: Colors.grey,
                height: 19,
              ),
              Container(
                margin: const EdgeInsets.only(top: 10, bottom: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '#ドッカンバトル',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '6,248 posts',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.grey,
                            ),
                          ),
                        ),
                        Container(
                          margin: const EdgeInsets.only(
                              left: 10, top: 2, bottom: 2),
                          child: const Text(
                            '日本のトレンド',
                            style: TextStyle(
                              color: Colors.grey,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ],
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: const Icon(
                        Icons.more_horiz,
                        color: Colors.grey,
                        size: 20,
                      ),
                    ),
                  ],
                ),
              ),
              const Divider(
                thickness: 0.09,
                color: Colors.grey,
                height: 19,
              ),
            ],
          ),
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat,
        floatingActionButton: FloatingActionButton(
          backgroundColor: Colors.blue,
          shape: CircleBorder(),
          onPressed: () {},
          child: Icon(
            Icons.add,
            color: Colors.white,
          ),
        ),
        bottomNavigationBar: Container(
          height: 90,
          decoration: BoxDecoration(
            border: Border(
              top: BorderSide(
                color: Colors.grey,
                width: 0.2,
              ),
            ),
          ),
          child: BottomNavigationBar(
            showSelectedLabels: false,
            showUnselectedLabels: false,
            backgroundColor: const Color(0xFF171F29),
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.home_filled,
                    color: Colors.white,
                    size: 20,
                  ),
                  label: ''),
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.search_outlined,
                    color: Colors.white,
                    size: 20,
                  ),
                  label: ''),
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.launch_outlined,
                    color: Colors.white,
                    size: 20,
                  ),
                  label: ''),
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.group_outlined,
                    color: Colors.white,
                    size: 20,
                  ),
                  label: ''),
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.notifications_outlined,
                    color: Colors.white,
                    size: 20,
                  ),
                  label: ''),
              BottomNavigationBarItem(
                  icon: Icon(
                    Icons.mail_outline,
                    color: Colors.white,
                    size: 20,
                  ),
                  label: ''),
            ],
            currentIndex: 0,
            fixedColor: Colors.white,
            type: BottomNavigationBarType.fixed,
          ),
        ),
      ),
    );
  }
}

これを機にもっとウィジェットについてどんどん知って行こうかと。

Discussion