class SingleChildScrollViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
SingleChildScrollViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'SingleChildScrollView',
body: renderSimple()
);
}
// 1. 기본 렌더링법
Widget renderSimple() {
return SingleChildScrollView(
child: Column(
children: rainbowColors.map((e) => renderContainer(color: e)).toList()
),
);
}
Widget renderContainer({required Color color}) {
return Container(
height: 300,
color: color,
);
}
}
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class SingleChildScrollViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
SingleChildScrollViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'SingleChildScrollView',
body: renderAlwaysScroll()
);
}
// 2. 화면을 넘어가지 않더라도 스크롤 되게
Widget renderAlwaysScroll() {
return SingleChildScrollView(
// 기본값
// physics: NeverScrollableScrollPhysics(),
// 넘어가지 않더라도 스크롤 되게
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: [
renderContainer(color: Colors.pink)
]
),
);
}
Widget renderContainer({required Color color}) {
return Container(
height: 300,
color: color,
);
}
}
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class SingleChildScrollViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
SingleChildScrollViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'SingleChildScrollView',
body: renderClip()
);
}
// 3. 잘리지 않고 스크롤되게
Widget renderClip() {
return SingleChildScrollView(
// 넘어가지 않더라도 스크롤 되게
clipBehavior: Clip.none,
child: Column(
children: [
renderContainer(color: Colors.pink)
]
),
);
}
Widget renderContainer({required Color color}) {
return Container(
height: 300,
color: color,
);
}
}
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class SingleChildScrollViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
SingleChildScrollViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'SingleChildScrollView',
body: renderPhysics()
);
}
// 4. 여러가지 physics 정리
Widget renderPhysics() {
return SingleChildScrollView(
// 기본값 NeverScrollableScrollPhysics() -> 넘어갈게 없다면 스크롤 안됨
// AlwaysScrollableScrollPhysics() -> 넘어갈게 없더라도 스크롤 되게
// BouncingScrollPhysics() -> 원래 iOS에서만 스크롤이 튕기는데 이거 넣으면 안드로이드도 튕김
// ClampingScrollPhysics() -> 안드로이드처럼 스크롤 안튕기게
physics: ClampingScrollPhysics(),
child: Column(
children: rainbowColors.map((e) => renderContainer(color: e)).toList()
),
);
}
Widget renderContainer({required Color color}) {
return Container(
height: 300,
color: color,
);
}
}
그렇게 썩 좋지는 않다.
100개의 Container를 렌더링한다면 ListView 같은경우는 한번에 다 불러오지않고 끊어서 효율적으로 불러오는 반면 100개를 한번에 싹다 렌더링해버린다.
얘도 SingleChildScrollView 처럼 한번에 다 나옴
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class ListViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
ListViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'ListView',
body: renderDefault()
);
}
Widget renderDefault() {
return ListView(
children: numbers.map((e) =>
renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e + 1
)
).toList()
);
}
Widget renderContainer({required Color color, required int index}) {
return Container(
height: 300,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
보여지는 부분만 렌더링 되고 그 후 스크롤 할 때 맞춰서 더 렌더링 해줌 ( 더 효율적임 )
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class ListViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
ListViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'ListView',
body: renderBuilder()
);
}
Widget renderBuilder() {
return ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
print('지금 몇번째게요? : $index 번입니다~!');
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index + 1
);
}
);
}
Widget renderContainer({required Color color, required int index}) {
return Container(
height: 300,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
보여지는 부분만 렌더링 되고 그 후 스크롤 할 때 맞춰서 더 렌더링 해줌 + 중간 중간에 추가할 위젯을 넣을 수 있음
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class ListViewScreen extends StatelessWidget {
final List<int> numbers = List.generate(100, (index) => index);
ListViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'ListView',
body: renderSeparated()
);
}
// 3. 2. + 중간 중간에 추가할 위젯을 넣을 수 있음
Widget renderSeparated() {
return ListView.separated(
itemCount: 100,
separatorBuilder: (BuildContext context, int index) {
if (index % 5 != 0 || index == 0) return Container();
print('separatorBuilder 몇번째게요? : $index 번입니다~!');
return renderContainer(
color: Colors.black,
index: index,
height: 100.0
);
},
itemBuilder: (BuildContext context, int index) {
print('지금 몇번째게요? : $index 번입니다~!');
return renderContainer(
color: rainbowColors[index % rainbowColors.length],
index: index + 1
);
}
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class GridViewScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
GridViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'GridViewScreen',
body: renderCount()
);
}
// 1. 한번에 다 그림
Widget renderCount() {
return GridView.count(
// 가로로 몇개 넣을래?
crossAxisCount: 2,
// 간격
crossAxisSpacing: 12.0,
mainAxisSpacing: 12.0,
children: numbers.map((e) => renderContainer(color: rainbowColors[e % rainbowColors.length], index: e)).toList(),
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
보여지는 부분만 렌더링 되고 그 후 스크롤 할 때 맞춰서 더 렌더링 해줌 ( 더 효율적임 )
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class GridViewScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
GridViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'GridViewScreen',
body: renderBuilder()
);
}
// 2. 보이는 것만 그림
Widget renderBuilder() {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12.0,
mainAxisSpacing: 12.0,
),
// 아이템 총 갯수
itemCount: 100,
itemBuilder: (context, index) {
return renderContainer(color: rainbowColors[index % rainbowColors.length], index: index);
}
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class GridViewScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
GridViewScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'GridViewScreen',
body: renderBuilderCrossAxisCount()
);
}
// 3. 최대 사이즈
Widget renderBuilderCrossAxisCount() {
return GridView.builder(
// 스크롤을 가로로
scrollDirection: Axis.horizontal,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
// 길이
maxCrossAxisExtent: 100
),
itemCount: 100,
itemBuilder: (context, index) {
return renderContainer(color: rainbowColors[index % rainbowColors.length], index: index);
}
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
class CustomScrollViewScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
CustomScrollViewScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
renderSliverAppBar(),
renderHeader(),
renderBuilderSliverList(),
renderHeader(),
renderSliverGridBuilder(),
renderHeader(),
renderBuilderSliverList(),
],
)
);
}
// AppBar
SliverAppBar renderSliverAppBar() {
return const SliverAppBar(
// 밑으로 스크롤하면 안보이고, 위로 스크롤하면 나오는거 floating: true (기본값은 false)
floating: true,
// 상단 고정 pinned: true (기본값은 false)
pinned: false,
// 자석효과 : 진짜 살짝만 아래로 내리거나 올리면 앱바가 올라가거나 내려감 (이거 쓸라면 floating true , pinned false)
snap: true,
// app bar를 따라오게 (기본값은 false 근데) 원래는 뒤에 흰 배경이 보임
stretch: true,
// app bar height
expandedHeight: 200,
// 텍스트가 app bar가 사라지지않았는데 더 빨리 사라짐
collapsedHeight: 150,
flexibleSpace: FlexibleSpaceBar(
// 배경을 넣을 수 있음 (위로 스크롤하면 자연스레 사라짐)
background: Image(image: AssetImage('asset/image_1.jpeg'), fit: BoxFit.cover),
// 앱바 밑에 title에 적은 글씨가 나옴
title: Text('FlexibleSpace'),
),
title: Text('custom scroll view'),
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
ListView 기본 생성자와 유사함
SliverList renderChildSliverList() {
return SliverList(
delegate: SliverChildListDelegate(
numbers.map((e) => renderContainer(color: rainbowColors[e % rainbowColors.length], index: e)).toList()
),
);
}
ListView.builder 와 유사함
SliverList renderBuilderSliverList() {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return renderContainer(color: rainbowColors[index % rainbowColors.length], index: index);
},
childCount: 10
),
);
}
GridView.count 와 유사함
SliverGrid renderChildSliverGrid() {
return SliverGrid(
delegate: SliverChildListDelegate(
numbers.map((e) => renderContainer(color: rainbowColors[e % rainbowColors.length], index: 1)).toList()
),
// max 최대 넓이 정하고 그 넓이 안에서 균등하게 배분
// fix 가로로 몇개 할건지
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
)
);
}
GridView.builder 와 유사함
SliverGrid renderSliverGridBuilder() {
return SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
return renderContainer(color: rainbowColors[index % rainbowColors.length], index: index);
}
),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
),
);
}
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) {
// TODO: implement build
return SizedBox.expand(child: child);
}
// TODO: implement maxExtent -> 최대 높이
double get maxExtent => maxHeight;
// TODO: implement minExtent -> 최소 높이
double get minExtent => minHeight;
// covariant -> 상속된 클래스도 사용가능
// oldDelegate -> build가 실행이 됐을 때 이전 Delegate
// this -> 새로운 delegate
// shouldRebuild -> 새로 build를 해야할지 말지 결정
// false -> build 안함 true -> build 다시함
bool shouldRebuild(_SliverFixedHeaderDelegate oldDelegate) {
// TODO: implement shouldRebuild
return oldDelegate.minHeight != minHeight || oldDelegate.maxHeight != maxHeight || oldDelegate.child != child;
// minHeight 나 maxHeight 나 child가 바꼈을 때 build 다시 함
}
}
class CustomScrollViewScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
CustomScrollViewScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
renderSliverAppBar(),
renderHeader(),
renderBuilderSliverList(),
renderHeader(),
renderSliverGridBuilder(),
renderHeader(),
renderBuilderSliverList(),
],
)
);
}
SliverPersistentHeader renderHeader() {
return SliverPersistentHeader(
pinned: true,
delegate: _SliverFixedHeaderDelegate(
child: Container(
color: Colors.black,
child: const Center(
child: Text(
'신기하지~',
style: TextStyle(
color: Colors.white,
),
),
),
),
minHeight: 50,
maxHeight: 200,
),
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
웹과 마찬가지로 우측에 스크롤바가 생김
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class ScrollbarScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
ScrollbarScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'ScrollbarScreen',
body: Scrollbar(
child: SingleChildScrollView(
child: Column(
children: numbers.map((e) => renderContainer(color: rainbowColors[e % rainbowColors.length], index: e)).toList(),
),
),
),
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
1,2,3…순서로 나열된 리스트를 중간에 터치를 길게해 위치를 바꿀 수 있다. 4,2,1,5,3… 이런 식으로
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class ReorderableListViewScreen extends StatefulWidget {
const ReorderableListViewScreen({super.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: renderBuilder()
);
}
Widget renderDefault() {
return ReorderableListView(
onReorder: ((int oldIndex, int newIndex) {
setState(() {
// oldIndex와 newIndex 모두 이동이 되기 전에 산정한다.
// [red, orange, yellow]
// [0, 1, 2]
// red 를 yellow 다음으로 옮기고 싶음
// red : 0 oldIndex -> 3 newIndex
// [orange , yellow , red]
if (oldIndex < newIndex) {
newIndex -= 1;
}
// [red , orange , yellow]
// yellow를 맨 앞으로 옮기고 싶다.
// yellow : 2 oldIndex -> 0 newIndex
// [yellow, red, orange]
final item = numbers.removeAt(oldIndex);
numbers.insert(newIndex, item);
});
}),
children: numbers.map((e) => renderContainer(color: rainbowColors[e % rainbowColors.length], index: e)).toList()
);
}
Widget renderBuilder() {
return ReorderableListView.builder(
itemCount: 100,
onReorder: ((int oldIndex, int newIndex) {
setState(() {
// oldIndex와 newIndex 모두 이동이 되기 전에 산정한다.
// [red, orange, yellow]
// [0, 1, 2]
// red 를 yellow 다음으로 옮기고 싶음
// red : 0 oldIndex -> 3 newIndex
// [orange , yellow , red]
if (oldIndex < newIndex) {
newIndex -= 1;
}
// [red , orange , yellow]
// yellow를 맨 앞으로 옮기고 싶다.
// yellow : 2 oldIndex -> 0 newIndex
// [yellow, red, orange]
final item = numbers.removeAt(oldIndex);
numbers.insert(newIndex, item);
});
}),
itemBuilder: ((context, index) {
return renderContainer(color: rainbowColors[numbers[index] % rainbowColors.length], index: numbers[index]);
}),
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
key: Key(index.toString()),
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}
서버 요청을 해야할 때 위에서 아래로 스크롤을 당기면 새로고침되면서 다시 서버요청을 할 수 있다.
import 'package:flutter/material.dart';
import 'package:scrollable_widgets/constants/colors.dart';
import 'package:scrollable_widgets/layouts/main_layout.dart';
class RefreshIndicatorScreen extends StatelessWidget {
List<int> numbers = List.generate(100, (index) => index);
RefreshIndicatorScreen({super.key});
Widget build(BuildContext context) {
return MainLayout(
title: 'RefreshIndicator',
body: RefreshIndicator(
onRefresh: () async {
// 서버 요쳥
await Future.delayed(Duration(seconds: 1));
},
child: ListView(
children: numbers.map((e) {
return renderContainer(
color: rainbowColors[e % rainbowColors.length],
index: e + 1
);
}).toList()
),
)
);
}
Widget renderContainer({required Color color, required int index, double? height}) {
print('내가 지금 몇번째이게? $index');
return Container(
height: height ?? 300.0,
color: color,
child: Center(
child: Text(
'$index 번째 리스트',
style: const TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w700,
color: Colors.white
)
)
)
);
}
}