skeleton_loader | Flutter Package
skeleton_loader는 likes도 적고 조금 마이너한 느낌인데,
Shimmer 패키지를 사용해서 만들어진 간단한 위젯이다.
사용하기 쉽고 몇몇 장점이 있어서 간단히 소개해보고자 한다.
1) 신규 플랫폼 런칭시 더 좋은 UI 제공
2) CircularProgressIndicator보다 더 나은 UX 제공
3) Front-End 개발자로써 라이브러리 학습
우선 플러터에 올라온 공식문서를 확인해보고, 내 패키지에 skeleton_loader를 설치했다.
패키지 안에는 2개의 위젯이 들어있다.
1. SkeletonLoader (콘텐츠의 갯수 등 정해져 있을 때)
2. SekeletonGridLoader
(그리드형, 콘텐츠의 갯수 등이 정해져 있지 않을 때)
둘 다 어렵지 않으니 둘 다 만들어 보도록 하자.
import 'package:flutter/material.dart';
import 'package:skeleton_loader/skeleton_loader.dart';
enum States { loading, loaded }
class SkeletonPracticeView extends StatefulWidget {
const SkeletonPracticeView({Key? key}) : super(key: key);
State<SkeletonPracticeView> createState() => _SkeletonPracticeViewState();
}
class _SkeletonPracticeViewState extends State<SkeletonPracticeView> {
final String title = 'Skeleton Loader Demo';
States screenState = States.loaded;
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
appBar: AppBar(
// 앱바 설정
),
body: TabBarView(
children: [
_columnTap(), // 스켈레톤 로더
_gridTap(), // 그리드형 스켈레톤 로더
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
screenState = States.loading;
});
Future.delayed(const Duration(seconds: 3), () {
setState(() {
screenState = States.loaded;
});
});
},
child: const Icon(Icons.refresh_rounded),
),
));
}
...
...
...
SkeletonLoader 위젯이 받는 builder는 Widget 타입이다.
저기에 뭘 넣는지에 따라 로딩 될 때 보여지는 위젯이 다르다.
Widget _columnTap() {
return screenState == States.loading
? SkeletonLoader(
baseColor: const Color.fromRGBO(240, 240, 240, 1),
highlightColor: Colors.amber,
period: const Duration(seconds: 1),
builder: _columnItemArray(),
)
: _columnItemArray();
}
Widget _columnItemArray() {
return Column(
children: List.generate(3, (index) => _columnItem()),
);
}
Widget _columnItem() {
return Center(
child: Column(children: [
Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
// Container(color: Colors.blue, child: Text("data")),
Container(
width: 100,
height: 100,
color: Colors.blue.shade100,
margin: const EdgeInsets.all(10),
child: const Icon(
Icons.support_agent_sharp,
size: 100,
)),
SizedBox(
width: 220,
height: 120,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
width: 200,
height: 30,
color: Colors.blue.shade100,
child: const Text('Name'),
),
Container(
width: 200,
height: 30,
color: Colors.blue.shade100,
),
],
),
)
],
),
)
]),
);
}
여기서 짚고 넘어갈 포인트는,
SkeletonLoader는 내 자식 위젯중 가장 상위에 있는 '색상'을 가진 놈을 보여주고 그 하위 자식 위젯들은 모두 가려서 보여준다.
Shimmer.fromColors 를 사용하고 있기 때문이다.
지금 _columnItem은 Container에 색을 칠하고 그 안에 아이콘과 텍스트를 집어넣고 있다.
그리고 Container의 부모 위젯들은 모두 색이 없다.
그러므로 Container가 로딩이 되는 것이다.
그러면 Container의 색상을 없애보자.
Container(
width: 100,
height: 100,
// color: Colors.blue.shade100,
margin: const EdgeInsets.all(10),
child: const Icon(
Icons.support_agent_sharp,
size: 100,
),
),
(지금은 9개로 서로 맞춰두었다)
Widget _gridTap() {
return screenState == States.loading
? SkeletonGridLoader(
baseColor: const Color.fromRGBO(240, 240, 240, 1),
highlightColor: Colors.amber,
period: const Duration(seconds: 1),
builder: _gridItem(), // 단일 아이템
items: 9, // 갯수
itemsPerRow: 3, //1 줄에 배치하는 갯수
childAspectRatio: 1,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
)
: _gridItemGrid();
}
대신 items라던지, itemsPerRow같은 용어가 GridView.builder에서는 itemCount, crossAxisCount로 사용했을 것이다.
Widget _gridItemGrid() {
return GridView.builder(
shrinkWrap: true,
itemCount: 9,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (BuildContext context, int index) {
// return Text(index.toString());
return _gridItem();
},
);
}
Widget _gridItem() {
return Container(
width: 100,
height: 100,
color: Colors.blue.shade100,
margin: const EdgeInsets.all(10),
child: const Icon(
Icons.support_agent_sharp,
size: 100,
));
}
이 위젯의 경우 그리드 타입으로 데이터를 불러와야 할 때,
특히 서버 통신으로 데이터를 불러온다고 하면 총 갯수가 얼마인지 fetching 단계에서는 알 수 없다.
그러므로 SkeletonGridLoader가 적정 수를 보여주도록 세팅하여서 적절한 로딩 UI를 구성할 수 있다.
이를 이용해서 단지 원이 빙글빙글 도는 것 보다 조금 더 나은 사용자 경험을 줘 보도록 하자.