solacking
solacking

Reputation: 335

Flutter - Scroll ListView selected item into the center of the screen?

I have a horizontal scrolling ListView with an undetermined number of items inside.

enter image description here

How can I programatically scroll a specific item into the center of my screen?

Context: On the previous screen I have multiple items, and when I click on one, I need it to navigate to this screen and scroll the item selected on the previous screen to the center of the new screen.

My trouble is really just with the scrolling part.

Thanks in advance.

ListView:

final listViewController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      scrollDirection: Axis.horizontal,
      physics: ClampingScrollPhysics(),
      controller: listViewController,
      padding: EdgeInsets.zero,
      itemCount: testArray.length,
      itemBuilder: (ctx, i) => Item(
        testArray[i],
        testArray[i] == 'item5' ? true : false,
        () => {
          // testing code for the scroll functionality
          listViewController.animateTo(
              i + MediaQuery.of(context).size.width / 2,
              duration: Duration(seconds: 1),
              curve: Curves.easeIn),
        },
      ),
      separatorBuilder: (ctx, i) => Padding(
        padding: EdgeInsets.symmetric(horizontal: 6),
      ),
    );
  }
}

Item Widget:

class Item extends StatelessWidget {
  final String itemName;
  final bool selectedItem;
  final VoidCallback navigationHandler;

  Item(
      this.itemName, this.selectedItem, this.navigationHandler);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: double.infinity,
      child: TextButton(
        onPressed: navigationHandler,
        child: Text(
          itemName,
          style: selectedItem
              ? Theme.of(context).textTheme.headline6?.copyWith(
                    fontSize: 22,
                  )
              : Theme.of(context).textTheme.headline6?.copyWith(
                    color: Color(0xff707070),
                  ),
        ),
      ),
    );
  }
}

Upvotes: 4

Views: 9275

Answers (2)

Mario Velasco
Mario Velasco

Reputation: 3456

As mentioned in this StackOverflow thread the easiest way would be to set a GlobalKey for each list item and use ensureVisible to scroll, with alignment: 0.5 that item will be center at the scroll

Scrollable.ensureVisible(itemKey.currentContext, alignment: 0.5);

If you need to scroll after build then:

WidgetsBinding.instance.addPostFrameCallback((_) => 
    Scrollable.ensureVisible(itemKey.currentContext, alignment: 0.5)) 

Upvotes: 2

croxx5f
croxx5f

Reputation: 5733

The best solution to this issue that I've found is to use the package scrollable_positioned_list which can scroll to items based on its index.

If you knew the extent of its children you could have used a FixedExtentScrollController as the controller of your lisview and would not have needed to rely on a external dependency.

The gist of using the package is just to create a controller , this time an ItemScrollController and just replace your ListView.separated to ScrollablePositionedList.separated

final ItemScrollController itemScrollController = ItemScrollController();

ScrollablePositionedList.separated(
  itemScrollController: itemScrollController,
...
);

One then can scroll to a particular item with:

itemScrollController.scrollTo(
 index: 150,
 duration: Duration(seconds: 1),
 curve: Curves.easeIn);

A complete example would be as follows

final testArray = [for (var i = 0; i < 100; i++) 'item$i'];

class _MyAppState extends State<MyApp> {
  final itemScrollController = ItemScrollController();
  

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: MyApp._title,
        home: Scaffold(
            body: ScrollablePositionedList.separated(
          itemCount: testArray.length,
          scrollDirection: Axis.horizontal,
          physics: ClampingScrollPhysics(),
          padding: EdgeInsets.zero,
          itemBuilder: (context, i) => Item(
            testArray[i],
            testArray[i] == 'item5' ? true : false,
            () => {
              // testing code for the scroll functionality
              itemScrollController.scrollTo(
                  index: (i + 5) % testArray.length,
                  duration: Duration(seconds: 1),
                  curve: Curves.easeIn,
                  alignment: 0.5),/// Needed to center the item when scrolling
         
            },
          ),
          itemScrollController: itemScrollController,
          separatorBuilder: (ctx, i) => Padding(padding: EdgeInsets.symmetric(horizontal: 6)),
        )));
  }
}

By the way be accustomed at whenever you're working with controllers create them in the State of a Stateful widget, so they are only created once, and dispose them if necessary. I'ts not the case with ItemScrollController but ScrollController would have needed to be disposed .

Upvotes: 4

Related Questions