Vintage appMaker의 Tech Blog

[Flutter] stickyheader 구현(CustomScrollView, SliverAppBar, SliverList) 본문

Source code or Tip/Flutter & Dart

[Flutter] stickyheader 구현(CustomScrollView, SliverAppBar, SliverList)

VintageappMaker 2022. 8. 13. 16:28


 

 

CustomScrollView class - widgets library - Dart API

A ScrollView that creates custom scroll effects using slivers. A CustomScrollView lets you supply slivers directly to create various scrolling effects, such as lists, grids, and expanding headers. For example, to create a scroll view that contains an expan

api.flutter.dev

아래로 스크롤 시, 특정 아이템이 상단에 픽스되기를 원한다면  Flutter에서는 CustomScrollVIew를 이용하여 sliver 파라메터에 위젯 리스트를 넘긴다. sliver에 넘겨지는 위젯 리스트들은 SliverAppBar()가 고정되는 해더의 역할을 하며 SliverList, SliverGrid와 같은 위젯을 다음 위치에 배치하면 된다. 즉 [해더, 내용, 해더, 내용] 형식으로 배치하여 사용할 수 있다. 

CustomScrollView(
  // (1) slivers에 위젯을 나열한다.  
  slivers: <Widget>[
    // (2) Header인 SliverAppBar를 먼저 배치 후, 그 다음에 SliverList를 배치한다.
    SliverAppBar(),
    SliverList() // 또는 SliverFixedExtentList()
    
    // (3) 순서대로 (2)번과 같은 내용을 차례대로 배열하면 멀티 stickyheader 구현이 가능하다.
    
    //SliverAppBar(),
    //SliverList() // 또는 SliverFixedExtentList()
    
    ... 
  ],
));

 

다음은 예제의 전체소스이다.

import 'package:flutter/material.dart';
import 'dart:ui';

void main() => runApp(MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  static String _title = 'Flutter SliverAppBar test';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      color: Colors.black,
      scrollBehavior: DeskScrollBehavior(),
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  final _pageController = PageController(viewportFraction: 1);
  int _index = 0;

  Widget _buildPageView() {
    return Container(
      color: Colors.black,
      height: 100,
      child: PageView.builder(
          itemCount: 10,
          controller: _pageController,
          itemBuilder: (BuildContext context, int index) {
            return Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: (index % 2 == 0)
                    ? Color.fromARGB(255, 196, 15, 15)
                    : Color.fromARGB(255, 73, 73, 72),
                child: SizedBox(
                    width: 100, child: Center(child: Text("${_index}"))),
              ),
            );
          },
          onPageChanged: (int index) => setState(() => _index = index)),
    );
  }

  Container _buildGridView() {
    return Container(
      child: GridView.builder(
          physics: NeverScrollableScrollPhysics(),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            mainAxisSpacing: 5,
            crossAxisSpacing: 5,
          ),
          itemCount: 10,
          shrinkWrap: true,
          itemBuilder: (BuildContext context, int index) {
            var url = (index % 2 == 0)
                ? "https://lh3.googleusercontent.com/GTmuiIZrppouc6hhdWiocybtRx1Tpbl52eYw4l-nAqHtHd4BpSMEqe-vGv7ZFiaHhG_l4v2m5Fdhapxw9aFLf28ErztHEv5WYIz5fA"
                : "https://oopy.lazyrockets.com/api/v2/notion/image?src=https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9e5d9e8e-4b0d-4f3f-9580-baf556faad5c%2Fios-logo.jpg&blockId=d983bdda-9a22-4eab-998b-9a0ff3f8ec73&width=2400";
            return Container(child: Image.network(url));
          }),
    );
  }

  Widget _buildHeader(String s, [Color c = Colors.white]) {
    return Container(
        width: double.infinity,
        height: 100,
        color: c,
        child: Center(
            child: Text(
          s,
          style: TextStyle(fontSize: 20, color: Colors.grey),
        )));
  }

  late List<Widget> widgetList1;

  @override
  void initState() {
    widgetList1 = [
      _buildHeader("+Grid뷰"), 
      _buildGridView(),
      _buildHeader("+Page뷰"), 
      for (var i = 0; i < 10; i++) _buildPageView(),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Flutter SliverAppBar test'),
        ),
        body: CustomScrollView(
          slivers: <Widget>[
            
            // 첫번째 Header
            SliverAppBar(
              pinned: true,
              expandedHeight: 200.0,
              flexibleSpace: FlexibleSpaceBar(
                title: Text('🍕 Flex한 SliverAppBar', style: TextStyle(fontSize: 20)),
                background: Image.network(fit: BoxFit.fill,"https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Desktop_Ubuntu_20.04.png/1024px-Desktop_Ubuntu_20.04.png"),
              ),
            ),
            SliverFixedExtentList(
              itemExtent: 50.0,
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    alignment: Alignment.center,
                    child: Text(
                      '🐈'.multiString(index),
                      style: TextStyle(fontSize: 30),
                    ),
                  );
                }, childCount: 10
              ),
            ),

            // 두번째 해더 
            SliverAppBar(
              pinned: true,
              title: ListTile(
                leading: Text("🍕",
                    style: TextStyle(fontSize: 20, color: Colors.white)),
                title: Text(
                  "고정된 SliverAppBar",
                  style: TextStyle(fontSize: 20, color: Colors.white),
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                  (context, index) => widgetList1[index],
                  childCount: widgetList1.length),
            ),
          ],
        ));
  }
}

// 웹과 desktop에서 모바일처럼 터치 스크롤 지원하기 위함
class DeskScrollBehavior extends MaterialScrollBehavior {
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      };
}

extension on String {
  String multiString(int n){
  return this * n;
  }
}

 

 

 

 

 

Comments