-
Notifications
You must be signed in to change notification settings - Fork 30.2k
InteractiveViewer focalLocalPoint doesn't give correct location in the child container when zoomed in or scaled #145215
Copy link
Copy link
Closed
Description
Steps to reproduce
- Click on a specific location in InteractiveViewer and check
ScaleStartDetails.localFocalPointdata. - Zoom the clicked location
- Click again at the same point while zoomed
- Check again
ScaleStartDetails.localFocalPointit's different from the first one
Expected results
ScaleStartDetails.localFocalPoint should point the exact same location of the child container whether InteractiveViewer zoomed or not.
Actual results
ScaleStartDetails.localFocalPoint changes when zoomed and point incorrect location in the child container.
Code sample
Code sample
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Painting',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
useMaterial3: true,
),
home: const MyHomePage(title: 'CustomPainter in InteractiveViewer'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final TransformationController _transformationController;
final List<Painting> _stack = [];
PaintingMode _mode = PaintingMode.Line;
@override
void initState() {
super.initState();
_transformationController = TransformationController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
actions: [
IconButton(
onPressed: () {
if (_mode != PaintingMode.Pan) {
setState(() {
_mode = PaintingMode.Pan;
});
}
},
icon: const Icon(Icons.pan_tool),
iconSize: 24,
color: _mode == PaintingMode.Pan
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
IconButton(
onPressed: () {
if (_mode != PaintingMode.RRectangular) {
setState(() {
_mode = PaintingMode.RRectangular;
});
}
},
icon: const Icon(Icons.crop_square),
iconSize: 24,
color: _mode == PaintingMode.RRectangular
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
IconButton(
onPressed: () {
if (_mode != PaintingMode.Line) {
setState(() {
_mode = PaintingMode.Line;
});
}
},
icon: const Icon(Icons.draw),
iconSize: 24,
color: _mode == PaintingMode.Line
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
TextButton(
onPressed: () {
_stack.clear();
},
child: const Text("Clear")),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: FittedBox(
child: ClipRect(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.8,
child: InteractiveViewer(
transformationController: _transformationController,
scaleEnabled: _mode == PaintingMode.Pan,
panEnabled: _mode == PaintingMode.Pan,
onInteractionStart: (details) =>
_onPanStart(Colors.red, 8, details),
onInteractionUpdate: (details) => _onPanUpdate(details),
child: CustomPaint(
painter: Painter(_stack),
),
),
),
),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Text(
"localFocalPoint is not correct while zoomed",
style: Theme.of(context).textTheme.headlineLarge,
),
)
],
),
),
);
}
_onPanStart(Color color, double stroke, ScaleStartDetails details) {
if (_mode == PaintingMode.Pan) {
return;
}
final scene = _transformationController.toScene(details.focalPoint);
print("""
Panning started,
scene: $scene,
focal: ${details.focalPoint},
focalLocal: ${details.localFocalPoint},
""");
_paintingStart(details.localFocalPoint, color, stroke);
}
void _onPanUpdate(ScaleUpdateDetails details) {
if (_mode == PaintingMode.Pan) {
return;
}
final scene = _transformationController.toScene(details.focalPoint);
print("""
Panning started,
scene: $scene,
focal: ${details.focalPoint},
focalLocal: ${details.localFocalPoint},
focalDelta: ${details.focalPointDelta},
delta: ${details.focalPointDelta},
""");
_paintingUpdate(details.localFocalPoint);
}
void _paintingStart(Offset startPoint, Color color, double stroke) {
if (_mode == PaintingMode.Line) {
setState(() {
_stack.add(Painting([startPoint], color, stroke, _mode));
});
} else if (_mode == PaintingMode.RRectangular) {
// Make left top and right bottom corner same point if user only taps once
setState(() {
_stack.add(Painting([startPoint, startPoint], color, stroke, _mode));
});
}
}
void _paintingUpdate(Offset value) {
final index = _stack.length - 1;
if (_mode == PaintingMode.Line) {
// Add every point
setState(() {
_stack[index].points.add(value);
});
} else if (_mode == PaintingMode.RRectangular) {
// Update right bottom corner
setState(() {
_stack[index].points[1] = value;
});
}
}
}
enum PaintingMode {
Pan,
Line,
RRectangular;
}
// Data class represent drawing event
class Painting {
final List<Offset> points;
final Color color;
final double stroke;
final PaintingMode mode;
Painting(this.points, this.color, this.stroke, this.mode);
}
class Painter extends CustomPainter {
final List<Painting> _paintings;
Painter(this._paintings);
@override
void paint(ui.Canvas canvas, Size size) {
if (_paintings.isEmpty) return;
for (var painting in _paintings) {
if (painting.mode == PaintingMode.Line) {
_drawLine(canvas, painting);
} else if (painting.mode == PaintingMode.RRectangular) {
_drawRRect(canvas, painting);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
_drawRRect(ui.Canvas canvas, Painting painting) {
final paint = Paint()
..style = PaintingStyle.stroke
..color = painting.color
..isAntiAlias = true
..strokeWidth = painting.stroke;
final topLeftCornerOffset = painting.points[0];
final topRightCornerOffset = painting.points[1];
final rect = Rect.fromPoints(topLeftCornerOffset, topRightCornerOffset);
final rrect = RRect.fromRectAndRadius(rect, Radius.circular(8));
canvas.drawRRect(rrect, paint);
}
_drawLine(ui.Canvas canvas, Painting painting) {
final paint = Paint()
..color = painting.color
..style = PaintingStyle.stroke
..isAntiAlias = true
..strokeWidth = painting.stroke;
for (var i = 0; i < painting.points.length; i++) {
if (i < painting.points.length - 1) {
canvas.drawLine(painting.points[i], painting.points[i + 1], paint);
} else {
canvas.drawPoints(ui.PointMode.points, [painting.points[i]], paint);
}
}
}
}Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Screen.Recording.2024-03-15.at.13.29.40.mov
Logs
Logs
[Paste your logs here]Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.19.3, on macOS 14.2.1 23C71 darwin-arm64, locale en-TR)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] IntelliJ IDEA Community Edition (version 2023.3.3)
[✓] VS Code (version 1.87.2)
[✓] Connected device (2 available)
[✓] Network resources
• No issues found!Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels