지난번에는 SmoothPageIndicator 를 backgroud 로 구현한 경우와 title 로 구현한 경우를 비교해 보았습니다.
이번에는 title 로 구현한 인디케이터를 숨기기 위해서 Scaffold 를 추가, 화면 상단을 조금 부드럽게 표현하기 위해서 Container 를 추가해보겠습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1
구현한 화면은 아래와 같습니다.
초기 화면 상단에 검은색 계열로 appBar 영역만큼 그라데이션 처리, 화면 스크롤시 이미지가 화면 밖으로 사라지면 appBar 영역이 흰색으로 변경됨.
./src/screens/home/item_detail_page.dart - 전체 코드는 아래 "더보기" 를 클릭하세요
더보기
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import '../../models/item_model.dart';
import '../../repo/item_service.dart';
import '../../utils/logger.dart';
class ItemDetailPage extends StatefulWidget {
const ItemDetailPage({Key? key}) : super(key: key);
@override
_ItemDetailPageState createState() => _ItemDetailPageState();
}
class _ItemDetailPageState extends State<ItemDetailPage> {
final PageController _pageController = PageController();
// 스크롤이 얼마나 되었는지 알기 위해서 컨트롤러 등록,
final ScrollController _scrollController = ScrollController();
// isAppbarCollapsed 이미지가 화면에서 사라졌는지 확인,
bool isAppbarCollapsed = false;
Size? _size;
num? _statusBarHeight;
late String newItemKey;
@override
void initState() {
newItemKey = Get.arguments['itemKey'];
logger.d('$_size!.width, $kToolbarHeight, $_statusBarHeight, ${isAppbarCollapsed.toString()}');
// 스크롤이 발생할때 마다 addListener 가 실행됨,
_scrollController.addListener(() {
if (_size == null && _statusBarHeight == null) return;
if (isAppbarCollapsed) {
// 여기는 이미지가 앱바 아래로 보여지기 시작하는 시점,
// 앱바 사이즈(kToolbarHeight), 상태바 사이즈(_statusBarHeight)
if (_scrollController.offset < _size!.width - kToolbarHeight - _statusBarHeight!) {
isAppbarCollapsed = false;
setState(() {});
}
} else {
// 여기는 이미지가 앱바에 위로 올라가서 안보이기 시작하는 시점,
// 앱바 사이즈(kToolbarHeight), 상태바 사이즈(_statusBarHeight)
if (_scrollController.offset > _size!.width - kToolbarHeight - _statusBarHeight!) {
isAppbarCollapsed = true;
setState(() {});
}
}
});
super.initState();
}
@override
void dispose() {
_pageController.dispose();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
logger.d('item detail screen >> build >>> [$newItemKey]');
return FutureBuilder<ItemModel2>(
future: ItemService().getItem(newItemKey),
builder: (context, snapshot) {
if (snapshot.hasData) {
// FutureBuilder 의 snapshot 에서 게시글 데이터 가져오기
ItemModel2 itemModel = snapshot.data!;
return LayoutBuilder(
builder: (context, constraints) {
_size = MediaQuery.of(context).size;
// 상태바 길이 가져오는 공식,
_statusBarHeight = MediaQuery.of(context).padding.top;
return Stack(
// fit 은 Stack 에 있는 모든 아이콘들이 화면에 가득차게 하는 옵션,
fit: StackFit.expand,
children: [
// 메인 정보를 표시하는 영역
Scaffold(
// 메인정보를 표시, CustomScrollView 는 listView 유사함
// listView 대신에 CustomScrollView 사용하는 이유는
// slivers 를 이용해서 화면을 구역으로 나눠서 각 구역마다 슬라이스를 구현할 수 있다,
body: CustomScrollView(
controller: _scrollController,
// children 을 대신하는 slivers 있고, slivers 안에는 sliver 형식의 위젯을 넣어줘야 한다
slivers: [
// 업로드한 사진 정보를 표시하는 영역
_imageAppBar(itemModel),
// 일반위젯을 sliver 안에 넣으러면 SliverToBoxAdapter 로 wrapping 해야 함,
SliverToBoxAdapter(
child: Container(
// 스크롤 테스트를 위해서 높이를 길게 적용함,
height: _size!.height,
color: Colors.cyan,
child: Center(child: Text(newItemKey)),
),
),
SliverToBoxAdapter(
child: Container(
// 스크롤 테스트를 위해서 높이를 길게 적용함,
height: _size!.height,
color: Colors.redAccent,
child: Center(child: Text(newItemKey)),
),
),
],
),
),
// 앱바 영역에 그라데이션 표현 추가
Positioned(
left: 0,
right: 0,
top: 0,
height: kToolbarHeight + _statusBarHeight!,
child: Container(
height: kToolbarHeight + _statusBarHeight!,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black45,
Colors.black38,
Colors.black26,
Colors.black12,
Colors.transparent,
],
),
),
),
),
// 화면 스크롤업 하면 앱바를 힌색으로 변경.
// 이전에 구현한 인디케이터가 appBar 타이틀위치에서 보여주던걸 숨김,
Positioned(
left: 0,
right: 0,
top: 0,
height: kToolbarHeight + _statusBarHeight!,
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
shadowColor: Colors.transparent,
backgroundColor: isAppbarCollapsed
? Colors.white
: Colors.transparent,
foregroundColor:
isAppbarCollapsed ? Colors.black87 : Colors.white,
),
),
)
],
);
},
);
}
return Container(
color: Colors.white,
child: const Center(child: CircularProgressIndicator()),
);
},
);
}
SliverAppBar _imageAppBar(ItemModel2 itemModel) {
return SliverAppBar(
// expandedHeight 에서는 세로 길이를 정해줄 수 있음,
expandedHeight: _size!.width,
// pinned: true 면 앱바 역역을 남기는 역할, false 면 스크롤시 같이 사라짐,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
// title: const Text('testing', style: TextStyle(color: Colors.black)),
// 타이틀 부분에 인디케이터 표시하고 아래에 위치함, 패키지 추가 필요함,
title: SizedBox(
child: SmoothPageIndicator(
controller: _pageController,
// PageController
count: itemModel.imageDownloadUrls.length,
effect: const WormEffect(
activeDotColor: Colors.black,
//Theme.of(context).primaryColor,
dotColor: Colors.black45,
//Theme.of(context).colorScheme.background,
radius: 3,
dotHeight: 6,
dotWidth: 6,
),
// your preferred effect
onDotClicked: (index) {}),
),
// background 로 이미지를 넣으면 됨, 이미지 표시
background: PageView.builder(
// 좌/우로 스크롤 가능하게 처리,
controller: _pageController,
// 옆페이지로 이동시 포커스를 옆페이지로 이동시켜 로딩을 미리하게 설정함,
allowImplicitScrolling: true,
itemBuilder: (BuildContext context, int index) {
return ExtendedImage.network(
itemModel.imageDownloadUrls[index],
fit: BoxFit.cover,
// 캐싱을 했지만 다시 로딩하는 경우가 있어서 이미지 사이즈를 줄여줌,
scale: 0.1,
);
},
itemCount: itemModel.imageDownloadUrls.length,
),
),
);
}
}
주요 부분은 아래와 같습니다.
1. 스크롤 컨트롤러 추가 및 리스너 등록을 통하여 스크롤 position 실시간 확인
@override
void initState() {
newItemKey = Get.arguments['itemKey'];
logger.d('$_size!.width, $kToolbarHeight, $_statusBarHeight, ${isAppbarCollapsed.toString()}');
// 스크롤이 발생할때 마다 addListener 가 실행됨,
_scrollController.addListener(() {
if (_size == null && _statusBarHeight == null) return;
if (isAppbarCollapsed) {
// 여기는 이미지가 앱바 아래로 보여지기 시작하는 시점,
// 앱바 사이즈(kToolbarHeight), 상태바 사이즈(_statusBarHeight)
if (_scrollController.offset < _size!.width - kToolbarHeight - _statusBarHeight!) {
isAppbarCollapsed = false;
setState(() {});
}
} else {
// 여기는 이미지가 앱바에 위로 올라가서 안보이기 시작하는 시점,
// 앱바 사이즈(kToolbarHeight), 상태바 사이즈(_statusBarHeight)
if (_scrollController.offset > _size!.width - kToolbarHeight - _statusBarHeight!) {
isAppbarCollapsed = true;
setState(() {});
}
}
});
super.initState();
}
2. Scaffold 를 Stack 으로 wrapping 하여 appBar 영역을 구현.
return LayoutBuilder(
builder: (context, constraints) {
_size = MediaQuery.of(context).size;
// 상태바 길이 가져오는 공식,
_statusBarHeight = MediaQuery.of(context).padding.top;
return Stack(
// fit 은 Stack 에 있는 모든 아이콘들이 화면에 가득차게 하는 옵션,
fit: StackFit.expand,
children: [
// 메인 정보를 표시하는 영역
Scaffold(
// 메인정보를 표시, CustomScrollView 는 listView 유사함
// listView 대신에 CustomScrollView 사용하는 이유는
// slivers 를 이용해서 화면을 구역으로 나눠서 각 구역마다 슬라이스를 구현할 수 있다,
body: CustomScrollView(
controller: _scrollController,
// children 을 대신하는 slivers 있고, slivers 안에는 sliver 형식의 위젯을 넣어줘야 한다
slivers: [
// 업로드한 사진 정보를 표시하는 영역
_imageAppBar(itemModel),
// 일반위젯을 sliver 안에 넣으러면 SliverToBoxAdapter 로 wrapping 해야 함,
SliverToBoxAdapter(
child: Container(
// 스크롤 테스트를 위해서 높이를 길게 적용함,
height: _size!.height,
color: Colors.cyan,
child: Center(child: Text(newItemKey)),
),
),
SliverToBoxAdapter(
child: Container(
// 스크롤 테스트를 위해서 높이를 길게 적용함,
height: _size!.height,
color: Colors.redAccent,
child: Center(child: Text(newItemKey)),
),
),
],
),
),
// 앱바 영역에 그라데이션 표현 추가
Positioned(
left: 0,
right: 0,
top: 0,
height: kToolbarHeight + _statusBarHeight!,
child: Container(
height: kToolbarHeight + _statusBarHeight!,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black45,
Colors.black38,
Colors.black26,
Colors.black12,
Colors.transparent,
],
),
),
),
),
// 화면 스크롤업 하면 앱바를 힌색으로 변경.
// 이전에 구현한 인디케이터가 appBar 타이틀위치에서 보여주던걸 숨김,
Positioned(
left: 0,
right: 0,
top: 0,
height: kToolbarHeight + _statusBarHeight!,
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
shadowColor: Colors.transparent,
backgroundColor: isAppbarCollapsed
? Colors.white
: Colors.transparent,
foregroundColor:
isAppbarCollapsed ? Colors.black87 : Colors.white,
),
),
)
],
);
},
);
'Flutter > 12 Clone 'Used Goods app'' 카테고리의 다른 글
[Flutter] Clone - 당근마켓43(Item detail & PageView) - 5 (0) | 2022.08.24 |
---|---|
[Flutter] Clone - 당근마켓42(Item detail & PageView) - 4 (0) | 2022.08.23 |
[Flutter] Clone - 당근마켓40(Item detail & PageView) - 2 (0) | 2022.08.21 |
[Flutter] Clone - 당근마켓39(Item detail & PageView) (0) | 2022.08.21 |
[Flutter] Clone - 당근마켓38(Item detail & CustomScrollView) (0) | 2022.08.21 |