Anonymous
Рисунок холста Flutter исчезает при попытке увеличить/уменьшить масштаб
Сообщение
Anonymous » 26 сен 2024, 20:34
У меня есть приложение, в котором линии рисуются в зависимости от чисел, которые вводит пользователь, и от места, где пользователь нажимает. Я добавил кнопки для увеличения и уменьшения масштаба в дочернее поле моего класса CustomPainter. Там я обновляю параметр _scaleFactor, который затем передаю в класс рисования, где он используется для увеличения или уменьшения линий. Я также загружаю изображение ресурса и не уверен, повлияет ли оно на мою проблему.
Проблема в том, что каждый раз, когда я нажимаю на одну из кнопок, все на холсте действительно масштабируется, и вы можете видите его на секунду, но после этого все исчезает, и вы можете заставить его появиться, только перетаскивая палец по экрану (полагаю, виджет GestureDetector начинает работать).
Это мой код:< /p>
Код: Выделить всё
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
static const PREFERENCES_IS_FIRST_LAUNCH_STRING =
"PREFERENCES_IS_FIRST_LAUNCH_STRING";
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State
with SingleTickerProviderStateMixin {
final myController1 = TextEditingController();
final myController2 = TextEditingController();
final myController3 = TextEditingController();
final myController4 = TextEditingController();
bool isComplete = false;
int ratio1 = 10;
int ratio2 = 10;
int ratio3 = 10;
int ratio4 = 10;
double _progress1 = 0.0;
late Animation animation1;
late AnimationController controller1;
var tappedPoint = const Offset(0, 0);
double _scaleFactor = 35.0;
double _baseScaleFactor = 35.0;
bool _dragging = false;
var xPos = 0.0;
var yPos = 0.0;
int _numRoots = 0;
final List _res = [];
final List _binRes = [];
String _roots = "";
bool shown = false;
final GlobalKey run = GlobalKey();
final GlobalKey input = GlobalKey();
final GlobalKey xPoint = GlobalKey();
final GlobalKey pOfX = GlobalKey();
final GlobalKey keyNumRoots = GlobalKey();
final GlobalKey drawing = GlobalKey();
late Future _imageFuture;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_isFirstLaunch().then((result) {
if (result) {
ShowCaseWidget.of(context)
.startShowCase([input, run, xPoint, pOfX, keyNumRoots, drawing]);
}
});
});
controller1 = AnimationController(
duration: const Duration(milliseconds: 3000), vsync: this)
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) {
isComplete = true;
} else {
isComplete = false;
}
});
animation1 = Tween(begin: 0.0, end: 1.0).animate(controller1)
..addListener(() {
setState(() {
_progress1 = animation1.value;
});
});
_imageFuture = _loadImage("assets/images/turtle.png");
}
Future _isFirstLaunch() async {
final sharedPreferences = await SharedPreferences.getInstance();
bool isFirstLaunch = sharedPreferences
.getBool(MyHomePage.PREFERENCES_IS_FIRST_LAUNCH_STRING) ??
true;
if (isFirstLaunch) {
sharedPreferences.setBool(
MyHomePage.PREFERENCES_IS_FIRST_LAUNCH_STRING, false);
}
return isFirstLaunch;
}
@override
void dispose() {
myController1.dispose();
myController2.dispose();
myController3.dispose();
myController4.dispose();
super.dispose();
}
Future _loadImage(String imagePath) async {
ByteData bd = await rootBundle.load(imagePath);
final Uint8List bytes = Uint8List.view(bd.buffer);
final ui.Codec codec = await ui.instantiateImageCodec(bytes,
targetHeight: 60, targetWidth: 60);
final ui.Image image = (await codec.getNextFrame()).image;
return image;
}
onPan(double x, double y) {
setState(() {
tappedPoint =
Offset(x, y);
MyPainter.setScale(_scaleFactor.toInt());
});
}
String calcDistance(String solution) {
double p = MyPainter.getP();
if (p.isNaN) {
return "0";
} else {
if ((p.abs()
toastification.show(
title: Text(AppLocalizations.of(context)!.allRoots),
style: ToastificationStyle.flatColored,
primaryColor: Colors.green,
autoCloseDuration: const Duration(seconds: 5)));
}
if (found) {
WidgetsBinding.instance.addPostFrameCallback((_) =>
toastification.show(
title: Text(AppLocalizations.of(context)!.congrats),
autoCloseDuration: const Duration(seconds: 3)));
}
}
}
return p.toStringAsFixed(5);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Turtle Math"), actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OnBoardingScreen()));
},
icon: const Icon(Icons.help_outline, color: Colors.white))
]),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Column(
children: [
AnimatedBuilder(
animation: controller1,
builder: (BuildContext context, _) {
return Showcase(
key: input,
description: AppLocalizations.of(context)!.coefficients,
child: Row(
children: [
const Spacer(flex: 1),
Expanded(
flex: 5,
child: SizedBox(
height: 50,
child: TextField(
textAlign: TextAlign.center,
controller: myController1,
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: "#1"),
),
),
),
Expanded(
flex: 5,
child: SizedBox(
height: 50,
child: TextField(
textAlign: TextAlign.center,
controller: myController2,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
// prefixIcon: const Icon(PiSymbol.pi_outline),
hintText: "#2",
),
),
),
),
Expanded(
flex: 5,
child: SizedBox(
height: 50,
child: TextField(
textAlign: TextAlign.center,
controller: myController3,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
// prefixIcon: const Icon(PiSymbol.pi_outline),
hintText: "#3",
),
),
),
),
Expanded(
flex: 5,
child: SizedBox(
height: 50,
child: TextField(
textAlign: TextAlign.center,
controller: myController4,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
// prefixIcon: const Icon(PiSymbol.pi_outline),
hintText: "#4",
),
),
),
),
Showcase(
key: run,
description:
AppLocalizations.of(context)!.startButton,
child: ElevatedButton(
onPressed: () {
setState(() {
if (myController1.text.isNotEmpty &
myController2.text.isNotEmpty &
myController3.text.isNotEmpty &
myController4.text.isNotEmpty) {
shown = false;
_res.clear();
_binRes.clear();
_numRoots = 0;
ratio1 = int.parse(myController1.text);
ratio2 = int.parse(myController2.text);
ratio3 = int.parse(myController3.text);
ratio4 = int.parse(myController4.text);
_roots = getRoots(
myController1.text,
myController2.text,
myController3.text,
myController4.text);
controller1.reset();
controller1.forward();
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text(
AppLocalizations.of(
context)!
.error)));
}
});
},
child:
Text(AppLocalizations.of(context)!.run))),
const Spacer(
flex: 1), // child: Text(Platform.localeName)),
],
),
);
},
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
getCoefficients(myController1.text, myController2.text,
myController3.text, myController4.text),
style: const TextStyle(fontSize: 18)),
),
Padding(
padding: const EdgeInsets.all(1.0),
child: Row(
children: [
Expanded(
child: Showcase(
key: xPoint,
description: AppLocalizations.of(context)!.xPoint,
child: Text(
"x = ${calcK(constraints.maxWidth / 2, constraints.maxHeight / 2)}",
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center),
),
),
Expanded(
child: Showcase(
key: pOfX,
description: AppLocalizations.of(context)!.pOfX,
child: Text(
"p(x) = ${calcDistance(calcK(constraints.maxWidth / 2, constraints.maxHeight / 2))}",
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center),
),
)
],
),
),
Padding(
padding: const EdgeInsets.all(1.0),
child:
// Text("$_roots ${_binRes.toString()} $_numRoots",
Showcase(
key: keyNumRoots,
description:
AppLocalizations.of(context)!.rootsDescription,
child: Text(
"${AppLocalizations.of(context)!.numRoots} $_numRoots",
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center),
)),
Expanded(
child: FutureBuilder(
future: _imageFuture,
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return GestureDetector(
// scaling
onScaleStart: (details) {
_dragging = true;
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (scaleDetails) {
if (scaleDetails.pointerCount == 3) {
setState(() {
_scaleFactor =
_baseScaleFactor * scaleDetails.scale;
});
}
if (scaleDetails.pointerCount == 1) {
onPan(scaleDetails.localFocalPoint.dx, scaleDetails.localFocalPoint.dy);
}
if (scaleDetails.pointerCount == 2) {
if (_dragging) {
setState(() {
xPos += scaleDetails.focalPointDelta.dx;
yPos += scaleDetails.focalPointDelta.dy;
});
}
}
},
onScaleEnd: (scaleDetails) {
_dragging = false;
},
// onPanUpdate: (details) => onPan(details),
child: Showcase(
key: drawing,
description:
AppLocalizations.of(context)!.drawing,
child: ClipRect(
child: CustomPaint(
painter: MyPainter(
constraints.maxWidth / 2,
constraints.maxHeight / 2,
ratio1,
ratio2,
ratio3,
ratio4,
isComplete,
_progress1,
tappedPoint,
xPos,
yPos,
snapshot.data),
size: Size(constraints.maxWidth,
constraints.maxHeight),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconButton(
onPressed: () {
setState(() {
_scaleFactor =
_baseScaleFactor - 5;
MyPainter.setScale(_scaleFactor.toInt());
});
},
icon: const Icon(Icons.zoom_out),
iconSize: 50,
),
IconButton(
onPressed: () {
setState(() {
_scaleFactor =
_baseScaleFactor + 5;
MyPainter.setScale(_scaleFactor.toInt());
});
},
icon: const Icon(Icons.zoom_in),
iconSize: 50,
)
],
),
),
),
),
);
} else {
return GestureDetector(
onScaleStart: (details) {
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (scaleDetails) {
if (scaleDetails.pointerCount == 3) {
setState(() {
_scaleFactor =
_baseScaleFactor * scaleDetails.scale;
});
}
if (scaleDetails.pointerCount == 1) {
onPan(scaleDetails.localFocalPoint.dx, scaleDetails.localFocalPoint.dy);
}
},
child: CustomPaint(
painter: MyPainter(
constraints.maxWidth / 2,
constraints.maxHeight / 2,
ratio1,
ratio2,
ratio3,
ratio4,
isComplete,
_progress1,
tappedPoint,
xPos,
yPos),
size: Size(constraints.maxWidth,
constraints.maxHeight)),
);
}
})),
],
);
},
));
}
}
class MyPainter extends CustomPainter {
ui.Image? image;
double _width = 0;
static double _height = 0;
static int _ratio1 = 0;
static int _ratio2 = 0;
static int _ratio3 = 0;
static int _ratio4 = 0;
static int _scale = 35;
final double _progress;
Offset tappedPoint;
static bool _isComplete = false;
static double k = 0;
double _xPos = 0;
double _yPos = 0;
MyPainter(width, height, ratio1, ratio2, ratio3, ratio4, isComplete,
this._progress, this.tappedPoint, this._xPos, this._yPos,
[this.image]) {
_width = width;
_height = height;
_ratio1 = ratio1;
_ratio2 = ratio2;
_ratio3 = ratio3;
_ratio4 = ratio4;
_isComplete = isComplete;
// _scale = (_width / 10).toInt();
}
static void setScale(int newScale) {
_scale = newScale;
}
static double getP() {
if (_isComplete == true) {
return (_ratio4 - k * (_ratio3 - k * (_ratio2 - k * _ratio1))) / _scale;
} else {
return -1;
}
}
static double getK() {
return k;
}
@override
void paint(Canvas canvas, Size size) {
Paint linePaint = Paint()
..strokeWidth = 20
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
Paint solutionPaint = Paint()
..strokeWidth = 8
..strokeCap = ui.StrokeCap.butt
..style = ui.PaintingStyle.stroke
..color = Colors.redAccent;
Paint imaginaryPaint = Paint()
..strokeWidth = 5
..strokeCap = ui.StrokeCap.butt
..style = ui.PaintingStyle.stroke
..color = const Color.fromRGBO(105, 105, 105, 0.5);
_ratio1 *= _scale;
_ratio2 *= _scale;
_ratio3 *= _scale;
_ratio4 *= _scale;
_width += _xPos;
_height += _yPos;
_width += (math.max(_ratio1, _ratio3) / 2) - _ratio1;
_height += (math.max(_ratio2, _ratio4) / 2);
var path = getPath();
Rect bounds = path.getBounds();
if (bounds.bottom >= size.height) {
path = path
.shift(Offset(0, -(bounds.bottom - _height + bounds.center.dy / 10)));
}
ui.PathMetrics pathMetrics = path.computeMetrics();
ui.PathMetric pathMetric = pathMetrics.elementAt(0);
final pos = pathMetric.getTangentForOffset(pathMetric.length * _progress);
Path extracted = pathMetric.extractPath(0.0, pathMetric.length * _progress);
linePaint.strokeWidth = 6;
canvas.drawPath(extracted, linePaint);
if (image == null) {
canvas.drawCircle(pos!.position, linePaint.strokeWidth / 2, linePaint);
} else {
Offset location = Offset(pos!.position.dx - image!.width / 2,
pos.position.dy - image!.height / 2);
canvas.save();
double cx = pos.position.dx;
double cy = pos.position.dy;
double angle = pos.angle;
if (angle == math.pi / 2) {
angle = -angle;
} else if (angle == -(math.pi / 2)) {
angle = math.pi / 2;
}
rotateTurtle(canvas: canvas, cx: cx, cy: cy, angle: angle);
canvas.drawPoints(ui.PointMode.points, [Offset(cx, cy)], linePaint);
canvas.drawImage(image!, location, linePaint);
canvas.restore();
if (_isComplete) {
// canvas.drawLine(Offset(_width, _height), tappedPoint, solutionPaint);
k = (tappedPoint.dy - _height) / (_width - tappedPoint.dx);
var solution = getSolution(k);
double shift = 0;
if (bounds.bottom >= size.height) {
shift = (bounds.bottom - _height + bounds.center.dy / 10);
solution = solution.shift(
Offset(0, -(bounds.bottom - _height + bounds.center.dy / 10)));
}
// imaginary lines
checkSolution(canvas, k, imaginaryPaint, extracted, shift);
canvas.drawPath(solution, solutionPaint);
}
}
}
Path getSolution(tangent) {
return Path()
..moveTo(_width, _height)
..lineTo(_width + _ratio1, _height - tangent * _ratio1)
..lineTo(_width + _ratio1 - tangent * (_ratio2 - tangent * _ratio1),
_height - _ratio2)
..lineTo(
_width + _ratio1 - _ratio3,
_height -
_ratio2 +
tangent * (_ratio3 - tangent * (_ratio2 - tangent * _ratio1)));
}
void rotateTurtle(
{required Canvas canvas,
required double cx,
required double cy,
required double angle}) {
canvas.translate(cx, cy);
canvas.rotate(angle);
canvas.translate(-cx, -cy);
}
Path getPath() {
return Path()
..moveTo(_width, _height)
..lineTo(_width + _ratio1, _height)
..lineTo(_width + _ratio1, _height - _ratio2)
..lineTo(_width + _ratio1 - _ratio3, _height - _ratio2)
..lineTo(_width + _ratio1 - _ratio3, _height - _ratio2 + _ratio4);
}
@override
bool shouldRepaint(covariant MyPainter oldDelegate) {
return (oldDelegate._progress != _progress);
}
}
Может кто-нибудь помочь мне понять, почему возникает эта задержка при нажатии кнопки масштабирования? Заранее спасибо.
Подробнее здесь:
https://stackoverflow.com/questions/790 ... oom-in-out
1727372087
Anonymous
У меня есть приложение, в котором линии рисуются в зависимости от чисел, которые вводит пользователь, и от места, где пользователь нажимает. Я добавил кнопки для увеличения и уменьшения масштаба в дочернее поле моего класса CustomPainter. Там я обновляю параметр _scaleFactor, который затем передаю в класс рисования, где он используется для увеличения или уменьшения линий. Я также загружаю изображение ресурса и не уверен, повлияет ли оно на мою проблему. Проблема в том, что каждый раз, когда я нажимаю на одну из кнопок, все на холсте действительно масштабируется, и вы можете видите его на секунду, но после этого все исчезает, и вы можете заставить его появиться, только перетаскивая палец по экрану (полагаю, виджет GestureDetector начинает работать). Это мой код:< /p> [code]class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); static const PREFERENCES_IS_FIRST_LAUNCH_STRING = "PREFERENCES_IS_FIRST_LAUNCH_STRING"; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State with SingleTickerProviderStateMixin { final myController1 = TextEditingController(); final myController2 = TextEditingController(); final myController3 = TextEditingController(); final myController4 = TextEditingController(); bool isComplete = false; int ratio1 = 10; int ratio2 = 10; int ratio3 = 10; int ratio4 = 10; double _progress1 = 0.0; late Animation animation1; late AnimationController controller1; var tappedPoint = const Offset(0, 0); double _scaleFactor = 35.0; double _baseScaleFactor = 35.0; bool _dragging = false; var xPos = 0.0; var yPos = 0.0; int _numRoots = 0; final List _res = []; final List _binRes = []; String _roots = ""; bool shown = false; final GlobalKey run = GlobalKey(); final GlobalKey input = GlobalKey(); final GlobalKey xPoint = GlobalKey(); final GlobalKey pOfX = GlobalKey(); final GlobalKey keyNumRoots = GlobalKey(); final GlobalKey drawing = GlobalKey(); late Future _imageFuture; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { _isFirstLaunch().then((result) { if (result) { ShowCaseWidget.of(context) .startShowCase([input, run, xPoint, pOfX, keyNumRoots, drawing]); } }); }); controller1 = AnimationController( duration: const Duration(milliseconds: 3000), vsync: this) ..addStatusListener((AnimationStatus status) { if (status == AnimationStatus.completed) { isComplete = true; } else { isComplete = false; } }); animation1 = Tween(begin: 0.0, end: 1.0).animate(controller1) ..addListener(() { setState(() { _progress1 = animation1.value; }); }); _imageFuture = _loadImage("assets/images/turtle.png"); } Future _isFirstLaunch() async { final sharedPreferences = await SharedPreferences.getInstance(); bool isFirstLaunch = sharedPreferences .getBool(MyHomePage.PREFERENCES_IS_FIRST_LAUNCH_STRING) ?? true; if (isFirstLaunch) { sharedPreferences.setBool( MyHomePage.PREFERENCES_IS_FIRST_LAUNCH_STRING, false); } return isFirstLaunch; } @override void dispose() { myController1.dispose(); myController2.dispose(); myController3.dispose(); myController4.dispose(); super.dispose(); } Future _loadImage(String imagePath) async { ByteData bd = await rootBundle.load(imagePath); final Uint8List bytes = Uint8List.view(bd.buffer); final ui.Codec codec = await ui.instantiateImageCodec(bytes, targetHeight: 60, targetWidth: 60); final ui.Image image = (await codec.getNextFrame()).image; return image; } onPan(double x, double y) { setState(() { tappedPoint = Offset(x, y); MyPainter.setScale(_scaleFactor.toInt()); }); } String calcDistance(String solution) { double p = MyPainter.getP(); if (p.isNaN) { return "0"; } else { if ((p.abs() toastification.show( title: Text(AppLocalizations.of(context)!.allRoots), style: ToastificationStyle.flatColored, primaryColor: Colors.green, autoCloseDuration: const Duration(seconds: 5))); } if (found) { WidgetsBinding.instance.addPostFrameCallback((_) => toastification.show( title: Text(AppLocalizations.of(context)!.congrats), autoCloseDuration: const Duration(seconds: 3))); } } } return p.toStringAsFixed(5); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Turtle Math"), actions: [ IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => OnBoardingScreen())); }, icon: const Icon(Icons.help_outline, color: Colors.white)) ]), body: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Column( children: [ AnimatedBuilder( animation: controller1, builder: (BuildContext context, _) { return Showcase( key: input, description: AppLocalizations.of(context)!.coefficients, child: Row( children: [ const Spacer(flex: 1), Expanded( flex: 5, child: SizedBox( height: 50, child: TextField( textAlign: TextAlign.center, controller: myController1, keyboardType: TextInputType.number, decoration: const InputDecoration(hintText: "#1"), ), ), ), Expanded( flex: 5, child: SizedBox( height: 50, child: TextField( textAlign: TextAlign.center, controller: myController2, keyboardType: TextInputType.number, decoration: const InputDecoration( // prefixIcon: const Icon(PiSymbol.pi_outline), hintText: "#2", ), ), ), ), Expanded( flex: 5, child: SizedBox( height: 50, child: TextField( textAlign: TextAlign.center, controller: myController3, keyboardType: TextInputType.number, decoration: const InputDecoration( // prefixIcon: const Icon(PiSymbol.pi_outline), hintText: "#3", ), ), ), ), Expanded( flex: 5, child: SizedBox( height: 50, child: TextField( textAlign: TextAlign.center, controller: myController4, keyboardType: TextInputType.number, decoration: const InputDecoration( // prefixIcon: const Icon(PiSymbol.pi_outline), hintText: "#4", ), ), ), ), Showcase( key: run, description: AppLocalizations.of(context)!.startButton, child: ElevatedButton( onPressed: () { setState(() { if (myController1.text.isNotEmpty & myController2.text.isNotEmpty & myController3.text.isNotEmpty & myController4.text.isNotEmpty) { shown = false; _res.clear(); _binRes.clear(); _numRoots = 0; ratio1 = int.parse(myController1.text); ratio2 = int.parse(myController2.text); ratio3 = int.parse(myController3.text); ratio4 = int.parse(myController4.text); _roots = getRoots( myController1.text, myController2.text, myController3.text, myController4.text); controller1.reset(); controller1.forward(); } else { ScaffoldMessenger.of(context) .showSnackBar(SnackBar( content: Text( AppLocalizations.of( context)! .error))); } }); }, child: Text(AppLocalizations.of(context)!.run))), const Spacer( flex: 1), // child: Text(Platform.localeName)), ], ), ); }, ), Padding( padding: const EdgeInsets.all(8.0), child: Text( getCoefficients(myController1.text, myController2.text, myController3.text, myController4.text), style: const TextStyle(fontSize: 18)), ), Padding( padding: const EdgeInsets.all(1.0), child: Row( children: [ Expanded( child: Showcase( key: xPoint, description: AppLocalizations.of(context)!.xPoint, child: Text( "x = ${calcK(constraints.maxWidth / 2, constraints.maxHeight / 2)}", style: const TextStyle(fontSize: 18), textAlign: TextAlign.center), ), ), Expanded( child: Showcase( key: pOfX, description: AppLocalizations.of(context)!.pOfX, child: Text( "p(x) = ${calcDistance(calcK(constraints.maxWidth / 2, constraints.maxHeight / 2))}", style: const TextStyle(fontSize: 18), textAlign: TextAlign.center), ), ) ], ), ), Padding( padding: const EdgeInsets.all(1.0), child: // Text("$_roots ${_binRes.toString()} $_numRoots", Showcase( key: keyNumRoots, description: AppLocalizations.of(context)!.rootsDescription, child: Text( "${AppLocalizations.of(context)!.numRoots} $_numRoots", style: const TextStyle(fontSize: 18), textAlign: TextAlign.center), )), Expanded( child: FutureBuilder( future: _imageFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { return GestureDetector( // scaling onScaleStart: (details) { _dragging = true; _baseScaleFactor = _scaleFactor; }, onScaleUpdate: (scaleDetails) { if (scaleDetails.pointerCount == 3) { setState(() { _scaleFactor = _baseScaleFactor * scaleDetails.scale; }); } if (scaleDetails.pointerCount == 1) { onPan(scaleDetails.localFocalPoint.dx, scaleDetails.localFocalPoint.dy); } if (scaleDetails.pointerCount == 2) { if (_dragging) { setState(() { xPos += scaleDetails.focalPointDelta.dx; yPos += scaleDetails.focalPointDelta.dy; }); } } }, onScaleEnd: (scaleDetails) { _dragging = false; }, // onPanUpdate: (details) => onPan(details), child: Showcase( key: drawing, description: AppLocalizations.of(context)!.drawing, child: ClipRect( child: CustomPaint( painter: MyPainter( constraints.maxWidth / 2, constraints.maxHeight / 2, ratio1, ratio2, ratio3, ratio4, isComplete, _progress1, tappedPoint, xPos, yPos, snapshot.data), size: Size(constraints.maxWidth, constraints.maxHeight), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ IconButton( onPressed: () { setState(() { _scaleFactor = _baseScaleFactor - 5; MyPainter.setScale(_scaleFactor.toInt()); }); }, icon: const Icon(Icons.zoom_out), iconSize: 50, ), IconButton( onPressed: () { setState(() { _scaleFactor = _baseScaleFactor + 5; MyPainter.setScale(_scaleFactor.toInt()); }); }, icon: const Icon(Icons.zoom_in), iconSize: 50, ) ], ), ), ), ), ); } else { return GestureDetector( onScaleStart: (details) { _baseScaleFactor = _scaleFactor; }, onScaleUpdate: (scaleDetails) { if (scaleDetails.pointerCount == 3) { setState(() { _scaleFactor = _baseScaleFactor * scaleDetails.scale; }); } if (scaleDetails.pointerCount == 1) { onPan(scaleDetails.localFocalPoint.dx, scaleDetails.localFocalPoint.dy); } }, child: CustomPaint( painter: MyPainter( constraints.maxWidth / 2, constraints.maxHeight / 2, ratio1, ratio2, ratio3, ratio4, isComplete, _progress1, tappedPoint, xPos, yPos), size: Size(constraints.maxWidth, constraints.maxHeight)), ); } })), ], ); }, )); } } class MyPainter extends CustomPainter { ui.Image? image; double _width = 0; static double _height = 0; static int _ratio1 = 0; static int _ratio2 = 0; static int _ratio3 = 0; static int _ratio4 = 0; static int _scale = 35; final double _progress; Offset tappedPoint; static bool _isComplete = false; static double k = 0; double _xPos = 0; double _yPos = 0; MyPainter(width, height, ratio1, ratio2, ratio3, ratio4, isComplete, this._progress, this.tappedPoint, this._xPos, this._yPos, [this.image]) { _width = width; _height = height; _ratio1 = ratio1; _ratio2 = ratio2; _ratio3 = ratio3; _ratio4 = ratio4; _isComplete = isComplete; // _scale = (_width / 10).toInt(); } static void setScale(int newScale) { _scale = newScale; } static double getP() { if (_isComplete == true) { return (_ratio4 - k * (_ratio3 - k * (_ratio2 - k * _ratio1))) / _scale; } else { return -1; } } static double getK() { return k; } @override void paint(Canvas canvas, Size size) { Paint linePaint = Paint() ..strokeWidth = 20 ..strokeCap = StrokeCap.butt ..style = PaintingStyle.stroke; Paint solutionPaint = Paint() ..strokeWidth = 8 ..strokeCap = ui.StrokeCap.butt ..style = ui.PaintingStyle.stroke ..color = Colors.redAccent; Paint imaginaryPaint = Paint() ..strokeWidth = 5 ..strokeCap = ui.StrokeCap.butt ..style = ui.PaintingStyle.stroke ..color = const Color.fromRGBO(105, 105, 105, 0.5); _ratio1 *= _scale; _ratio2 *= _scale; _ratio3 *= _scale; _ratio4 *= _scale; _width += _xPos; _height += _yPos; _width += (math.max(_ratio1, _ratio3) / 2) - _ratio1; _height += (math.max(_ratio2, _ratio4) / 2); var path = getPath(); Rect bounds = path.getBounds(); if (bounds.bottom >= size.height) { path = path .shift(Offset(0, -(bounds.bottom - _height + bounds.center.dy / 10))); } ui.PathMetrics pathMetrics = path.computeMetrics(); ui.PathMetric pathMetric = pathMetrics.elementAt(0); final pos = pathMetric.getTangentForOffset(pathMetric.length * _progress); Path extracted = pathMetric.extractPath(0.0, pathMetric.length * _progress); linePaint.strokeWidth = 6; canvas.drawPath(extracted, linePaint); if (image == null) { canvas.drawCircle(pos!.position, linePaint.strokeWidth / 2, linePaint); } else { Offset location = Offset(pos!.position.dx - image!.width / 2, pos.position.dy - image!.height / 2); canvas.save(); double cx = pos.position.dx; double cy = pos.position.dy; double angle = pos.angle; if (angle == math.pi / 2) { angle = -angle; } else if (angle == -(math.pi / 2)) { angle = math.pi / 2; } rotateTurtle(canvas: canvas, cx: cx, cy: cy, angle: angle); canvas.drawPoints(ui.PointMode.points, [Offset(cx, cy)], linePaint); canvas.drawImage(image!, location, linePaint); canvas.restore(); if (_isComplete) { // canvas.drawLine(Offset(_width, _height), tappedPoint, solutionPaint); k = (tappedPoint.dy - _height) / (_width - tappedPoint.dx); var solution = getSolution(k); double shift = 0; if (bounds.bottom >= size.height) { shift = (bounds.bottom - _height + bounds.center.dy / 10); solution = solution.shift( Offset(0, -(bounds.bottom - _height + bounds.center.dy / 10))); } // imaginary lines checkSolution(canvas, k, imaginaryPaint, extracted, shift); canvas.drawPath(solution, solutionPaint); } } } Path getSolution(tangent) { return Path() ..moveTo(_width, _height) ..lineTo(_width + _ratio1, _height - tangent * _ratio1) ..lineTo(_width + _ratio1 - tangent * (_ratio2 - tangent * _ratio1), _height - _ratio2) ..lineTo( _width + _ratio1 - _ratio3, _height - _ratio2 + tangent * (_ratio3 - tangent * (_ratio2 - tangent * _ratio1))); } void rotateTurtle( {required Canvas canvas, required double cx, required double cy, required double angle}) { canvas.translate(cx, cy); canvas.rotate(angle); canvas.translate(-cx, -cy); } Path getPath() { return Path() ..moveTo(_width, _height) ..lineTo(_width + _ratio1, _height) ..lineTo(_width + _ratio1, _height - _ratio2) ..lineTo(_width + _ratio1 - _ratio3, _height - _ratio2) ..lineTo(_width + _ratio1 - _ratio3, _height - _ratio2 + _ratio4); } @override bool shouldRepaint(covariant MyPainter oldDelegate) { return (oldDelegate._progress != _progress); } } [/code] Может кто-нибудь помочь мне понять, почему возникает эта задержка при нажатии кнопки масштабирования? Заранее спасибо. Подробнее здесь: [url]https://stackoverflow.com/questions/79028535/flutter-canvas-drawing-disappears-when-trying-to-zoom-in-out[/url]