import 'package:flutter/material.dart';
const rainbowColors = [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.indigo,
Colors.purple,
];
import 'package:flutter/material.dart';
class MainLayout extends StatelessWidget {
final String title;
final Widget body;
const MainLayout({
required this.title,
required this.body,
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: body,
);
}
}
SingleChildScrollView(
child: Column(
children: rainbowColors
.map(
(e) => renderContainer(color: e),
)
.toList(),
),
);
SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: [
renderContainer(color: Colors.black),
],
),
)
SingleChildScrollView(
clipBehavior: Clip.none,
physics: BouncingScrollPhysics(),
child: Column(
children: [
renderContainer(color: Colors.black),
],
),
)
SingleChildScrollView(
physics: BouncingScrollPhysics(),
// ClampingScrollPhySics() - 안드로이드 스타일
child: Column(
children: rainbowColors
.map(
(e) => renderContainer(color: e),
)
.toList(),
),
)
class SingleChildScrollViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
SingleChildScrollViewScreen({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MainLayout(
title: 'SingleChildScrollView',
body: SingleChildScrollView(
child: Column(
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e,
),
)
.toList(),
),
),
);
}
SingleChildScrollViewd
에서는 child: Column(children:[])
을 사용해야 했지만 ListView
에서는 children
에 바로 값을 넣을 수 있다.ListView(
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e,
),
)
.toList(),
);
ListView.builder
의 경우 화면에 출력된 위젯들만 반환된다.ListView.builder(
itemBuilder: (context, index) {
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index,
);
},
itemCount: 100,
);
ListView.separated(
itemBuilder: (context, index) {
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index,
);
},
separatorBuilder: (context, index) {
return renderContainer(
color: Colors.black, index: index, height: 100);
},
itemCount: 100,
)
crossAxisCount
의 숫자에 따라 좌우에 배치되는 수가 달라진다.GridView.count(
crossAxisCount: 3,
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e,
),
)
.toList(),
)
crossAxisSpacing
, mainAxisSpacing
을 사용해서 위젯들 간의 간격을 줄 수 있다.GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12.0,
mainAxisSpacing: 12.0,
),
itemBuilder: (context, index) {
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index,
);
},
)
위젯을 옮길 수 있는 ListView
이다.
ListView
에 onReorder
값이 추가 됬는데, onReorder
에는 oldIndex
와 newIndex
를 파라미터를 받는 함수를 사용한다.
이 ListView
는 위젯을 옮겨야 하기 때문에 위젯의 자리가 바뀌면서 기존에 있던 index
가 새로운 index
로 바뀐다.
index
산정 방식에 대해 알아보자. 이 때 조건이 있는데 인덱스를 번호를 정할 때는 인덱스를 옮기기 전에 산정을 해야한다.
[a, b, c]
에서a
를c
뒤로 옮긴다고 가정해보자
a
는c
다음인 인덱스 3번자리로 가게 된다.
이 과정에서oldIndex
는0
이 되고newIndex
는3
이 된다
산정 후 위치는[b, c, a]
로 된다
[a, b, c]
에서c
를a
앞으로 옮긴다고 가정해보자
c
는 0번 인덱스 자리로 가게 된다.
이 과정에서oldIndex
는2
이 되고newIndex
는0
이 된다
산정 후 위치는[c, a, b]
가 된다.실제 위치를 구하게 되면
oldIndex
<newIndex
조건에서newIndex
에서-1
을 하면 실제로 위젯이 이동한 위치이다
oldIndex
>newIndex
조건에서는newIndex
를 그대로 사용하면 된다.
ReorderableListView(
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length], index: e),
)
.toList(),
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = numbers.removeAt(oldIndex);
numbers.insert(newIndex, item);
});
},
)
ReorderableListView
에는 꼭 key
를 넣어줘야 한다.Container
를 사용하고 있다. 사람이 봤을 때는 색깔별로 다른 Container
인 것을 인지하지만 시스템적으로 ReorderableListView
인지하지 못한다. 그래서 이것을 구분해주기 위해 key
값을 넣어줘야 한다. 그리고 key
값은 절대로 겹치지 않는 값을 넣어줘야 하기 때문에 index
를 넣어준다.Widget renderContainer({
required Color color,
required int index,
double? height,
}) {
print(index);
return Container(
key: Key(index.toString()), // key
height: height ?? 300,
color: color,
child: Center(
child: Text(
index.toString(),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 30.0,
),
),
),
);
}
}
ReorderableListView.builder(
itemBuilder: (context, index) {
return renderContainer(
color: rainbowColors[index % rainbowColors.length], index: index);
},
itemCount: 100,
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = numbers.removeAt(oldIndex);
numbers.insert(newIndex, item);
});
},
)
class _ReorderalbeListViewScreenState extends State<ReorderalbeListViewScreen> {
List<int> numbers = List.generate(100, (index) => index);
Widget build(BuildContext context) {
return MainLayout(
title: 'ReorderalbeListViewScreen',
body: ReorderableListView.builder(
itemBuilder: (context, index) {
return renderContainer(
color: rainbowColors[numbers[index] % rainbowColors.length],
index: numbers[index],
);
},
itemCount: numbers.length,
onReorder: (int oldIndex, int newIndex) {
setState(() {
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: 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,
),
),
),
);
}
SliverAppBar(
pinned: true,
title: const Text('CustomScrollViewScreen'),
)
SliverAppBar(
floating: true,
title: const Text('CustomScrollViewScreen'),
)
floating: true
이여야 한다.SliverAppBar(
floating: true,
snap: true,
title: const Text('CustomScrollViewScreen'),
)
SliverAppBar(
stretch: true,
title: const Text('CustomScrollViewScreen'),
)
SliverAppBar(
expandedHeight: 200,
floating: true,
snap: true,
title: const Text('CustomScrollViewScreen'),
)
SliverAppBar(
expandedHeight: 200,
collapsedHeight: 200,
floating: true,
snap: true,
title: const Text('CustomScrollViewScreen'),
)
SliverAppBar(
expandedHeight: 200,
collapsedHeight: 150,
floating: true,
snap: true,
flexibleSpace: FlexibleSpaceBar(
background: Image.asset(
'asset/img/image_1.jpeg',
fit: BoxFit.cover,
),
title: Text("Hello"),
),
title: const Text('CustomScrollViewScreen'),
)
SliverChildListDelegate()
를 사용한다.SliverList(
delegate: SliverChildListDelegate(
numbers
.map((e) => renderContainer(
color: rainbowColors[e % rainbowColors.length], index: e))
.toList(),
),
)
delegate
에 SliverChildBuilderDelegate()
를 사용하면 된다.SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index),
),
)
SliverGrid(
delegate: SliverChildListDelegate(
numbers
.map((e) => renderContainer(
color: rainbowColors[e % rainbowColors.length], index: e))
.toList(),
),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
)
SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) => renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index),
childCount: 100,
),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 150),
)
RefreshIndicator(
onRefresh: () async {
await Future.delayed(
Duration(seconds: 3),
);
},
child: ListView(
children: numbers
.map(
(e) => renderContainer(
color: rainbowColors[e % rainbowColors.length], index: e),
)
.toList(),
),
)