Vintage appMaker의 Tech Blog

[Flutter] ScrollView에서 이동시 item 위치찾기 본문

Source code or Tip/Flutter & Dart

[Flutter] ScrollView에서 이동시 item 위치찾기

VintageappMaker 2022. 8. 12. 10:38

 

Android와 같은 native 앱을 개발하다보면 list관련 view들은 스크롤이 변할 시, 리스트 item의 인덱스 정보를 넘겨주는 경향이 있다. 그래서 Flutter에서도 Scroll 변경시 처리하는 listener에서 index를 찾아보았지만 찾을 수 없었다. 단지 Flutter는 위젯의 키값을 사용하여 스크롤될 때의 위치를 계산하면서 파악할 수 밖에 없었다. 

 

1. GlobalKey()를 사용해야 한다. 정보를 알고자 하는 위젯에 키값을 대입한다. 

2. Scroll관련 listener에서 key값으로 위젯을 가져온다. 

3. 이때, context의 findRenderObject를 사용하여 RenderBox 정보를 가져온다. 

4. 그 값이 보여지는 위치의 상단을 기준으로 0값인지 -값인지를 비교한다(-이면 지나간 것임)

 

다음은 전체소스이다. 

import 'dart:ui';

import 'package:flutter/material.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      scrollBehavior: DeskScrollBehavior(),
      title: 'list(grid) in ScrollView',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MainPage(title: 'list(grid) in ScrollView'),
    );
  }
}

class MainPage extends StatefulWidget {
  const MainPage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  final _pageController = PageController(viewportFraction: 0.8);
  int _index = 0;

  String sMessage = "";

  List<GlobalKey> keys = <GlobalKey>[GlobalKey(), GlobalKey(), GlobalKey()];
  final ScrollController _scrollController = ScrollController();

  // y값 가져오기
  double getWidgetPosition(GlobalKey key) {
    if (key.currentContext != null) {
      final RenderBox renderBox =
          key.currentContext!.findRenderObject() as RenderBox;
      var position = renderBox.localToGlobal(Offset.zero);
      return position.dy - renderBox.size.height; //
    }

    return 0;
  }

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      print('offset = ${_scrollController.offset}');

      var y1 = getWidgetPosition(keys[0]);
      var y2 = getWidgetPosition(keys[1]);
      var y3 = getWidgetPosition(keys[2]);

      print("y1 : ${y1}, y2 : ${y2}, y3 : ${y3}");

      if (y1 < 0 && y2 > 0 && y3 > 0) {
        setState(() {
          sMessage = "section 1";
        });
      } else if (y1 < 0 && y2 < 0 && y3 > 0) {
        setState(() {
          sMessage = "section 2";
        });
      } else if (y1 < 0 && y2 < 0 && y3 < 0) {
        setState(() {
          sMessage = "section 3";
        });
      } else {
        setState(() {
          sMessage = "영역밖임";
        });
      }
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          _buildHeader(sMessage),
          SizedBox(height: 30),
          Expanded(
            child: SingleChildScrollView(
              controller: _scrollController,
              physics: AlwaysScrollableScrollPhysics(),
              child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _buildTitle("GridSection", keys[0], Colors.blue),
                    _buildGridView(),
                    _buildTitle("PageViewSection", keys[1], Colors.blue),
                    _buildPageView(),
                    _buildTitle("ListSection", keys[2], Colors.blue),
                    _buildListView(),
                  ]),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildHeader(String s) {
    return Container(
        width: double.infinity,
        height: 100,
        color: Color.fromARGB(255, 20, 2, 64),
        child: Center(
            child: Text(
          s,
          style: TextStyle(fontSize: 40, color: Colors.white),
        )));
  }

  Widget _buildTitle(String s, Key k, [Color c = Colors.red]) {
    return Container(
        key: k,
        width: double.infinity,
        height: 100,
        color: c,
        child: Center(
            child: Text(
          s,
          style: TextStyle(fontSize: 40, color: Colors.white),
        )));
  }

  Container _buildGridView() {
    return Container(
      child: GridView.builder(
          physics: NeverScrollableScrollPhysics(),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            mainAxisSpacing: 1,
            crossAxisSpacing: 1,
          ),
          itemCount: 10,
          shrinkWrap: true,
          itemBuilder: (BuildContext context, int index) {
            var url = getUrl(index);
            return Container(child: Image.network(url));
          }),
    );
  }

  Widget _buildPageView() {
    return Container(
      height: 200,
      child: PageView.builder(
          itemCount: 10,
          controller: _pageController,
          itemBuilder: (BuildContext context, int index) {
            return Transform.scale(
                scale: index == _index ? 1 : 0.98,
                child: Container(
                  color: (index % 2 == 0)
                      ? Color.fromARGB(255, 70, 70, 70)
                      : Color.fromARGB(255, 0, 0, 0),
                  child: SizedBox(
                      width: 100,
                      child: Center(
                          child:
                              Container(child: Image.network(getUrl(index))))),
                ));
          },
          onPageChanged: (int index) => setState(() => _index = index)),
    );
  }

  Container _buildListView() {
    return Container(
      child: ListView.builder(
        physics: NeverScrollableScrollPhysics(),
        shrinkWrap: true,
        padding: const EdgeInsets.all(8),
        itemCount: 30,
        itemBuilder: (BuildContext context, int index) {
          return Center(child: Container(child: Image.network(getUrl(index))));
        },
      ),
    );
  }

  String getUrl(int index) {
    return (index % 2 == 0)
        ? "https://cdn.cloudflare.steamstatic.com/steam/apps/108710/capsule_616x353.jpg?t=1616438999"
        : "https://cdn-ext.fanatical.com/production/product/1280x720/674039a7-bdf2-4b0a-856c-624b8d4b1c8e.jpeg";
  }
}

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

 

 

[전체소스]

 

Comments