Created
June 28, 2023 15:45
-
-
Save jinyongp/252f87e790119799f8be13d1c7875a6d to your computer and use it in GitHub Desktop.
Flutter Shazam App Clone
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
title: 'Shazam', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: const HomePage(), | |
); | |
} | |
} | |
class HomePage extends StatefulWidget { | |
const HomePage({Key? key}) : super(key: key); | |
@override | |
State<HomePage> createState() => _HomePageState(); | |
} | |
class _HomePageState extends State<HomePage> { | |
@override | |
Widget build(BuildContext context) { | |
return DefaultTabController( | |
initialIndex: 1, | |
length: 3, | |
child: Builder(builder: (context) { | |
DefaultTabController.of(context).addListener(() { | |
setState(() {}); | |
}); | |
return Scaffold( | |
body: Stack( | |
children: [ | |
const TabBarView( | |
children: [ | |
FirstTab(), | |
SecondTab(), | |
ThirdTab(), | |
], | |
), | |
tabIndicator(context), | |
], | |
), | |
); | |
}), | |
); | |
} | |
Widget tabIndicator(BuildContext context) { | |
return SafeArea( | |
child: Padding( | |
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), | |
child: Column( | |
children: [ | |
Container( | |
alignment: Alignment.topCenter, | |
child: TabPageSelector( | |
color: DefaultTabController.of(context).index == 1 | |
? Colors.blue[300] | |
: Colors.grey[400], | |
selectedColor: DefaultTabController.of(context).index == 1 | |
? Colors.white | |
: Colors.blue, | |
indicatorSize: 8, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class FirstTab extends StatelessWidget { | |
const FirstTab({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
const menus = [ | |
{'icon': Icons.ac_unit, 'title': 'Shazam'}, | |
{'icon': Icons.person, 'title': '아티스트'}, | |
{'icon': Icons.music_note, 'title': '회원님을 위한 재생 목록'}, | |
]; | |
const songs = [ | |
{ | |
'imageUrl': 'https://i.ytimg.com/vi/jAO0KXRdz_4/hqdefault.jpg', | |
'title': '가을밤에 든 생각', | |
'artist': '잔나비', | |
}, | |
{ | |
'imageUrl': 'https://i.ytimg.com/vi/jAO0KXRdz_4/hqdefault.jpg', | |
'title': '가을밤에 든 생각', | |
'artist': '잔나비', | |
}, | |
{ | |
'imageUrl': 'https://i.ytimg.com/vi/jAO0KXRdz_4/hqdefault.jpg', | |
'title': '가을밤에 든 생각', | |
'artist': '잔나비', | |
}, | |
{ | |
'imageUrl': 'https://i.ytimg.com/vi/jAO0KXRdz_4/hqdefault.jpg', | |
'title': '가을밤에 든 생각', | |
'artist': '잔나비', | |
}, | |
{ | |
'imageUrl': 'https://i.ytimg.com/vi/jAO0KXRdz_4/hqdefault.jpg', | |
'title': '가을밤에 든 생각', | |
'artist': '잔나비', | |
}, | |
{ | |
'imageUrl': 'https://i.ytimg.com/vi/jAO0KXRdz_4/hqdefault.jpg', | |
'title': '가을밤에 든 생각', | |
'artist': '잔나비', | |
}, | |
]; | |
return SafeArea( | |
child: Padding( | |
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 16), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
SizedBox( | |
width: 24, | |
height: 24, | |
child: IconButton( | |
padding: EdgeInsets.zero, | |
onPressed: () {}, | |
icon: const Icon( | |
Icons.settings, | |
)), | |
), | |
const Text("라이브러리", | |
style: TextStyle( | |
color: Colors.black, | |
fontWeight: FontWeight.w600, | |
fontSize: 16)), | |
const SizedBox(width: 24), | |
], | |
), | |
const SizedBox(height: 20), | |
Column( | |
children: menus.map((menu) { | |
return Column( | |
children: [ | |
Row(children: [ | |
Icon(menu['icon'] as IconData), | |
const SizedBox(width: 8), | |
Text(menu['title'] as String, | |
style: const TextStyle( | |
fontSize: 16, fontWeight: FontWeight.w500)) | |
]), | |
menus.last != menu | |
? const Divider(height: 20, thickness: 0.5) | |
: const SizedBox(), | |
], | |
); | |
}).toList(), | |
), | |
const SizedBox(height: 20), | |
const Text("최근 Shazam", | |
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700)), | |
const SizedBox(height: 16), | |
Expanded( | |
child: GridView.builder( | |
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | |
crossAxisCount: 2, | |
crossAxisSpacing: 16, | |
mainAxisSpacing: 16, | |
childAspectRatio: 1 / 1.05, | |
), | |
itemCount: songs.length, | |
itemBuilder: (context, index) { | |
return songListItem(songs[index]); | |
}), | |
) | |
], | |
), | |
)); | |
} | |
Widget songListItem(Map<String, String> song) { | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Card( | |
child: ClipRRect( | |
borderRadius: BorderRadius.circular(8), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Image.network( | |
song['imageUrl'] as String, | |
fit: BoxFit.cover, | |
), | |
Container( | |
padding: const EdgeInsets.all(8.0), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Text(song['title'] as String, | |
style: const TextStyle( | |
fontSize: 16, fontWeight: FontWeight.w600)), | |
const SizedBox(height: 4), | |
Text(song['artist'] as String, | |
style: const TextStyle( | |
fontSize: 12, fontWeight: FontWeight.w400)), | |
], | |
), | |
), | |
], | |
), | |
), | |
), | |
], | |
); | |
} | |
} | |
class SecondTab extends StatelessWidget { | |
const SecondTab({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
decoration: BoxDecoration( | |
gradient: LinearGradient( | |
begin: Alignment.topCenter, | |
end: Alignment.bottomCenter, | |
colors: [Colors.blue.shade200, Colors.blueAccent, Colors.blue.shade900], | |
)), | |
child: Stack( | |
children: [ | |
SafeArea( | |
child: Padding( | |
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), | |
child: Row(children: [ | |
GestureDetector( | |
child: const Column(children: [ | |
Icon(Icons.person, color: Colors.white, size: 28), | |
Text("라이브러리", style: TextStyle(color: Colors.white)) | |
]), | |
onTap: () { | |
DefaultTabController.of(context).animateTo(0); | |
}, | |
), | |
const Expanded(child: SizedBox()), | |
GestureDetector( | |
child: const Column( | |
children: [ | |
Icon(Icons.show_chart_rounded, | |
color: Colors.white, size: 28), | |
Text("차트", style: TextStyle(color: Colors.white)) | |
], | |
), | |
onTap: () { | |
DefaultTabController.of(context).animateTo(2); | |
}, | |
), | |
]), | |
)), | |
Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
const Text("Shazam하려면 탭하세요.", | |
style: TextStyle( | |
color: Colors.white, | |
fontSize: 24, | |
fontWeight: FontWeight.w600)), | |
const SizedBox(height: 50), | |
Row( | |
children: [ | |
Expanded(flex: 1, child: Container()), | |
Expanded( | |
flex: 2, | |
child: ClipRRect( | |
borderRadius: BorderRadius.circular(9999), | |
child: Image.network( | |
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Shazam_icon.svg/800px-Shazam_icon.svg.png", | |
fit: BoxFit.fitWidth, | |
color: Colors.blue.shade300, | |
colorBlendMode: BlendMode.lighten, | |
), | |
), | |
), | |
Expanded(flex: 1, child: Container()), | |
], | |
), | |
const SizedBox(height: 100), | |
Container( | |
width: 40, | |
height: 40, | |
decoration: BoxDecoration( | |
shape: BoxShape.circle, | |
color: Colors.blue.shade300, | |
), | |
child: | |
const Icon(Icons.search, color: Colors.white, size: 28)), | |
], | |
) | |
], | |
), | |
); | |
} | |
} | |
class ThirdTab extends StatelessWidget { | |
const ThirdTab({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
const chartData = { | |
'korea': [ | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
], | |
'global': [ | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
], | |
'newyork': [ | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
{ | |
'imageUrl': 'https://i.ibb.co/xf2HpfG/dynamite.jpg', | |
'name': 'Dynamite', | |
'artist': 'BTS', | |
}, | |
], | |
}; | |
return SafeArea( | |
child: Padding( | |
padding: const EdgeInsets.symmetric(vertical: 2), | |
child: Column( | |
children: [ | |
const Center( | |
child: Text("차트", | |
style: TextStyle( | |
color: Colors.black, | |
fontWeight: FontWeight.w600, | |
fontSize: 16)), | |
), | |
const SizedBox( | |
height: 20, | |
), | |
Expanded( | |
child: ListView( | |
children: [ | |
header(), | |
Divider(height: 10, thickness: 10, color: Colors.grey.shade300), | |
listItem(chartData['korea']!, "대한민국 차트"), | |
Divider(height: 10, thickness: 10, color: Colors.grey.shade300), | |
listItem(chartData['global']!, "글로벌 차트"), | |
Divider(height: 10, thickness: 10, color: Colors.grey.shade300), | |
listItem(chartData['newyork']!, "뉴욕 차트"), | |
], | |
), | |
) | |
], | |
), | |
)); | |
} | |
Widget header() { | |
return Container( | |
color: Colors.deepPurple, | |
width: double.infinity, | |
child: Padding( | |
padding: const EdgeInsets.symmetric(vertical: 50.0, horizontal: 30), | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
ClipRRect( | |
borderRadius: BorderRadius.circular(4), | |
child: Container( | |
color: Colors.white, | |
child: const ButtonBar( | |
alignment: MainAxisAlignment.center, | |
buttonPadding: EdgeInsets.all(10), | |
children: [ | |
Text("국가 및 도시별 차트", | |
style: TextStyle( | |
color: Colors.deepPurple, | |
fontWeight: FontWeight.w600, | |
fontSize: 16)), | |
], | |
), | |
), | |
), | |
const SizedBox(height: 20), | |
const Text("전 세계", | |
style: TextStyle( | |
color: Colors.white, | |
fontWeight: FontWeight.w600, | |
fontSize: 16)), | |
], | |
), | |
), | |
); | |
} | |
Widget listItem(List<Map<String, String>> data, String chartName) { | |
return Padding( | |
padding: const EdgeInsets.symmetric(vertical: 10.0), | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 8.0), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: [ | |
Text(chartName, style: const TextStyle(fontSize: 16)), | |
const Text("모두 보기", | |
style: TextStyle(color: Colors.blue, fontSize: 14)), | |
], | |
), | |
), | |
const SizedBox(height: 8), | |
Row( | |
children: data.map((datum) { | |
return Expanded( | |
child: Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 8), | |
child: chartItem(datum), | |
)); | |
}).toList(), | |
) | |
], | |
), | |
); | |
} | |
Widget chartItem(Map<String, String> datum) { | |
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ | |
Image.network( | |
datum['imageUrl'] as String, | |
), | |
Text(datum['name'] as String, | |
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), | |
Text(datum['artist'] as String), | |
]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment