본문 바로가기

Flutter/12 Clone 'Used Goods app'

[Flutter] Clone - 당근마켓39(Item detail & PageView)

이번에는 Item detail page 구현에 필요한 PageView 에 대해서 알아보겠습니다.

이미지가 여러개인 경우, 인디케이터도 같이 구현하는 방법에 대해서 알아보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

 

 

구현한 화면은 아래와 같습니다.

 

 

 

 

 

인디케이터를 위해서 추가한 패키지는 아래와 같습니다.

 

smooth_page_indicator: ^1.0.0+2

 

 

 

./src/screens/home/item_detail_page.dart

 

class _ItemDetailPageState extends State<ItemDetailPage> {
  final PageController _pageController = PageController();
  Size? _size;
  late String newItemKey;

  @override
  void initState() {
    newItemKey = Get.arguments['itemKey'];
    super.initState();
  }

  @override
  void dispose() {
    _pageController.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!;
          // provider 입포트하고 고객 데이터 가져오기
          // UserModel2 userModel = context.read<UserNotifier>().userModel!;
          // UserModel1 userModel = UserController.to.userModel.value!;

          return LayoutBuilder(
            builder: (context, constraints) {
              _size = MediaQuery.of(context).size;
              // _statusBarHeight = MediaQuery.of(context).padding.top;
              return Stack(
                fit: StackFit.expand,
                children: [
                  // 메인 정보를 표시하는 영역
                  Scaffold(
                    // 메인정보를 표시, CustomScrollView 는 listView 유사함
                    // listView 대신에 CustomScrollView 사용하는 이유는
                    // 화면을 구역으로 나눠서 각 구역마다 슬라이스를 구현할 수 있다,
                    body: CustomScrollView(
                      // controller: _scrollController,
                      // children 을 대신하는 slivers 있고, slivers 안에는 sliver 형식의 위젯을 넣어줘야 한다
                      slivers: [
                        // 업로드한 사진 정보를 표시하는 영역
                        _imageAppBar(itemModel),

                        // 일반위젯을 sliver 안에 넣으러면 SliverToBoxAdapter 로 wrapping 해야 함,
                        SliverToBoxAdapter(
                          child: Container(
                            // 스크롤 테스트를 위해서 높이를 길게 적용함,
                            height: _size!.height * 2,
                            color: Colors.cyan,
                            child: Center(child: Text(newItemKey)),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              );
            },
          );
        }
        return Container(
          color: Colors.white,
          child: const Center(child: CircularProgressIndicator()),
        );
      },
    );
  }

// ********************  background 부분이 메인 수정 부분임 ********************
  SliverAppBar _imageAppBar(ItemModel2 itemModel) {
    return SliverAppBar(
      // expandedHeight 에서는 세로 길이를 정해줄 수 있음,
      expandedHeight: _size!.width,
      // pinned: true 면 앱바 역역을 남기는 역할, false 면 스크롤시 같이 사라짐,
      pinned: true,
      flexibleSpace: FlexibleSpaceBar(
        // background 로 이미지를 넣으면 됨, 이미지 표시
        background: Stack(
          children: [
            // 좌/우로 스크롤 가능하게 처리,
            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,
            ),
            Positioned(
              bottom: padding_16,
              // 중간으로 위치시키기 위해서 좌/우 0 으로 설정,
              left: 0,
              right: 0,
              child: Center(
                child: SmoothPageIndicator(
                    controller: _pageController, // PageController
                    count: itemModel.imageDownloadUrls.length,
                    effect: WormEffect(
                        activeDotColor: Theme.of(context).primaryColor,
                        dotColor: Theme.of(context).colorScheme.background,
                        radius: 4,
                        dotHeight: 8,
                        dotWidth: 8), // your preferred effect
                    onDotClicked: (index) {}),
              ),
            ),
          ],
        ),
      ),
    );
  }
}