import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: StorePage(),
);
}
}
class StorePage extends StatelessWidget {
StorePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
);
}
}
일반적인 앱은
return Scaffold();
부터 시작한다
Row 가로
Column 세로
Listview 세로인데 스크롤가능해서 많이 넣게
Container = html,css의 div같은거
자식이 없으면 최대한 크게 잡히고
자식이 있으면 자식 크기에 맞게 조정됨
무조건 부모 제약조건에 따라서 자식 따라감
부모의 Constrain
constraints: BoxConstraints(
minWidth: double.infinity,
maxWidth: double.infinity,,
minHeight: double.infinity,
maxHeight: double.infinity,
)
때문에 자식 크기 변경이 안되면
자식을 다른 걸로 감싸자 (Align이 좋음 배치하는 위치도 정할 수 있고)
Constraints go down. Sizes go up. Parent sets position.
제약사항(constraints) 범위를 결정하는 것은 부모 위젯
크기를 결정하는 것은 자식 위젯
자식의 위치를 결정하는 것은 부모 위젯
위 내용을 알아야 플러터의 레이아웃 컨셉을 이해할 수 있으며 더 자세히 설명하면 아래와 같다.
모든 위젯은 부모로부터 자신의 제약사항을 내려 받는다. 이 때 제약사항은 네 개의 실수(double)이며 최소 너비, 최대 너비, 최소 높이, 최대 높이이다.
부모 위젯은 자식 위젯들에게 자신들의 제약사항을 알려준다. 이 때 제약사항이 자식마다 다를 수 있다. 부모 위젯은 각각의 자식 위젯이 필요로 하는 크기를 구한다.
부모 위젯은 각각의 자식 위젯의 위치를 결정한다.
부모 위젯은 자신의 크기를 결정하고 또 위로 자신의 부모 위젯에 자신의 크기를 알려준다. (물론 자신의 제약사항을 벗어날 수는 없다.)
const로 만들면 상태가 똑같음
상태가 다르면 const로 만들면 안된다
모든 위젯은 SizedBox로 감싸서 width, height를 주면 크기를 지정할 수 있다
tabbar
SingleTickerProviderStateMixin 를 with한다(컴퍼지션)
initState 오버라이드 (처음 실행때만 1번 실행됨. 초기화역할)
클래스 필드로 TabController? _tabController; 추가하고 인잇스테이트때 만들어지게
import 'package:flutter/material.dart';
class ProfileTab extends StatefulWidget {
_ProfileTabState createState() => _ProfileTabState();
}
class _ProfileTabState extends State<ProfileTab>
with SingleTickerProviderStateMixin {
TabController? _tabController; // 탭바와 탭바뷰를 연결함
void initState() {
// 처음에 오브젝트 초기화 메서드
super.initState();
_tabController = TabController(length: 3, vsync: this);
print("initState초기화됨");
}
Widget build(BuildContext context) {
print("빌드다시됨");
return Column(
children: [
TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 5, crossAxisSpacing: 5, crossAxisCount: 3),
itemCount: 50,
itemBuilder: (context, index) => Image.network(
"https://picsum.photos/id/${index + 50}/200/200"),
),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 5, mainAxisSpacing: 5),
children: [
Container(color: Colors.green),
],
),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 5, mainAxisSpacing: 5),
children: [
Container(color: Colors.red),
],
),
],
),
),
],
);
}
}
GridView
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, mainAxisSpacing: 1, crossAxisSpacing: 1),
itemCount: 22,
itemBuilder: (context, index) => Image.network(
"https://picsum.photos/id/${index+1}/200/200"),
)
네이스티스크롤뷰
전체가 리스트인데 그 안에 리스트가 또 있을때 사용
NestedScrollView
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverList(
delegate: SliverChildListDelegate([
ProfileHeader(),
ProfileCountInfo(),
ProfileButtons(),
]))
];
},
body: Expanded(child: ProfileTab()), // 리스트(스크롤있는거) 바디에 넣기
)
자주 쓰는 거나 앱 기본 디자인은 테마
context=위치 정보
화면 이동
https://jong99.tistory.com/167
Navigator 스택에 새 페이지를 push하면 해당 페이지로 이동이 되고, pop하면 이전 페이지로 이동하는 형태이다. Pop하다가 더이상 뺄 페이지가 없으면 앱이 종료된다.
initialRoute: "/login", // 앱 처음 시작화면
routes: {
"/login": (context) => LoginPage(),
"/home": (context) => HomePage(),
},
MyApp에 routes 해두고
Form(
child: Column(
children: [
CustomTextFormField(text: "Email"),
CustomTextFormField(text: "Password", isPassword: true),
SizedBox(height: large_gap),
TextButton(
onPressed: () {
Navigator.pushNamed(context, "/home"); // 푸쉬네임하면 화면 두장이 겹침
},
child: Text("Login"),
)
],
),
)