Flutter.완성본
2주차 과제 완성본(shazam)
도슬
2023. 7. 1. 00:59
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
//생성자 선언. : '이 클래스를 이렇게 생성하겠다' 라고 지정
//이 생성자에서는 key값을 파라미터로 받아
//MyApp 클래스가 상속받는 StatlessWidget 클래스에 key를 전달해줄 수 있다
//선언하지 않으면 기본 생성자만 쓰입니다. MyApp()으로 선언해서 객체를 생성할 수 있다.
//대신 key와 같은 인자를 선언하지 않았기 때문에 super 생성자에 인자를 넘겨줄 수 없다.
@override
Widget build(BuildContext context) {
return MaterialApp(
//1. 앱의 시작은 MaterialApp으로 시작
//그 다음 home에다가 띄워줄 HomePage위젯을 작성해서 불러오자.
home: HomePage(),
);
}
}
//2. HomePage 클래스를 StatefulWidget으로 생성
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(
//3. DefaultTabController위젯으로 반환하도록 만들자.
//DefaultTabController를 이용해서 화면을 옆으로 넘길 수 있는 바를 구현가능
//(그냥 TabBar만 사용해서 에러가 날 경우에 SizedBox를 이용)
initialIndex: 1,
length: 3, //3종류의 탭이므로 length를 3으로 줬다.
child: Scaffold(
body: Stack(
children: [
TabBarView(
//3. TabBarView를 이용해 3종류의 탭을 만들었다.
children: [
FirstTab(),
SecondTab(),
ThirdTab(),
//3종류의 탭 각각 밑에 내용이없으면 오류가남
],
),
SafeArea(
//SafeArea : 폰에따라 카메라, 버튼의 위치가 제각각이라 기종에 따라
//내용이 가려지거나 사라지는 UI가 있을 수 있다.
//디자인한 UI가 제대로 보여지기 위해서 사용한다.
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 16),
child: Column(
children: [
Container(
alignment: Alignment.topCenter,
child: TabPage(),
),
],
),
),
),
],
),
));
}
}
class TabPage extends StatefulWidget {
const TabPage({super.key});
//4. 'TabPage'를 stateful 위젯으로 생성
@override
State<TabPage> createState() => _TabPageState();
}
class _TabPageState extends State<TabPage> {
@override
Widget build(BuildContext context) {
return TabPageSelector(
color: DefaultTabController.of(context).index == 1
? Colors.blue[300]
: Colors.blue[400],
//조건? 반환값1 : 반환값2
//: 조건이 참일때 반환값1, 조건이 거짓일때 반환값2
selectedColor: DefaultTabController.of(context).index == 1
? Colors.white
: Colors.blue,
indicatorSize: 8,
);
}
}
//첫번쨰 탭
class FirstTab extends StatelessWidget {
const FirstTab({Key? key}) : super(key: key);
//1. TabBarView를 이용해 FirstTab만 만들고 FirstTab을 StatelessWidget으로 생성했다.
//위에서 TabBarView로 3종류의 탭 모두 선언했다면 오류가난다..
@override
Widget build(BuildContext context) {
const songs = [
{
'title': '가을밤에 든 생각',
'artist': '잔나비',
},
{
'title': '가을밤에 든 생각',
'artist': '잔나비',
},
{
'title': '가을밤에 든 생각',
'artist': '잔나비',
},
{
'title': '가을밤에 든 생각',
'artist': '잔나비',
},
{
'title': '가을밤에 든 생각',
'artist': '잔나비',
},
{
'title': '가을밤에 든 생각',
'artist': '잔나비',
},
];
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
//7. 칼럼 속 내부를 crossAxisAlignment: CrossAxisAlignment.start를 이용해
//좌로 밀착 시켰다.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
//8. 첫줄 'settings'아이콘과 '라이브러리'텍스트를
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
//crossAxisAlignment: CrossAxisAlignment.start,
//를 이용해서 가로로 떨어뜨리고 세로로 위로 밀착시켰다.
children: [
Padding(
//2. 아이콘과 텍스트를 Row를 이용해 가로로 배치
padding: const EdgeInsets.symmetric(vertical: 4),
child: Icon(Icons.settings),
),
Text(
"라이브러리",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Icon(null),
//9. 현재 8번에서 사용한 spaceBetween때문에 세팅아이콘과 라이브러리 글자가
//양쪽끝에 밀착해 있다. 이때 Icon(null)을 사용해서 라이브러리 뒤에
//가상의 아이콘을 배치해서 '라이브러리 텍스트'를 중간으로 오게한다.
//정리하면 우측 빈공간에 가상의 아이콘이 있는 상황이다.
]),
SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
//3. 아이콘+텍스트 배치
children: [
ImageIcon(
size: 18,
),
SizedBox(width: 12),
Text(
"Shazam",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
Divider(), //Divider를 이용해 선을 그어서 구분지어주었다.
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
//4. 아이콘+텍스트 배치
children: [
Icon(Icons.person_rounded),
SizedBox(width: 8),
Text(
"아티스트",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
Divider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
//5. 아이콘+텍스트 배치
children: [
Icon(Icons.music_note),
SizedBox(width: 8),
Text(
"회원님을 위한 재생목록",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
SizedBox(height: 16),
Text(
//6. 텍스트 배치
"최근 Shazam",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 16),
Expanded(
child: GridView.builder(
//GridView.builder로 Grid레이아웃 만들기
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
//행에 보여줄 Item 갯수
childAspectRatio: 3 / 5,
//가로 : 세로 의 비율
),
itemCount: songs.length,
itemBuilder: (context, index) {
var song = songs[index];
String imageUrl = song['imageUrl']!;
String title = song['title']!;
String artist = song['artist']!;
index % 2 == 0;
return Container(
margin: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(8),
),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
blurRadius: 1,
spreadRadius: 1,
),
],
),
child: Column(children: [
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
child: Image.network(
imageUrl,
fit: BoxFit.cover,
height: MediaQuery.of(context).size.width *
0.5 *
5 /
3 *
0.55,
),
),
Expanded(
child: Container(
padding: const EdgeInsets.all(8),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
artist,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
Image.network(
width: 60,
),
SizedBox(height: 5),
],
),
),
)
]),
);
},
)),
])));
}
}
//두번쨰 탭
class SecondTab extends StatelessWidget {
const SecondTab({Key? key}) : super(key: key); //SecondTab을 Stateless위젯으로 생성
@override
Widget build(BuildContext context) {
return Container(
//배경1. 전체 박스를 컨테이너로 묶음
decoration: BoxDecoration(
gradient: LinearGradient(
//배경2. 배경에 그라디언트 효과를 주는 컴포넌트. 2가지 이상의 색상으로 그라디언트 효과를 나타낼 수 있다.
begin: Alignment.topCenter, //탑센터부터
end: Alignment.bottomCenter, //바텀센터까지
colors: [
Colors.blue[300]!,
Colors.blue[900]!
], //배경3. 블루300~ 블루900 점점 진해진다.
)),
child: SafeArea(
//7. SafeArea를 이용. 여백을 확보해서 가려지는부분 없애줌.
child: Column(
//6. 전체를 Cloumn으로 묶는다.
children: [
Padding(
// 5. 라이브러리와 차트 위치조정을 위해 패딩으로 묶어줌
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
//3. 라이브러리와 차트를 가로배치하기위해서 Row 사용
children: [
GestureDetector(
// 2. 라이브러리 아이콘+텍스트를 버튼으로 만들어줌
onTap: () {
DefaultTabController.of(context).animateTo(0);
},
child: Column(
//1. 라이브러리 아이콘+텍스트를 만들어줌
children: [
Icon(
Icons.person,
color: Colors.white,
),
Text(
"라이브러리",
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
)
],
),
),
Spacer(), //Spacer()은 위젯과 위젯사이의 간격 조절. (예) Spacer(flex: 2)
GestureDetector(
// 4. 1~3번과정과 동일하게 차트의 아이콘+텍스트를 만들어줌
onTap: () {
DefaultTabController.of(context).animateTo(2);
},
child: Column(
children: [
Icon(
Icons.show_chart,
color: Colors.white,
),
Text(
"차트",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
)
],
),
),
],
),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
// MediaQuery클래스의 of(context)를 사용하면
//현재 화면의 MedaQueryData가 변경될때마다 위젯이 자동으로 리빌딩 된다.
//빌드 위젯 내에 위치하여야한다. 그렇지 않을경우 null값을 반환한다.
// MediaQuery.of(context).size 앱 화면 크기
// MediaQuery.of(context).size.height 앱 화면 높이
// MediaQuery.of(context).size.width 앱 화면 넓이
// MediaQuery.of(context).devicePixelRatio 앱 화면 비율
Text(
//8. 텍스트 만들기
"Shazam하려면 탭하세요",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.06),
Container(
//9. 시야를 좀 크게보자. 순서대로 배경과 이미지불러오기
alignment: Alignment.center, // Alignment: 정렬 , 즉 중앙정렬
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue[300],
shape: BoxShape.circle,
),
child: Image.network(
color: Colors.white,
width: 130,
height: 130,
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.12,
),
Container(
//10. 9번과 동일하게 작성
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.blue[400],
shape: BoxShape.circle,
),
child: Icon(
Icons.search,
color: Colors.white,
size: 30,
),
),
],
),
),
);
}
}
//세번째 탭
class ThirdTab extends StatelessWidget {
const ThirdTab({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//1. 소환될 데이터 작성
const chartData = {
'korea': [
{
'name': 'Dynamite',
'artist': 'BTS',
},
{
'name': 'Dynamite',
'artist': 'BTS',
},
{
'name': 'Dynamite',
'artist': 'BTS',
},
],
'global': [
{
'name': 'Dynamite',
'artist': 'BTS',
},
{
'name': 'Dynamite',
'artist': 'BTS',
},
{
'name': 'Dynamite',
'artist': 'BTS',
},
],
'newyork': [
{
'name': 'Dynamite',
'artist': 'BTS',
},
{
'name': 'Dynamite',
'artist': 'BTS',
},
{
'name': 'Dynamite',
'artist': 'BTS',
},
],
};
return SafeArea(
//2. 크게보고 SafeArea -> Column -> Text 까지 작성
child: Column(
children: [
Text(
"차트",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 20,
),
Expanded(
//8. Expanded를 이용해 오버플로우를 없애줌
child: ListView(
//7. ListView를 이용해 스크롤 만들어줌
children: [
Stack(
//5. Stack으로 컨테이너와 패딩을 묶고 배경 설정
alignment: Alignment.center,
children: [
Container(
width: double.infinity,
height: 180,
color: Colors.purple[900],
),
Column(
children: [
Container(
//3. 컨테이너로 묶고 엘레베이터버튼과 텍스트를 만듬.
width: MediaQuery.of(context).size.width * 0.8,
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.white),
),
child: Text(
"국가 및 도시별 차트",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.purple[900],
),
),
),
),
Padding(
//4. 패딩으로 묶고 텍스트 작성
padding: const EdgeInsets.all(8),
child: Text(
"전 세계",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
)
],
),
],
),
Divider(
//6. 선긋고 동일하게 작성
color: Colors.grey[400],
thickness: 10,
height: 10,
),
Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Text(
"대한민국 차트",
style: TextStyle(
fontSize: 16,
),
),
Spacer(),
Text(
"모두 보기",
style: TextStyle(color: Colors.blue),
),
],
),
),
Row(
children: [
...chartData['korea']!.map((song) {
String imageUrl = song['imageUrl']!;
String name = song['name']!;
String artist = song['artist']!;
// 솔직히 불러오는건 잘 모르겠다.. 눈에만 익혀보자.
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
imageUrl,
width: MediaQuery.of(context).size.width * 0.29,
),
Text(
name,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(artist),
],
),
);
}),
],
),
],
),
Divider(
color: Colors.grey[400],
thickness: 10,
height: 10,
),
Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Text(
"글로벌 차트",
style: TextStyle(
fontSize: 16,
),
),
Spacer(),
Text(
"모두 보기",
style: TextStyle(color: Colors.blue),
),
],
),
),
Row(
children: [
...chartData['global']!.map((song) {
String imageUrl = song['imageUrl']!;
String name = song['name']!;
String artist = song['artist']!;
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
imageUrl,
width: MediaQuery.of(context).size.width * 0.29,
),
Text(
name,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(artist),
],
),
);
}),
],
),
],
),
Divider(
color: Colors.grey[400],
thickness: 10,
height: 10,
),
Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Text(
"뉴욕 차트",
style: TextStyle(
fontSize: 16,
),
),
Spacer(),
Text(
"모두 보기",
style: TextStyle(color: Colors.blue),
),
],
),
),
Row(
children: [
...chartData['newyork']!.map((song) {
String imageUrl = song['imageUrl']!;
String name = song['name']!;
String artist = song['artist']!;
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
imageUrl,
width: MediaQuery.of(context).size.width * 0.29,
),
Text(
name,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(artist),
],
),
);
}),
],
),
],
),
],
),
),
],
));
}
}