본문 바로가기
Flutter&Dart

[Flutter 꿀팁] 특정 위젯 위치로 스크롤 이동하는 방법 - Scrollable.ensureVisible

by 밥바비 2023. 4. 12.
반응형

Flutter로 개발을 하다 보면 특정 이벤트 발생 시(예를 들어 버튼 클릭),  특정 위젯의 위치로 스크롤을 이동시켜야 하는 경우가 생긴다. 이때 유용한 메서드가 Scrollable.ensureVisible 이다.

대문 이미지!
Flutter : Scrollable.ensureVisible

사용법

사용법은 간단하다. 

  1. GlobalKey를 선언
  2. 1에서 선언한 GlobalKey를 이동하길 원하는 위젯의 Key로 등록
  3. Scrollable.ensureVisible의 첫 번째 파라미터로 위에서 선언된  GlobalKey의 currentContext를 넘기며 메서드를 실행
Scrollable.ensureVisible(
	globalKey.currentContext!,
	duration: const Duration(milliseconds: 500),
	curve: Curves.ease,
);

위와 같은 방식으로 간단하게 사용 가능하며, 아래는 위와 같은 방식으로 구현한 예제 화면이다. 상단 AppBar의 버튼을 클릭하면 해당하는 색상의 위젯으로 스크롤이 이동하는 것을 확인할 수 있다.

예제 실행 화면 gif
예제 실행 화면

사용 예시 코드

// GlobalKey 선언
final redKey = GlobalKey();
final blueKey = GlobalKey();
final greenKey = GlobalKey();

// key 등록
SliverToBoxAdapter(
  key: greenKey,
  child: Container(
    color: Colors.green,
    height: MediaQuery.of(context).size.height / 2 + 100,
  ),
),

// Scrollable.ensureVisible 메소드 호출
ElevatedButton(
  style: const ButtonStyle(
    backgroundColor:
        MaterialStatePropertyAll<Color>(Colors.green),
  ),
  onPressed: () {
    Scrollable.ensureVisible(
      greenKey.currentContext!,
      duration: const Duration(milliseconds: 500),
      curve: Curves.ease,
    );
  },
  child: const Text('Green'),
),

주의점

사용법에서 알 수 있듯이 Scrollable.ensureVisible은 위젯의 context를 사용하기 때문에 GlobalKey로 접근하려는 위젯의 context가 존재해야 사용할 수 있다. (해당 context가 위젯 트리상에 존재해야 한다) 그렇기 때문에 실제코드에선, 예시 코드의 null assertion operator(느낌표 표시,!)를 사용한 부분을 예외 처리해주어야 한다.

if (redKey.currentContext != null) {
  Scrollable.ensureVisible(
    redKey.currentContext!,
    duration: const Duration(milliseconds: 500),
    curve: Curves.ease,
  );
}

아래는 위 예시의 전체 코드이다

더보기
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const ScrollExample(),
    );
  }
}

class ScrollExample extends StatelessWidget {
  const ScrollExample({super.key});

  @override
  Widget build(BuildContext context) {
    final redKey = GlobalKey();
    final blueKey = GlobalKey();
    final greenKey = GlobalKey();

    return Scaffold(
      body: SafeArea(
        child: CustomScrollView(
          slivers: [
            SliverAppBar(
              backgroundColor: Colors.white,
              pinned: true,
              title: Row(
                children: [
                  ElevatedButton(
                    style: const ButtonStyle(
                      backgroundColor:
                          MaterialStatePropertyAll<Color>(Colors.red),
                    ),
                    onPressed: () {
                      if (redKey.currentContext != null) {
                        Scrollable.ensureVisible(
                          redKey.currentContext!,
                          duration: const Duration(milliseconds: 500),
                          curve: Curves.ease,
                        );
                      }
                    },
                    child: const Text('Red'),
                  ),
                  const SizedBox(
                    width: 10,
                  ),
                  ElevatedButton(
                    style: const ButtonStyle(
                      backgroundColor:
                          MaterialStatePropertyAll<Color>(Colors.blue),
                    ),
                    onPressed: () {
                      Scrollable.ensureVisible(
                        blueKey.currentContext!,
                        duration: const Duration(milliseconds: 500),
                        curve: Curves.ease,
                      );
                    },
                    child: const Text('Blue'),
                  ),
                  const SizedBox(
                    width: 10,
                  ),
                  ElevatedButton(
                    style: const ButtonStyle(
                      backgroundColor:
                          MaterialStatePropertyAll<Color>(Colors.green),
                    ),
                    onPressed: () {
                      Scrollable.ensureVisible(
                        greenKey.currentContext!,
                        duration: const Duration(milliseconds: 500),
                        curve: Curves.ease,
                      );
                    },
                    child: const Text('Green'),
                  ),
                ],
              ),
            ),
            SliverToBoxAdapter(
              // key: redKey,
              child: Container(
                color: Colors.red,
                height: MediaQuery.of(context).size.height / 2 + 100,
              ),
            ),
            SliverToBoxAdapter(
              key: blueKey,
              child: Container(
                color: Colors.blue,
                height: MediaQuery.of(context).size.height / 2 + 100,
              ),
            ),
            SliverToBoxAdapter(
              key: greenKey,
              child: Container(
                color: Colors.green,
                height: MediaQuery.of(context).size.height / 2 + 100,
              ),
            ),
            SliverToBoxAdapter(
              child: Container(
                color: Colors.white,
                height: MediaQuery.of(context).size.height / 2 + 100,
              ),
            )
          ],
        ),
      ),
    );
  }
}
반응형

댓글