Is MaterialApp/Scaffold absorb my Listener? - flutter

I try to code a very simple Whiteboard. When I use
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Listener(
the app isn't recognizing any mouseclick (onPointerDown()). When I use directly
Widget build(BuildContext context) {
return Container(
child: Listener(
everything is ok and I can see some action in the onPointerDown(). So, I think I miss something. I want to use the MaterialApp, to get access to some of the features.
What I tried so far:
At first, I tried to minimize my function, to focus on only that problem.
So, this is my full minimized code:
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.white,
child: Listener(
onPointerDown: (details) {
print('Action');
},
child: CustomPaint(
painter: DrawingPainter(),
),
))));
}
}
class DrawingPainter extends CustomPainter {
DrawingPainter();
#override
void paint(Canvas canvas, Size size) {}
#override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
What I recognized, the size of the DrawingPainter is (0.0, 0.0). Maybe the Problem is that the Painter isn't span about the full size? If this is so, how can I change that? I tried to set the size, but ended with size == 0.0, 0.0 again.

Yes, you just have to use a SizedBox.expand or anything that will force your content to expand its size to match parent.
see https://zu4c06ylu4d0.zapp.page/#/

Related

Keep global ThemeData across screens

Is it possible to set ThemeData globally so you don't have to on the next screens?
Currently after I use the Navigator.pushI have to make sure the new screen Widget have instructions like Theme.of(context).backgroundColor
Is there a way for the app screens/widgets to automatically pick whatever is set in the main.dart theme?
Eg, Here is my main.dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(backgroundColor: Colors.red, primaryColor: Colors.green),
home: LoadingScreen(),
);
}
}
and this is the loadingscreen:
class _LoadingScreenState extends State<LoadingScreen> {
#override
void initState() {
super.initState();
getFiles();
}
void getFiles() async {
await Future.delayed(Duration(seconds: 3));
Navigator.push(context, MaterialPageRoute(builder: (context) {
return StartScreen(file: null);
}));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
height: 100.0,
)),
),
);
}
}
Without a Theme.of(context).backgroundColor in the Container, the app will load a white page behind the image, instead of red as defined in the ThemeData.
Is that a way to avoid the extensive usage of Theme.of(context).backgroundColor everywhere?
When you set the Scaffolds backgroundcolor in the ThemeData every Scaffold should use that color as a backgroundcolor. On your LoadingScreen the extra Container is unecessery since it would override the Scaffolds backgroundcolor.
Refer to this answer on how to set the backgroundcolor: https://stackoverflow.com/a/55581726/13406356

Flutter's MediaQuery rectangle seems incorrect

A Rect with a top-left at (0, 0) and sized MediaQuery.of(context).size should match exactly the rectangle left after the AppBar rectangle is present.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(scaffoldBackgroundColor: const Color(0xFF80EFEF)),
home: Scaffold(
appBar: AppBar(
title: Text('MyApp'),
),
body: MyWidget()));
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
var deviceData = MediaQuery.of(context);
return CustomPaint(painter: MyPainter(appSize: deviceData.size));
}
}
class MyPainter extends CustomPainter {
Size appSize;
MyPainter({this.appSize});
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = Colors.indigo;
const double margin1 = 70;
canvas.drawRect(
Rect.fromLTWH(margin1, margin1, appSize.width - 2 * margin1,
appSize.height - 2 * margin1),
paint);
}
#override
bool shouldRepaint(MyPainter oldDelegate) => false;
}
As you see here:
it does not. It protrudes (when targeting chrome) from the bottom. We're only able to see that when we use a margin to reduce the rectangle's size.
Why does the rectangle not match the expected area? Is this a bug?
You can use layoutbuilder to know the exact remaining space left.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LayoutBuilder Example')),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Text('Width: ${constraints.maxWidth} Height: ${constraints.maxHeight}');
},
),
);
}
When we write a Widget class such as
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
var deviceData = MediaQuery.of(context);
return CustomPaint(painter: MyPainter(appSize: deviceData.size));
}
}
and make a MediaQuery.of(), the context that we are querying is not that of MyWidget. It is the context of the parent of MyWidget in the widget objects tree.
In this case the parent is the Scaffold. Hence the context we get does include the AppBar!
(To make it clearer, it would perhaps be more apt to write parentContext, rather than context.)
The solution is to add an intermediate "dummy" parent widget that has the correct dimensions.
This does not need to be a full-fledged new class. Using either Builder or LayoutBuilder is enough.
Using Builder doesn't cut it (why?)
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: new ThemeData(scaffoldBackgroundColor: const Color(0xFF80EFEF)),
home: Scaffold(
appBar: AppBar(
title: Text('MyApp'),
),
body: Builder(
builder:
(BuildContext context) {
return MyWidget();
},
)));
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
var deviceData = MediaQuery.of(context);
return CustomPaint(painter: MyPainter(appSize: deviceData.size));
}
}
Hence we need to use, as in Ayad's answer, LayoutBuilder.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: new ThemeData(scaffoldBackgroundColor: const Color(0xFF80EFEF)),
home: Scaffold(
appBar: AppBar(
title: Text('MyApp'),
),
body: LayoutBuilder(
builder:
(BuildContext context, constraints) {
return MyWidget(size: Size(constraints.maxWidth, constraints.maxHeight));
},
)));
}
}
class MyWidget extends StatelessWidget {
Size size;
MyWidget({this.size}) {}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: MyPainter(appSize: size));
}
}
We then see that we have the correct Size.

Flutter clippath over appbar

Hi Im having problem with clippath, Im trying to do a clippath like in the inserted image link [https://imgur.com/qDspPgV]. However, I realised I cant actually do this in flutter because appbar is block the way so it gives me a white space.
Is there a way where I could overlap it?
This is my code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: Colors.transparent));
runApp(LoginUI());
}
class LoginUI extends StatefulWidget {
#override
_LoginUIState createState() => _LoginUIState();
}
class _LoginUIState extends State<LoginUI> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Login"),
),
body: SafeArea(child: Builder(builder: (BuildContext context) {
return Container(
color: Colors.white,
child: ClipPath(
clipper: MyClipper(),
child: Container(
color: Colors.red,
)));
})),
));
}
}
class MyClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Path path = new Path();
path.lineTo(0, size.height * .5);
path.lineTo(size.width, 0);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
This is my design output because of appbar
[https://imgur.com/UaKFvBO]
the clippath on the design is a placeholder. I havent decided to change it yet until I actually manage to overlap the appbar.
Edit: suggestion upon without appbar [https://imgur.com/93O1kX7], it doesnt go thoroughly on the status bar.
Edit: Thanks Albert [ https://imgur.com/5QsztCr ].
You can achieve what you want just by omitting the appBar Scaffold parameter and passing this clipped container at the top of a Column that you would have in your Scaffold's body.
Also remember that SafeArea paddings out from the status bar among other things.

There are some parts that I don't understand well about Flutter's Key

I practiced after watching a video explaining Flutter's Key.
https://api.flutter.dev/flutter/foundation/Key-class.html
This video shows an example of changing the location of a container with a specific color. (About 1 minute and 50 seconds)
In the video, the statefulwidget says that without a key, the location will not change.
But I wrote the example code myself and confirmed that it worked without giving a key to the stateful widget.
I think I wrote the example code wrong. Below is the code I wrote.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: KeyPractice(),
);
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({#required this.color});
final Color color;
#override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: widget.color,
);
}
}
class KeyPractice extends StatefulWidget {
#override
_KeyPracticeState createState() => _KeyPracticeState();
}
class _KeyPracticeState extends State<KeyPractice> {
List<Widget> tiles;
#override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(
color: Colors.blueAccent,
),
StatefulColorfulTile(
color: Colors.amber,
),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.autorenew,
),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
},
),
);
}
}
The above codes switch positions with each other.
What happens to the example of how the widget does not reposition each other when the stateful widget in the video does not assign keys?
And I understand that the key works only on the Stateful widget, does the Stateless use the key?
And I understood that Key only works with the Stateful widget. I wonder if the Stateless widget uses a key.
If I misunderstood, please teach me.
You're storing the color in the State of KeyPractice. The example they use stores it in the State of the child, in your case: StatefulColorfulTile.
Below is an example of the use of keys to correctly reposition widgets like you're trying to do. My example ended up very similar to what's shown on this medium article. Removing the keys here prevents the widgets from reflecting the color swap, but the use of the keys allows for the intended behavior.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: KeyPractice(),
);
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);
#override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
final Color myColor = UniqueColorGenerator.getColor();
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: myColor,
);
}
}
class KeyPractice extends StatefulWidget {
#override
_KeyPracticeState createState() => _KeyPracticeState();
}
class _KeyPracticeState extends State<KeyPractice> {
List<Widget> tiles;
#override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(key: UniqueKey()),
StatefulColorfulTile(key: UniqueKey()),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.autorenew,
),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
},
),
);
}
}
class UniqueColorGenerator {
static Random random = new Random();
static Color getColor() {
return Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255));
}
}

How to draw on a CustomPaint widget at position of the pointer down event?

I'm trying to create a simple widget so that when the user presses the screen, a circle appears at that position. I have a CustomPaint widget wrapped by a Listener widget like this:
new Listener(
onPointerDown: down,
child: new CustomPaint(
painter: painter,
size: Size.infinite,
),
)
The problem is that the pointer down events are supplied in global coordinates, and the painting is done in coordinates local to the CustomPaint widget. How should I convert these two coordinate systems?
This page says I can use the RenderBox.globalToLocal method but then how do I get the RenderBox of the CustomPaint widget?
You don't necessarily have to wrap the listener in a widget. You can also use a GlobalKey to get the RenderObject.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
GlobalKey _paintKey = new GlobalKey();
Offset _offset;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('CustomPaint example'),
),
body: new Listener(
onPointerDown: (PointerDownEvent event) {
RenderBox referenceBox = _paintKey.currentContext.findRenderObject();
Offset offset = referenceBox.globalToLocal(event.position);
setState(() {
_offset = offset;
});
},
child: new CustomPaint(
key: _paintKey,
painter: new MyCustomPainter(_offset),
child: new ConstrainedBox(
constraints: new BoxConstraints.expand(),
),
),
),
);
}
}
class MyCustomPainter extends CustomPainter {
final Offset _offset;
MyCustomPainter(this._offset);
#override
void paint(Canvas canvas, Size size) {
if (_offset == null) return;
canvas.drawCircle(_offset, 10.0, new Paint()..color = Colors.blue);
}
#override
bool shouldRepaint(MyCustomPainter other) => other._offset != _offset;
}
OK this worked for me (thanks to help from #mikemimik on gitter):
Wrap the listener in a new custom widget that extends StatelessWidget. The build() method of that widget then gets access to the RenderBox like this:
#override
Widget build(BuildContext context) {
void down(PointerDownEvent evt) {
RenderBox box = context.findRenderObject();
painter.addPos(box.globalToLocal(evt.position));
}
return new Listener(
onPointerDown: down,
child: new CustomPaint(
painter: painter,
size: Size.infinite,
),
);
}