유저가 Item 위치를 스크롤하여 변경할 때
DB의 순서를 함께 수정할 수 있습니다.
class ReorderableListViewScreen extends StatefulWidget {
const ReorderableListViewScreen({Key? key}) : super(key: key);
State<ReorderableListViewScreen> createState() =>
_ReorderableListViewScreenState();
}
class _ReorderableListViewScreenState extends State<ReorderableListViewScreen> {
List<int> numbers = List.generate(100, (index) => index);
Widget build(BuildContext context) {
return MainLayout(
title: 'ReorderableListViewScreen',
body: ReorderableListView.builder(
itemBuilder: (context, index) {
return renderContainer(
//index 를 numbers[index]로 변경해서 색깔과 인덱스로 위치 변경시 바뀜
color: rainbowColors[numbers[index] % rainbowColors.length],
index: numbers[index],
);
},
itemCount: numbers.length,
onReorder: (int oldIndex, int newIndex) {
setState(() {
// oldIndex와 newIndex 모두
// 이동이 되기 전에 산정한다.
//
//
// [red, orange, yellow]
// [0, 1, 2]
//
// red를 yellow 다음으로 옮기고싶다.
// red : 0 oldIndex -> 3 newIndex
// [orange, yellow, red]
//
// [red, orange, yellow]
// yellow를 맨 앞으로 옮기고싶다.
// yellow : 2 oldIndex -> 0 newIndex
// [yellow, red, orange]
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = numbers.removeAt(oldIndex);
numbers.insert(newIndex, item);
});
},
),
);
}
Widget renderDefault() {
return ReorderableListView(
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e,
),
)
.toList(),
onReorder: (int oldIndex, int newIndex) {
setState(() {
// oldIndex와 newIndex 모두
// 이동이 되기 전에 산정한다.
//
//
// [red, orange, yellow]
// [0, 1, 2]
//
// red를 yellow 다음으로 옮기고싶다.
// red : 0 oldIndex -> 3 newIndex
// [orange, yellow, red]
//
// [red, orange, yellow]
// yellow를 맨 앞으로 옮기고싶다.
// yellow : 2 oldIndex -> 0 newIndex
// [yellow, red, orange]
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = numbers.removeAt(oldIndex);
numbers.insert(newIndex, item);
});
},
);
}
Widget renderContainer({
required Color color,
required int index,
double? height,
}) {
print(index);
return Container(
//Key는 서로 다른 Container로 인식
key: Key(index.toString()),
height: height ?? 300,
color: color,
child: Center(
child: Text(
index.toString(),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 30.0,
),
),
),
);
}
}
// ListView.builder 생성자와 유사함.
SliverList renderBuilderSliverList() {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index,
);
},
childCount: 15,
),
);
}
// GridView.builder 와 비슷함
SliverGrid renderSliverGridBuilder() {
return SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index,
);
},
childCount: 30,
),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
),
);
}
SliverAppBar renderSliverAppbar() {
return SliverAppBar(
// 스크롤 했을때 리스트의 중간에도 AppBar가 내려오게 할 수 있음.
floating: true,
// 완전 고정, 헤더가 여러 개라면 위에 쌓인다.
pinned: false,
// 자석 효과
// floating true 에만 사용가능
// false면 중간이 존재
snap: true,
// 맨 위에서 한계 이상으로 스크롤 했을때
// 앱바가 스크롤 한 남는 공간을 차지
stretch: false,
// 앱바 최대 사이즈
expandedHeight: 200,
// 최소 사이즈
collapsedHeight: 150,
flexibleSpace: FlexibleSpaceBar(
background: Image.asset(
'asset/img/image_1.jpeg',
fit: BoxFit.cover,
),
title: Text('FlexibleSpace'),
),
title: Text('CustomScrollViewScreen'),
);
}
class _SliverFixedHeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
final double maxHeight;
final double minHeight;
_SliverFixedHeaderDelegate({
required this.child,
required this.maxHeight,
required this.minHeight,
});
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(
child: child,
);
}
// 최대 높이
double get maxExtent => maxHeight;
// 최소 높이
double get minExtent => minHeight;
// covariant - 해당 클래스를 상속한 클래스도 사용 가능
// ex) bool shouldRebuild(covariant SliverPersistHeaderDelegate oldDelegate) {
// oldDelegate - build가 실행이 됐을때 이전 Delegate
// this - 새로운 delegate
// shouldRebuild - 새로 build를 해야할지 말지 결정. false - build 안함 true - 빌드 다시함
// child, maxHeight, minHeight 값이 바뀔 때마다 Rebuild
bool shouldRebuild(_SliverFixedHeaderDelegate oldDelegate) {
return oldDelegate.minHeight != minHeight ||
oldDelegate.maxHeight != maxHeight ||
oldDelegate.child != child;
}
}
SliverPersistentHeader renderHeader() {
return SliverPersistentHeader(
pinned: true,
delegate: _SliverFixedHeaderDelegate(
child: Container(
color: Colors.black,
child: Center(
child: Text(
'신기하지요',
style: TextStyle(
color: Colors.white,
),
),
),
),
minHeight: 50,
maxHeight: 200,
),
);
}
body: Scrollbar(
child: SingleChildScrollView(
child: Column(
children: numbers.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e,
),
).toList(),
),
),
),
await Future.delayed(Duration(seconds: 3));
3초 만큼 RefreshIndicator가 보여집니다.
이후 서버 요청 코드를 추가할 수 있습니다.
body: RefreshIndicator(
onRefresh: () async {
// 서버 요청
await Future.delayed(Duration(seconds: 3));
},
child: ListView(
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e,
),
)
.toList(),
),
),