How to Automaticallly scroll to a position of a Row inside SingleChildScrollView in Flutter
Asked Answered
E

4

9

I am using a SingleChildScrollView. Its scrollDirection is set to Horizontal with >20 child widgets placed inside a Row Widget. I want to programmatically scroll to a position widget(i.e, 5th or 6th position) in the Row. Is there any way to do it programmatically ?

SingleChildScrollView(
    scrollDirection: Axis.horizontal,
    child: Row(
      children: buttons,
    ),
  )
Effete answered 9/12, 2020 at 16:24 Comment(1)
call Scrollable.ensureVisible method thenDarcydarda
S
14

The easiest way to doing this is using Scrollable.ensureVisible.

ensureVisible method

Scrolls the scrollables that enclose the given context so as to make the given context visible.

Please see the code below :

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _value = 0;
  static final List<GlobalKey> _key = List.generate(20, (index) => GlobalKey());
  final List<Widget> buttons = List.generate(
    20,
    (index) => RaisedButton(
      onPressed: () {},
      color: index % 2 == 0 ? Colors.grey : Colors.white,
      child: Text("Button No # ${index + 1}", key: _key[index]),
    ),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: buttons,
            ),
          ),
          DropdownButton(
            value: _value,
            items: List.generate(
              20,
              (index) => DropdownMenuItem(
                  child: Text("Goto Button # ${index + 1}"), value: index),
            ),
            onChanged: (value) {
              setState(() {
                _value = value;
                print("calling");
                Scrollable.ensureVisible(_key[value].currentContext);
              });
            },
          )
        ],
      ),
    );
  }
}
Spinode answered 9/12, 2020 at 16:48 Comment(2)
ensureVisible will scroll on both axises if the SingleChildScrollView is a child of another scrollable widget.Secularity
Is there a way to prevent scrolling in both axes (i.e. only allow scrolling in horizontal direction?)Argyrol
P
10

You could define a ScrollController:

ScrollController _controller = new ScrollController();

Pass it to the SingleChildScrollView:

SingleChildScrollView(
    controller: _scrollController,
    scrollDirection: Axis.horizontal,
    child: Row(
      children: buttons,
    ),
  ),

And programmatically scroll it as follows:

void scroll(double position) {
 _scrollController.jumpTo(position);
}

Or, if a scroll animation is desired:

void scrollAnimated(double position) {
 _scrollController.animateTo(position, Duration(seconds: 1), Curves.ease);
}

If you'd like to automatically scroll immediately after the layout has been built, you could do so by overriding the initState method:

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => scroll(500)); // scroll automatically  500px (as an example)
  }
Packhorse answered 9/12, 2020 at 16:32 Comment(2)
this solution provides a way to scroll by a given amount of width, what i need is to scroll by position. The accepted answer above solves my purpose.Effete
In case anyone else was wondering, to scroll to the end using the above technique: _scrollController.jumpTo(_scrollController.positions.last.maxScrollExtent)Draughtboard
N
0

You can add ScrollController in SingleChildScrollView and scroll to your specific position which you want

_scrollController.animateTo(
           //here specifing position= 100 mean 100px
                      100,
                      curve: Curves.ease,
                      duration: Duration(seconds: 1),
Napoleon answered 19/8, 2021 at 11:2 Comment(0)
P
0

This could also be done in a stateless widget, by triggering it in the build method. Used addPostFrameCallback to ensure that the scrolling is done after the rendering is done. You'll need to add back state management for the value of the choice. But then you have a choice of using GetX, StatefulWidget or any other.

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(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);

  final String? title;

  static final List<GlobalKey> _key = List.generate(20, (index) => GlobalKey());
  final List<Widget> buttons = List.generate(
    20,
    (index) => ElevatedButton(
      onPressed: () {},
      style: ButtonStyle(backgroundColor: MaterialStateProperty.resolveWith((_)=> index % 2 == 0 ? Colors.lightGreen : Colors.orange)),
      child: Text("Button No # ${index + 1}", key: _key[index]),
    ),
  );

  @override
  Widget build(BuildContext context) {
    final thisWidget = Scaffold(
      appBar: AppBar(
        title: Text(title!),
      ),
      body: Column(
        children: [
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: buttons,
            ),
          ),
          DropdownButton(
            value: 0,
            items: List.generate(
              20,
              (index) => DropdownMenuItem(
                  value: index,
                  child: Text("Goto Button # ${index + 1}")),
            ),
            onChanged: (value) {
              if (value != null) {
                WidgetsBinding.instance
                  .addPostFrameCallback((_) => Scrollable.ensureVisible(_key[value].currentContext!));
              }
            },
          )
        ],
      ),
    );
    WidgetsBinding.instance
      .addPostFrameCallback((_) => Scrollable.ensureVisible(_key[15].currentContext!));
    return thisWidget;
  }
}
Purdy answered 14/3, 2023 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.