상태를 변경하기 위해서 페이지를 하나 만들어서 테스트
class HelloPage extends StatefulWidget {
HelloPage({Key? key}) : super(key: key);
State<HelloPage> createState() => _HelloPageState(); // 자동완성으로 만들어짐
}
class _HelloPageState extends State<HelloPage> {
int num = 1;
Widget build(BuildContext context) {
print("또 실행됨");
return Scaffold(
appBar: AppBar(),
body: Align( // 기본이 가운데 정렬
// alignment: Alignment.topCenter, // 디폴트값
child: Column(
children: [
Text("Hello ${num}", style: TextStyle(fontSize: 50),),
ElevatedButton(
onPressed: (){
setState(() {
num++;
});
print("num : ${num}");
}, child: Text("변경", style: TextStyle(fontSize: 20)))
],
),
),
);
}
}
상태가 변하는 위젯을 만들기 위해서는 StatefulWidget
으로 만든다.
StatelessWidget
는 상태가 변경되었을때 변경감지를 하지못해 rebuild를 하지 못한다.
StatefulWidget
는 setState()
메소드를 가지고 있는데 필드에 지정한 int num = 1;
의 값을 변경하고 있다.
여기서 num
은 Text("Hello ${num}"
에서 사용되어 아래처럼 숫자를 화면에 출력한다.
일반적으로 클래스의 필드는 상태를 의미한다.
setState()
의 내부에서 상태를 변경한다면 (num증가) setState()
는 상태변경을 감지하게 되고 해당 상태를 포함하는 위젯을 rebuild하게 된다.
따라서 변경 버튼을 클릭하게 되면 ElevatedButton
의 onPressed
가 실행되어 setState()
에 의해 상태가 변하게 되는데 콘솔에도 다음과 같이 출력이 되면서 화면의 그림도 rebuild되어 값이 변하게 된다.
클릭하게 되면 num
의 값이 변경되고 rebuild되어서 '또 실행됨'이 출력되고 화면이 변한다.
아래처럼 자동차와 지하철 이미지를 클릭했을때 아래 정보를 변경하고 싶다면 Stateful위젯을 이용한다.
TabBar
는 일반적으로 앱의 다른 화면으로 전환할 수 있는 여러 개의 탭으로 구성된 위젯이다.
탭을 선택했을때 선택된 탭으로 전환하기 위해 TabBarView
, TabController
와 함께 사용하는데 TabController
는 상태를 추적하고 업데이트해주는 기능을 가진다.
class ProfileTap extends StatefulWidget {
const ProfileTap({Key? key}) : super(key: key);
State<ProfileTap> createState() => _ProfileTapState();
}
class _ProfileTapState extends State<ProfileTap>
with SingleTickerProviderStateMixin {
TabController? _tabController;
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
// requied vsync -> TickerProvider
}
initState()
로 상태를 빌드한다.
TabController
는 vsync
를 반드시 필요로 하는데 TabBar
의 애니메이션 동작을 동기화하기 위한 객체를 나타낸다.
즉, 필요로 하는 탭의 수만큼 지정해주면 된다. 이를 위해서 Mixin을 이용한다.
플러터는 애니메이션에 관련된 API를 제공해주는데 이들중 하나가 Ticker
클래스다.
Ticker
클래스는 다수의 애니메이션을 동시에 제어할 수 있다.
플러터에서 애니메이션을 구현할 때는 AnimationController
클래스를 사용한다.
SingleTickerProviderStateMixin
은 AnimationController
클래스의 객체를 생성하고 제어하는 데 사용되는 Mixin이다.
또한 다른 애니메이션과의 충돌을 방지하는 장점이 있다.
Mixin을 통해 현 상태클래스가 TickerProvider
객체에 접근 가능해진다.
TabBarController
는 vsync
프로퍼티를 통해 TickerProvider
타입의 객체를 받게 된다.
이를 통해 탭전환을 하는 애니메이션을 구현하게 된다.
build(BuildContext context) {
return Column(
children: [
TabBar(controller: _tabController, tabs: [
Tab(
icon: Icon(Icons.directions_car),
),
Tab(
icon: Icon(Icons.directions_transit),
),
]),
Expanded(
child: TabBarView(controller: _tabController, children: [
GridView.builder(
itemCount: 42,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 3열 고정
crossAxisSpacing: 10, // 갭 지정
mainAxisSpacing: 10),
itemBuilder: (context, index) {
// print("실행 : ${index+1}");
// return Image.network("http://picsum.photos/id/${index+1}/200/200");
return CachedNetworkImage(
imageUrl: "http://picsum.photos/id/${index + 1}/200/200", // 다운받은 이미지
placeholder: (context, url) => CircularProgressIndicator(), // 다운받는중 (기본이미지)
errorWidget: (context, url, error) => Icon(Icons.error), // 다운실패
);
Image.network("");
}),
Container(
color: Colors.green,
),
Container(
color: Colors.grey,
),
]),
)
],
);
}
}
Widget
Row나 Column은 모두 크기가 있어야 한다.
내부의 요소가 외부보다 커지면 에러가 터지기 때문에 TabBarView
는 남은 영역을 모두 차지하라고 Expanded
위젯으로 감싼다
캐싱을 사용하지 않고 다운로드를 한다면
return Image.network("http://picsum.photos/id/${index+1}/200/200");
뷰홀더에 15장만 다운받게 된다. ( print로 확인)
또한 스크롤이 길어지면 이미 다운받았던 이미지를 날려버려 15장을 초과하는 순서는 버리고 다시 다운받는 행위를 반복한다. 이러한 자원낭비를 최소화하기 위해 캐싱을 이용한다.
캐싱을 사용하기 위해 의존성을 추가한다.
dependencies:
flutter:
sdk: flutter
cached_network_image: ^3.2.3 # 추가
이후 CachedNetworkImage
를 사용할 수 있게 되는데 캐싱을 이용하므로 한번 다운받은 이미지를 다시 다운받지 않아 자원의 낭비를 최소화 한다.