FetFrumos
FetFrumos

Reputation: 5944

Flutter: How scroll programmatically and periodic(from stream)?

I have flutter widget. It is SingleChildScrollView with children + "middle cursor":

Text

I can scroll this list left and right - the cursor always remains in the middle. Now I need to scroll programmatically the "children" relative to the cursor with a frequency of one second.

I tried adding a timer and a thread and processing this in my widget. Also I added SteamBuilder in my widget, but I get an error in one frame.

My error frame:

enter image description here

My normal frame:

enter image description here

This error occurs after one frame. Any ideas how to fix this - u will be very grateful.

This is my full code:

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

class StartApp extends StatefulWidget {
  @override
  _StartAppState createState() => _StartAppState();
}

class _StartAppState extends State<StartApp> {
  StreamController<double> _currentPositionController;
  double position = 0;
  Timer _progressTimer;

  void positionUpdate(Timer timer) {
    position += 0.550;
    _currentPositionController.add(position);
  }

  @override
  void initState() {
    super.initState();
    _progressTimer = Timer.periodic(
        Duration(milliseconds: 350), positionUpdate);
    _currentPositionController = StreamController<double>.broadcast();
  }

  @override
  void dispose() {
    _progressTimer?.cancel();
    _currentPositionController?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: TmpPage(currentPosition:_currentPositionController.stream ),
      ),
    );
  }
}

This is my widget(TmpPage) :

import 'package:flutter/material.dart';
import 'dart:ui';

class TmpPage extends StatefulWidget {
  final Stream<double> currentPosition;

  const TmpPage({Key key, this.currentPosition}) : super(key: key);
  @override
  _TmpPageState createState() => _TmpPageState();
}

class _TmpPageState extends State<TmpPage> with WidgetsBindingObserver {

  double before = 0;
  double halfWidth;
  double leftPosition;
  ScrollController _controller;

  @override
  void initState() {

    WidgetsBinding.instance.addObserver(this);
    halfWidth =0;
    _controller = ScrollController();
    _controller.addListener(_scrollListener);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _controller.removeListener(_scrollListener);
    _controller.dispose();
    super.dispose();
  }

  double width = 0.0;
  double height = 0.0;

  @override void didChangeMetrics() {
    setState(() {
      width = window.physicalSize.width;
      height = window.physicalSize.height;
      halfWidth = window.physicalSize.width / 2;
      //halfWidth = (MediaQuery.of(context).size.width) / 2;
      leftPosition = halfWidth;
    });
  }

  @override
  Widget build(BuildContext context) {
    return _getBody(context);
  }

  Widget _getBody(context) {
    if(halfWidth ==0)
      halfWidth = (MediaQuery.of(context).size.width) / 2;
    if (before == 0)
      leftPosition = halfWidth;
    return StreamBuilder<double>(
      stream: widget.currentPosition,
      builder: (context, snapshot) {
        if(snapshot.data !=null && _controller.hasClients){
            _controller.jumpTo(snapshot.data);
          }
        return NotificationListener<ScrollNotification>(
          onNotification: (scrollNotification) {
            if (scrollNotification is ScrollUpdateNotification) {
              var m = scrollNotification.metrics;
              before = m.extentBefore;
              setState(() {
                leftPosition = before;
              });
              return false;
            }
            return false;
          },
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              SingleChildScrollView(
                controller: _controller,
                  scrollDirection: Axis.horizontal,
                  child: Row(
                    children: <Widget>[
                      SizedBox(
                        width: halfWidth,
                      ),
                      Stack(
                        children: <Widget>[
                          Row(
                            children: List.generate(
                                30,
                                    (i) => Padding(
                                  padding: const EdgeInsets.all(2.0),
                                  child: new Container(
                                    height: 42.0,
                                    width: 42.0,
                                    color: _getColor(i),
                                  ),
                                )).toList(),
                          ),
                          Positioned(
                            left: leftPosition,
                            top: 0,
                            bottom: 0,
                            child: Container(
                              color: Colors.red,
                              width: 2,
                            ),
                          ),
                        ],
                      ),
                      SizedBox(
                        width: halfWidth,
                      ),
                    ],
                  )),
            ],
          ),
        );
      }
    );
  }

   Color _getColor(int i) {
    if(i<10)
      return Colors.blueGrey;
    if(i<20)
      return Colors.orange;
    if(i<30)
      return Colors.lightBlueAccent;
    else
      return Colors.green;

  }


  void _scrollListener() {
    if (_controller.offset >= _controller.position.maxScrollExtent &&
        !_controller.position.outOfRange) {
      print('end');
      _controller.jumpTo(_controller.offset - 1);
    }
    if (_controller.offset <= _controller.position.minScrollExtent &&
        !_controller.position.outOfRange) {
      print('start');
      _controller.jumpTo(_controller.offset + 1);
    }
  }
}

Upvotes: 0

Views: 540

Answers (1)

Gregory Conrad
Gregory Conrad

Reputation: 1577

You cannot trigger a build from within the build method, like with setState. Thus, you can wrap any calls to setState with

WidgetsBinding.instance.addPostFrameCallback((_) {
  // Your code here
});

in order to call the code at the end of the frame, after the build method completes.

Upvotes: 1

Related Questions