guricode

[Flutter]무한 스크롤을 구현하는 방법,당겨서 새로고침 기능을 구현하는 방법 본문

앱/Flutter&Dart

[Flutter]무한 스크롤을 구현하는 방법,당겨서 새로고침 기능을 구현하는 방법

agentrakugaki 2025. 10. 20. 02:14

1.무한스크롤

 

무한 스크롤은 스크롤이 목록 끝에 가까워지면 다음페이지를 비동기로 로드한다.

핵심은 ScrollController의 position.pixels / maxScrollExtent를 감지하는 것이다.

class InfiniteList extends StatefulWidget {
  const InfiniteList({super.key});
  @override State<InfiniteList> createState() => _InfiniteListState();
}

class _InfiniteListState extends State<InfiniteList> {
  final _c = ScrollController();
  final _items = <int>[];
  bool _loading = false;
  int _page = 0;

  @override
  void initState() {
    super.initState();
    _fetch(); // 첫 페이지
    _c.addListener(() {
      final nearBottom = _c.position.pixels >= _c.position.maxScrollExtent - 200;
      if (nearBottom && !_loading) _fetch();
    });
  }

  Future<void> _fetch() async {
    _loading = true;
    setState(() {});
    await Future.delayed(const Duration(milliseconds: 600)); // mock API
    _items.addAll(List.generate(20, (i) => _page * 20 + i));
    _page++;
    _loading = false;
    setState(() {});
  }

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

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _c,
      itemCount: _items.length + 1, // 로딩 인디케이터 1칸
      itemBuilder: (_, i) {
        if (i == _items.length) {
          return _loading ? const Padding(
            padding: EdgeInsets.all(16),
            child: Center(child: CircularProgressIndicator()),
          ) : const SizedBox.shrink();
        }
        return ListTile(title: Text('Item ${_items[i]}'));
      },
    );
  }
}

 

 

2.당겨서 새로고침(Pull to Refresh)

당겨서 새로고침은 리스트 최상단에서 끌어내리면 전체 또는 앞 페이지를 재요청하는 기능이다.

flutter 내장 RefreshIndicator 사용한다.

class RefreshList extends StatefulWidget {
  const RefreshList({super.key});
  @override State<RefreshList> createState() => _RefreshListState();
}

class _RefreshListState extends State<RefreshList> {
  var _items = List.generate(20, (i) => 'Row $i');

  Future<void> _refresh() async {
    await Future.delayed(const Duration(milliseconds: 700)); // mock API
    setState(() {
      _items = List.generate(20, (i) => 'New $i'); // 전체 갱신
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      child: ListView.builder(
        physics: const AlwaysScrollableScrollPhysics(), // 데이터 0건여도 당김 허용
        itemCount: _items.length,
        itemBuilder: (_, i) => ListTile(title: Text(_items[i])),
      ),
    );
  }
}

 

함께 쓸 때의 패턴(요약)

  • 리스트 화면: RefreshIndicator(ListView.builder + ScrollController)
  • 상단 당김 → page=0로 초기화 후 첫 페이지 재요청
  • 하단 근접 → 다음 페이지 요청
  • 로딩 상태를 각각 분리: isRefreshing, isPaging