Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to autocorrect scroll position in a listview? (flutter)

Tags:

flutter

I have a horizontal listviewbuilder as shown in the picture on the top:Listview is on the top of the screen where you can see the dates. In this picture, I scrolled a bit and stopped at this position

Is there a way to autocorrect the current position so that the widget on the right corner fits perfectly to his full width? (here number 27)Because if you stop scrolling, it doesn't look nice if it is cut out like the number 27 in the picture. So is it possible to stop only on the fullest itemwidget so that the current position will not be like in the picture?

Here is my code for the listview builder:

import './date_widget.dart';


import 'package:date_picker_timeline/gestures/tap.dart';
import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';

class MyDatePickerTimeline extends StatefulWidget {
 

  
  
  DateTime currentDate;
  DateChangeListener onDateChange;
 
  String locale;

  // Creates the DatePickerTimeline Widget
  MyDatePickerTimeline(
    this.currentDate, {
    Key key,
    
   
    
    
    
    this.onDateChange,
    this.locale = "de",
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => new _MyDatePickerTimelineState();
}

class _MyDatePickerTimelineState extends State<MyDatePickerTimeline> {

  @override void initState() {
    super.initState();

    initializeDateFormatting(widget.locale, null);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      
     // padding: EdgeInsets.only(bottom: 600),
      width: MediaQuery.of(context).size.width ,
      height: 200,
      child: ListView.builder(
        reverse: true,
        itemCount: 5000,
        scrollDirection: Axis.horizontal,
        itemBuilder: (context, index) {
          // Return the Date Widget
          DateTime _date = DateTime.now().subtract(Duration(days: index));
          DateTime date = new DateTime(_date.year, _date.month, _date.day);
          bool isSelected = compareDate(date, widget.currentDate);

          return MyDateWidget(
            date: date,
           
            locale: widget.locale,
            selectionColor:
                isSelected ? Colors.black12 : Colors.transparent,
            onDateSelected: (selectedDate) {
              // A date is selected
              if (widget.onDateChange != null) {
                widget.onDateChange(selectedDate);
              }
              setState(() {
                widget.currentDate = selectedDate;
              });
            },
          );
        },
      ),
    );
  }

  bool compareDate(DateTime date1, DateTime date2) {
    return date1.day == date2.day &&
        date1.month == date2.month &&
        date1.year == date2.year;
  }
}

Or is it possible to set a fixed width that you can scroll?

like image 438
Georg Panteleev Avatar asked Oct 17 '25 12:10

Georg Panteleev


2 Answers

It seems that you are looking for PageScrollPhysics. From the PageScrollPhysics class documentation:

Scroll physics used by a PageView. These physics cause the page view to snap to page boundaries.

Having your ListView's physics property set to PageScrollPhysics will make the list scroll in a paginated, discrete way. If you also set the width of the widgets inside your ListView equal a fraction of the screen width, then the widgets inside the ListView will never be cut, no matter how many items there are in the list, how big the screen gets, or how the user scrolls.

Check out this sample I wrote to show you a way to implement this kind of scroll physics. You can copy it and run it in DartPad to see if it is what you are looking for. Note that there are 3 entries per page scrolled, yet there are 7 entries total in the ListView, and there is no way to have any of them cut in the view.

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: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        physics: PageScrollPhysics(),
        scrollDirection: Axis.horizontal,
        children: <Widget>[
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[900],
            child: const Center(child: Text('Entry A')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[800],
            child: const Center(child: Text('Entry B')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[700],
            child: const Center(child: Text('Entry C')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[600],
            child: const Center(child: Text('Entry D')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[500],
            child: const Center(child: Text('Entry E')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[400],
            child: const Center(child: Text('Entry F')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[300],
            child: const Center(child: Text('Entry G')),
          ),
        ],
      ),
    );
  }
}

like image 59
drogel Avatar answered Oct 19 '25 04:10

drogel


The accepted answer works fine but has one limitation: It snaps based on the viewport size. However, if you want to snap to each of the items in the list, it is not possible. I have used the following physics implementation where the snap size can be configured to the size of the items. This has a different limitation: It only works for equally sized children.

import 'package:flutter/material.dart';

class SnapScrollPhysics extends ScrollPhysics {
  const SnapScrollPhysics({super.parent, required this.snapSize});

  final double snapSize;

  @override
  SnapScrollSize applyTo(ScrollPhysics? ancestor) {
    return SnapScrollSize(parent: buildParent(ancestor), snapSize: snapSize);
  }

  double _getPage(ScrollMetrics position) {
    return position.pixels / snapSize;
  }

  double _getPixels(ScrollMetrics position, double page) {
    return page * snapSize;
  }

  double _getTargetPixels(
      ScrollMetrics position, Tolerance tolerance, double velocity) {
    double page = _getPage(position);
    if (velocity < -tolerance.velocity) {
      page -= 0.5;
    } else if (velocity > tolerance.velocity) {
      page += 0.5;
    }
    return _getPixels(position, page.roundToDouble());
  }

  @override
  Simulation? createBallisticSimulation(
      ScrollMetrics position, double velocity) {
    // If we're out of range and not headed back in range, defer to the parent
    // ballistics, which should put us back in range at a page boundary.
    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
      return super.createBallisticSimulation(position, velocity);
    }
    final Tolerance tolerance = this.tolerance;
    final double target = _getTargetPixels(position, tolerance, velocity);
    if (target != position.pixels) {
      return ScrollSpringSimulation(spring, position.pixels, target, velocity,
          tolerance: tolerance);
    }
    return null;
  }

  @override
  bool get allowImplicitScrolling => false;
}

You can see it in action in the adapted example from the accepted answer:


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: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        physics: SnapScrollPhysics(snapSize: MediaQuery.of(context).size.width/3),
        scrollDirection: Axis.horizontal,
        children: <Widget>[
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[900],
            child: const Center(child: Text('Entry A')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[800],
            child: const Center(child: Text('Entry B')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[700],
            child: const Center(child: Text('Entry C')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[600],
            child: const Center(child: Text('Entry D')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[500],
            child: const Center(child: Text('Entry E')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[400],
            child: const Center(child: Text('Entry F')),
          ),
          Container(
            width: MediaQuery.of(context).size.width/3,
            color: Colors.amber[300],
            child: const Center(child: Text('Entry G')),
          ),
        ],
      ),
    );
  }
}


class SnapScrollSize extends ScrollPhysics {
  const SnapScrollSize({super.parent, required this.snapSize});

  final double snapSize;

  @override
  SnapScrollSize applyTo(ScrollPhysics? ancestor) {
    return SnapScrollSize(parent: buildParent(ancestor), snapSize: snapSize);
  }

  double _getPage(ScrollMetrics position) {
    return position.pixels / snapSize;
  }

  double _getPixels(ScrollMetrics position, double page) {
    return page * snapSize;
  }

  double _getTargetPixels(
      ScrollMetrics position, Tolerance tolerance, double velocity) {
    double page = _getPage(position);
    if (velocity < -tolerance.velocity) {
      page -= 0.5;
    } else if (velocity > tolerance.velocity) {
      page += 0.5;
    }
    return _getPixels(position, page.roundToDouble());
  }

  @override
  Simulation? createBallisticSimulation(
      ScrollMetrics position, double velocity) {
    // If we're out of range and not headed back in range, defer to the parent
    // ballistics, which should put us back in range at a page boundary.
    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
      return super.createBallisticSimulation(position, velocity);
    }
    final Tolerance tolerance = this.tolerance;
    final double target = _getTargetPixels(position, tolerance, velocity);
    if (target != position.pixels) {
      return ScrollSpringSimulation(spring, position.pixels, target, velocity,
          tolerance: tolerance);
    }
    return null;
  }

  @override
  bool get allowImplicitScrolling => false;
}

like image 27
mik.corcuera Avatar answered Oct 19 '25 04:10

mik.corcuera



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!