Flutter customPainter doesn't repaint - flutter

shouldRepaint doesn't repaint when there is a change:
import 'package:flutter/material.dart';
class MainCanvas extends CustomPainter{
static var penPointsToRender = [];
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()..color=Colors.black..strokeWidth=4;
renderPenPoints(paint, canvas, 2);
}
static void renderPenPoints(Paint paint, Canvas canvas, double pointsRadius){
for (int i = 0; i < penPointsToRender.length - 1; i++) {
if (penPointsToRender[i + 1] != null && penPointsToRender[i] != null) {
canvas.drawLine(penPointsToRender[i], penPointsToRender[i + 1], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
this list get's updated on every touch.
I have this custom gesture detector that uses a provider as it's state management system:
import 'package:flutter/material.dart';
import 'package:flutter_canvas/Widgets/Models/canvas_gesture_detector_model.dart';
class CanvasGestureDetector extends StatefulWidget {
CanvasGestureDetector(this.canvasGestureDetectorModel,{Key key,this.child}) : super(key: key);
CanvasGestureDetectorModel canvasGestureDetectorModel;
Widget child;
#override
_CanvasGestureDetectorState createState() => _CanvasGestureDetectorState();
}
class _CanvasGestureDetectorState extends State<CanvasGestureDetector> {
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanDown: widget.canvasGestureDetectorModel.onPanDown,
onPanUpdate: widget.canvasGestureDetectorModel.onPanUpdate,
onPanEnd: widget.canvasGestureDetectorModel.onPanEnd,
child: widget.child,
);
}
}
the model of the detector is:
import 'package:flutter/material.dart';
import 'package:flutter_canvas/Widgets/Canvas/canvas.dart';
import 'package:flutter_canvas/Widgets/Canvas/canvas_area.dart';
class CanvasGestureDetectorModel with ChangeNotifier{
Function(DragDownDetails) onPanDown;
Function(DragUpdateDetails) onPanUpdate;
Function(DragEndDetails) onPanEnd;
void changeGestureBehavior(BuildContext context,String toolName){
switch(toolName){
case "Brush":
setBrushGestureDetection(context);
break;
}
}
void setBrushGestureDetection(BuildContext context){
onPanDown = (panInfo){
MainCanvas.penPointsToRender.add(panInfo.localPosition);
};
onPanUpdate = (panInfo){
if(
panInfo.globalPosition.dx < CanvasArea.canvasPaddingHorizontal / 2 ||
panInfo.globalPosition.dx > CanvasArea.canvasPaddingHorizontal / 2 + (MediaQuery.of(context).size.width - CanvasArea.canvasPaddingHorizontal) ||
panInfo.globalPosition.dy < CanvasArea.canvasPaddingVertical / 2 - 20 ||
panInfo.globalPosition.dy > (MediaQuery.of(context).size.height - CanvasArea.canvasPaddingVertical) + (CanvasArea.canvasPaddingVertical ) / 2 -20
){return;}
if (MainCanvas.penPointsToRender.elementAt(
MainCanvas.penPointsToRender.length - 1) != panInfo.localPosition) {
MainCanvas.penPointsToRender.add(panInfo.localPosition);
}
};
onPanEnd = (panInfo){
MainCanvas.penPointsToRender.add(null);
};
notifyListeners();
}
}
from some reason,
when I update the number of penPointsToRender it doesn't cause the customPainter to repaint, even though it updates and adds the points to the static list.
I have no idea what causes this, any suggestions would be appreciated

Related

How to implement a setup function and draw loop function like processing framework in flutter?

I am successful in drawing a chaos game fractal in flutter with custom paint, it needs 200000 iterations to render a complete fractal. The custom paint widget only shows up after it completes all the iterations. But I want to subsequently draw to the screen, not all at once.
Supporting Code
class Pair<T> {
T x;
T y;
Pair(this.x, this.y);
#override
String toString() {
return "{$x,$y}";
}
}
Color bgColor = Color(0xFF2C394B);
Color screenBgColor = Color(0xFF082032);
Color btnColor = Color(0xFFFF4C29);
Color btnTextColor = Color(0xFF2C394B);
Main Code
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:fractals/constants.dart';
import 'package:fractals/home_page_viewmodel.dart';
import 'package:fractals/utils.dart';
import 'package:provider/provider.dart';
class ChaosGameFractalPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var random = Random();
var center = size / 2;
randomHeight() => (size * random.nextDouble()).height;
randomWidth() => (size * random.nextDouble()).width;
List<Pair> points = [];
var n = 5;
for (int i = 0; i < n; i++) {
var angle = i * (360 / n);
var radians = angle * (pi / 180);
var loc = Pair((cos(radians)), sin(radians));
loc.x *= 200;
loc.y *= 200;
loc.x += center.width;
loc.y += center.height;
print(loc);
points.add(loc);
}
var current = Pair(
lerpDouble(points[0].x, points[1].x, random.nextDouble())!,
lerpDouble(points[0].y, points[1].y, random.nextDouble())!);
var paint = Paint()..color = btnColor;
canvas.drawCircle(Offset(current.x, current.y), 1, paint);
for (int i = 0; i < n; i++) {
canvas.drawCircle(Offset(points[i].x, points[i].y), 1, paint);
}
void render(List<Pair<dynamic>> points, Random random, Pair<double> current,
Canvas canvas, Paint paint) {
var previous;
for (int i = 0; i < 200000; i++) {
var next = points[random.nextInt(points.length)];
if (next != previous) {
current.x = lerpDouble(current.x, next.x, .5)!;
current.y = lerpDouble(current.y, next.y, .5)!;
canvas.drawCircle(Offset(current.x, current.y), .5, paint);
}
previous = next;
}
}
render(points, random, current, canvas, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
class ChaosGameFractal extends StatelessWidget {
const ChaosGameFractal({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Consumer<HomePageViewModel>(
builder: (context, value, child) => CustomPaint(
size: Size(400, 500), foregroundPainter: ChaosGameFractalPainter()),
),
);
}
}

Flame-engine, Draggable component doesn't work

I am completely newbie in flutter world specially flame-engine. Maybe it's a simple issue. But currently I couldn't figure it out. So please help me!
The main issue is my Draggable component doesn't work all event handler functions seem like doesn't not work correctly. Do you have any idea?
Draggable component code:
import 'package:flame/components.dart';
import 'package:flame/input.dart';
// Zooh bolomjtoi component
class DrawerComponent extends PositionComponent with Draggable {
late final mainRectangle;
Vector2? dragDeltaPosition;
bool get isDragging => dragDeltaPosition != null;
#override
Future<void>? onLoad() async {
final bg = await Sprite.load('drawer.png');
final localSize = Vector2(350, 210);
final mainRectangle = SpriteComponent(size: localSize, sprite: bg);
mainRectangle.position = isDragging ? (dragDeltaPosition)! : Vector2(0, 0);
add(mainRectangle);
}
#override
bool onDragStart(DragStartInfo info) {
print("Drag ehelleee..");
dragDeltaPosition = info.eventPosition.game - position;
return false;
}
#override
bool onDragUpdate(DragUpdateInfo info) {
print("Drag update..");
if (isDragging) {
final localCoords = info.eventPosition.game;
position.setFrom(localCoords - dragDeltaPosition!);
}
return false;
}
#override
bool onDragEnd(DragEndInfo info) {
print("Drag end...");
dragDeltaPosition = null;
return false;
}
#override
bool onDragCancel() {
dragDeltaPosition = null;
return false;
}
}
Main FlameGame class:
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:furniture/drawer.dart';
class Core extends FlameGame with HasDraggables {
late final SpriteComponent mainRectangle;
#override
Future<void>? onLoad() async {
final bg = await Sprite.load('example.jpeg');
final localSize = Vector2(super.size.x * 0.7, super.size.y * 0.6);
final mainRectangle = SpriteComponent(size: localSize, sprite: bg);
mainRectangle.position = Vector2(super.size.x * 0.15, super.size.y * 0.2);
mainRectangle.angle = 0;
add(mainRectangle);
add(DrawerComponent());
}
}
Flutter main class:
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:furniture/core.dart';
void main() {
final game = Core();
runApp(GameWidget(game: game));
}
I think your problem is into your onLoad() because you don't declare the values correctly, I have this example for you:
class Sushi extends SpriteComponent with Draggable, HasGameRef<JapaneseChef>{
/// Sushi constructor
Sushi( this._img, this._position, this._isMove, this._isDraggable,){
position = _position;
size = Vector2(250,250);
}
late final String _img;
late final Vector2 _position;
late Vector2 _dragDeltaPosition;
late final bool _isMove;
late final bool _isDraggable;
/// Get delta position
Vector2 get dragDeltaPosition => _dragDeltaPosition;
set dragDeltaPosition(Vector2? dragDeltaPosition) {
if(dragDeltaPosition != null){
_dragDeltaPosition = dragDeltaPosition;
}
}
#override
Future<void>? onLoad() async{
final spriteSushi = await Sprite.load(_img);
sprite = spriteSushi;
changePriorityWithoutResorting(5);
return super.onLoad();
}
#override
void update(double dt) {
if (_isMove) {
position.x -= 0.6;
if (position.x < -size.x) {
remove(this);
}
}
super.update(dt);
}
#override
bool onDragStart(int pointerId, DragStartInfo info) {
if(_isDraggable) {
dragDeltaPosition = info.eventPosition.game - position;
}
return false;
}
#override
bool onDragCancel(int pointerId) {
dragDeltaPosition = null;
return super.onDragCancel(pointerId);
}
#override
bool onDragUpdate(int pointerId, DragUpdateInfo info) {
if (parent is! JapaneseChef) {
return true;
}
final dragDeltaPosition = this.dragDeltaPosition;
position.setFrom(info.eventPosition.game - dragDeltaPosition);
return false;
}
#override
bool onDragEnd(int pointerId, DragEndInfo info) {
dragDeltaPosition = null;
return false;
}
}
Also, you can see the example from Flame engine
Example result

Flutter trigonometry animation elements not reaching their desired positions at the same time

The animation begins like this, with both circles in the center, but ends up where one circle reaches the end faster for some reason. How could I make it so that they reach the ends at the same time? I don't understand why this code isn't working. Any help would be much appreciated, thank you!
#override
void paint(Canvas canvas, Size size) {
var percentFinished = _animation.value;
var middleY = size.height / 2;
var middleX = size.width / 2;
double radius = 40;
var angles = [180, 0];
for (var angle in angles) {
var radians = (angle * pi) / 180;
var endingX = middleX + cos(radians) * radius;
var endingY = middleY - sin(radians) * radius;
var addToY = negativeOrPositiveOrZero(sin(radians)) * percentFinished;
var addToX = negativeOrPositiveOrZero(cos(radians)) * percentFinished;
canvas.drawCircle(Offset(endingX * addToX + middleX, endingY * addToY + middleY), 10, Paint());
}
}
int negativeOrPositiveOrZero(double a) {
int num = a.toInt();
if (num > 0){
print("1" + ": " + num.toString());
return 1;
}
else if (num < 0) {
return -1;
}
else {
return 0;
}
}
Below is just some screenshots of what I'm talking about.
The animation starts like this, with two balls in the center
But it ends in this state where one circle reaches the end before the other. The desired behavior is to have them reach their side of the screen at the same time.
I think your problem is how you compute your endingX.
var endingX = middleX + cos(radians) * radius;
It seems that your endingX should be the distance between the side of the Canvas and the perimeter of the Circles in their initial position. It's, therefore, the same for both directions:
var endingX = middleX - radius;
Then, a few simplifications on your code:
negativeOrPositiveOrZero
You have a getter for that in dart:math: sign
Trigonometry
I suppose the sample you posted is much simpler than your real code and the simplifications hereafter are probably not meaningful.
However, pay attention that near zero calculus on computers is quite messy!
import 'dart:math';
void main() {
print(' sin(0).sign: ${sin(0).sign}');
print(' sin(0).sign: ${sin(0).sign}');
print('------------------------------------------');
print(' sin(180*pi/180): ${sin(180*pi/180)}');
print('!! sin(180*pi/180).sign: ${sin(180*pi/180).sign}');
}
sin(0).sign: 0
sin(0).sign: 0
------------------------------------------
sin(180*pi/180): 1.2246467991473532e-16
!! sin(180*pi/180).sign: 1
Full example after correction and simplification
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Rolling balls',
home: Scaffold(
body: Center(
child: Container(
width: 300,
height: 200,
color: Colors.amber.shade300,
child: const MyCustomPaint(),
),
),
),
);
}
}
class MyCustomPaint extends StatefulWidget {
const MyCustomPaint({Key? key}) : super(key: key);
#override
_MyCustomPaintState createState() => _MyCustomPaintState();
}
class _MyCustomPaintState extends State<MyCustomPaint>
with SingleTickerProviderStateMixin {
late Animation<double> _animation;
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 4),
);
Tween<double> _animationTween = Tween(begin: 0.0, end: 1.0);
_animation = _animationTween.animate(_controller)
..addListener(() => setState(() {}))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: MyCustomPainter(_animation.value),
);
}
}
class MyCustomPainter extends CustomPainter {
final double percentFinished;
MyCustomPainter(this.percentFinished);
#override
void paint(Canvas canvas, Size size) {
double middleY = size.height / 2;
double middleX = size.width / 2;
double radius = size.width / 20;
Paint paint = Paint()..color = Colors.black;
for (int direction in [1, -1]) {
var endingX = middleX - radius;
var addToX = direction * percentFinished;
canvas.drawCircle(
Offset(endingX * addToX + middleX, middleY),
radius,
paint,
);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

Keep a widget at a relative point even when image get's scaled

Since a few days I'm trying to keep a drawn circle (using a CustomPainter) on an image while the image gets scaled (zoom in/out). To render the image and enable the zoom gesture I use photo_view. I can move the circle according to the change of the view port as long as nothing gets scaled. All values (scale,offset) are provided by the PhotoViewController as a stream as shown below:
main.dart
import 'package:backtrack/markerPainter.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'dart:math' as math;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
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> {
Offset position;
Offset startPosition;
Offset scaleChange;
Offset offsetToCenter;
double initialScale;
Offset forPoint;
PhotoViewScaleStateController controller;
PhotoViewController controller2;
double lastscale;
#override
void initState() {
super.initState();
controller = PhotoViewScaleStateController();
controller2 = PhotoViewController();
controller2.outputStateStream.listen((onData) {
// print("position change: " + onData.position.toString());
// print("scale change: " + onData.scale.toString());
if (position != null) {
setState(() {
final scaleToUser = initialScale + (initialScale - onData.scale);
//Change to the unscaled point inverted to reflect the opposite movement of the user
final newOffset = (offsetToCenter - onData.position) * -1;
print("new offset: " + newOffset.toString());
if (onData.scale != lastscale) {
if (onData.scale > initialScale) {
//zoom in
var zoomeChnage = initialScale-onData.scale;
scaleChange = startPosition * zoomeChnage;
print("new scaleChange: " + scaleChange.toString());
} else {
//zoom out
}
lastscale = onData.scale;
}
forPoint = newOffset-scaleChange;
print("new forPoint: " + forPoint.toString());
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildMap(context));
}
Widget _buildMap(BuildContext context) {
return Container(
child: CustomPaint(
foregroundPainter: MyPainter(position, forPoint),
child: PhotoView(
imageProvider: AssetImage("asset/map.JPG"),
onTapUp: (a, b, c) {
setState(() {
position = b.localPosition;
offsetToCenter = c.position;
forPoint = Offset.zero;
initialScale = c.scale;
print("localPosition: " + b.localPosition.toString());
print("offsetToCenter: " + offsetToCenter.toString());
print("initialScale: " + initialScale.toString());
});
},
scaleStateController: controller,
controller: controller2,
),
),
);
}
}
Just in case, below is the widget that renders the circle on top of the image.
markerPainter.dart
class MyPainter extends CustomPainter {
final Offset position;
final Offset centerOffset;
final Paint line;
MyPainter(this.position, this.centerOffset)
: line = new Paint()
..color = Colors.pink
..style = PaintingStyle.fill
..strokeWidth = 5;
#override
void paint(Canvas canvas, Size size) {
if (position == null) {
return;
}
canvas.drawCircle(position + centerOffset, 5, line);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
I really tried a lot but it looks like my brain can't solve it, any help is very welcome.
I found a solution, using this helper class I can calculate a relative point for an absolute point:
FlatMapState(
{this.imageScale,
this.initialViewPort,
this.imageSize,
this.viewPortDelta,
this.viewPortOffsetToCenter,
this.currentViewPort});
Offset absolutePostionToViewPort(Offset absolutePosition) {
var relativeViewPortPosition =
((imageSize * imageScale).center(Offset.zero) -
absolutePosition * imageScale) *
-1;
relativeViewPortPosition += viewPortOffsetToCenter;
return relativeViewPortPosition + viewPortDelta / -2;
}
}
The class gets more or less all information from the PhotoViewController update event like this:
bool _photoViewValueIsValid(PhotoViewControllerValue value) {
return value != null && value.position != null && value.scale != null;
}
FlatMapState createCurrentState(PhotoViewControllerValue photoViewValue) {
if (_photoViewValueIsValid(photoViewValue) && imageSize != null) {
if (lastViewPort == null) {
lastViewPort = stickyKey.currentContext.size;
}
return FlatMapState(
imageScale: photoViewValue.scale,
imageSize: imageSize,
initialViewPort: lastViewPort,
viewPortDelta: Offset(
lastViewPort.width - stickyKey.currentContext.size.width,
lastViewPort.height - stickyKey.currentContext.size.height),
viewPortOffsetToCenter: photoViewValue.position,
currentViewPort: Offset(stickyKey.currentContext.size.width,
stickyKey.currentContext.size.height),
);
} else {
//The map or image is still loading, the state can't be generated
return null;
}
}
Please note that the calculated value has to be drawn by setting the point of origin to the center like this:
#override
void paint(Canvas canvas, Size size) {
//In case the state is null or nothing to draw leave
if(flatMapState == null || drawableElements == null){
return;
}
//Center the canvas (0,0) is now the center, done to have the same origion as photoview
var viewPortCenter = flatMapState.initialViewPort.center(Offset.zero);
canvas.translate(viewPortCenter.dx, viewPortCenter.dy);
drawableElements.forEach((drawable) => drawable.draw(
canvas,
size,
flatMapState,
));
}

flutter PageView with dynamic boundary using custom ScrollPhysics

I'm trying to set PageView boundary dynamically.
Here's a simplified example, which starts page 10, and left and right boundary is set by random number generator (leftEnd, rightEnd).
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyTabbedPage(),
);
}
}
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({Key key}) : super(key: key);
#override
_MyTabbedPageState createState() => new _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
final leftEnd = Random().nextInt(5);
final rightEnd = 10 + Random().nextInt(5);
CustomScrollPhysics scrollPhysics = CustomScrollPhysics();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: PageView.builder(
controller: PageController(initialPage: 10),
physics: scrollPhysics,
itemBuilder: (context, index) {
scrollPhysics.leftEnd = (index <= leftEnd);
scrollPhysics.rightEnd = (index >= rightEnd);
// --------- print (1) ----------
print("is leftEnd: ${index <= leftEnd}");
print("is rightEnd: ${index >= rightEnd}");
print("scrollphysics.leftEnd: ${scrollPhysics.leftEnd}");
print("scrollphysics.rightEnd: ${scrollPhysics.rightEnd}");
return Center(
child: Text("Item $index"),
);
}
));
}
}
class CustomScrollPhysics extends ScrollPhysics {
CustomScrollPhysics({ScrollPhysics parent}) : super(parent: parent);
bool leftEnd = false;
bool rightEnd = false;
bool isGoingLeft = false;
#override
CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomScrollPhysics(parent: buildParent(ancestor));
}
#override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
isGoingLeft = offset.sign < 0;
return offset;
}
#override
double applyBoundaryConditions(ScrollMetrics position, double value) {
//print("applyBoundaryConditions");
assert(() {
if (value == position.pixels) {
throw FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
if (value < position.pixels && position.pixels <= position.minScrollExtent)
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value)
// overscroll
return value - position.pixels;
if (value < position.minScrollExtent &&
position.minScrollExtent < position.pixels) // hit top edge
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent &&
position.maxScrollExtent < value) // hit bottom edge
return value - position.maxScrollExtent;
// --------- print (2) ----------
if (leftEnd) print("leftEnd");
if (rightEnd) print("rightEnd");
if (isGoingLeft) print("isGoingLeft");
if (leftEnd && !isGoingLeft) {
return value - position.pixels;
} else if (rightEnd && isGoingLeft) {
return value - position.pixels;
}
return 0.0;
}
}
scrollphysics.leftEnd/rightEnd changes inside PageView.builder (based on the print (1)), but it doesn't change in CustomScrollPhysics (no print (2)).
Could anyone explain what's happening here?
Is this the right way to set a dynamic boundary for PageView?
ScrollPhysics which your CustomScrollPhysics is extending from is marked as immutable. Even though you are changing its booleans inside your itemBuilder the actual booleans are not changed (as seen by not printing leftEnd and rightEnd in applyBoundaryConditions). I don't have an official explanation as why scrollPhysics.leftEnd in your itemBuilder shows the desired changed boolean while it didn't change in it's class itself but i would guess it is because you didn't set those variables as final as they should be and nothing is holding you back to change them so your local scrollPhysics in _MyTabbedPageState is showing those changes even though they are not changed internally. isGoingLeft is only getting printed because it is being changed inside the CustomScrollPhysics itself rather then from the outside as seen in itemBuilder.
As a quick fix i created another class:
class Status {
bool leftEnd = false;
bool rightEnd = false;
bool isGoingLeft = false;
}
changed your CustomScrollPhysics a bit:
class CustomScrollPhysics extends ScrollPhysics {
final Status status;
CustomScrollPhysics(this.status, {ScrollPhysics parent})
: super(parent: parent);
#override
CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomScrollPhysics(this.status, parent: buildParent(ancestor));
}
...
and added a Status instance inside the constructor call in your state:
CustomScrollPhysics scrollPhysics = CustomScrollPhysics(Status());
Apply every change to this.status inside CustomScrollPhysics. Now it should work as you intended.