😎
やってみた01 Flutter 模写 X(Twitter)編
概要
ウィジェットツリーについて知識を深めるために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