Related
I'm trying to use a GridView to handle displays for multiple Card, each Card contains of an Image. Unfortunately it turns out that the Image is taking a larger height than its parent (see attached picture for the details).
I'm pretty new to Flutter layout so any ideas why this is happening and how I can resolve this? I want the layout to be something like this:
Display 2 cards on each line.
The Card width or height should not be fixed.
The Image height should be scaled according to its width.
class SquadSelectionScreen extends StatelessWidget {
final List<Team> teams;
const SquadSelectionScreen({super.key, required this.teams});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Squads'),
),
body: GridView.count(
crossAxisSpacing: 10,
crossAxisCount: 2,
padding: const EdgeInsets.all(16),
children: teams
.map(
(team) => SquadView(team: team),
)
.toList(),
),
);
}
}
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.push('/squads/${team.code}');
},
child: Card(
elevation: 1,
child: Column(
children: [
Image(
image: NetworkImage(team.imageUrl),
),
const SizedBox(
height: 8,
),
Center(
child: Text(team.name),
),
],
),
),
);
}
}
Using GridView.count has a very visible drawback, namely the size of the aspect ratio of the grid will always be one (1:1 or Square) and can't be changed.
So if you look at the code above, you can't set an image with the same aspect ratio because the text will sink.
The first suggestion for me if you still want to use GridView.count is
Wrapping your Image with AspectRatio that has value higher than one (example set Ratio to 4/3, 5/3, 16/9, or landscape looks). Note: 4/3 = is higher than 1, 16/9 = is higher than 1, etc..
Then wrap the Text Widget with Expanded()
Example code:
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
AspectRatio(
aspectRatio: 4/3, // you can set the value to 16/9 or anything that result is higher than one
child: Image(
image: NetworkImage(team.imageUrl),
fit: BoxFit.cover, // set How the image looks to Fit
),
),
const SizedBox(
height: 8,
),
Expanded(
child: Center(
child: Text(team.name, overflow: TextOverflow.ellipsis),
),
),
],
),
),
),
);
}
}
I suggest you try GridView.builder or another GridView. You can look at the documentation here
or this third package this will be good for to try flutter_staggered_grid_view. The flutter_staggered_grid_view is more flexible to create GridView with various size.
Good day to all. I wanted to use the touchable tool, which allows you to read clicks on the canvas, but there was an error with gesture_detector, which I don't understand how to fix. Here is the code I wrote:
Container(
child: FittedBox(
child: tooth.length == 20 && mouth != null
? SizedBox(
width: mouth?.width.toDouble(),
height: mouth?.height.toDouble(),
child: CanvasTouchDetector(
builder: (context) => CustomPaint(
painter:
FaceOutlinePainter(context),
),
))
: Text('data')),
),
And Flutter sends me to this error. As I understand it, it does not depend on CustomPaint.
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Incorrect GestureDetector arguments.'),
ErrorDescription(
'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',
),
ErrorHint('Just use the scale gesture recognizer.'),
]);
I will be very grateful for your help.
With best wishes,
from Dmitry
That's because you are using one Listener per CustomPainter, you should use just one Listener for all your Stack.
And if you want to know if the current touch event is inside each Circle , you could use GlobalKeys to get the RenderBox for each Circle, then you have the renderBox, and the PointerEvent, you can easily check the HitTest, check the code:
class _MyHomePageState extends State<MyHomePage> {
GlobalKey _keyYellow = GlobalKey();
GlobalKey _keyRed = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Listener(
onPointerMove: (PointerEvent details) {
final RenderBox box = _keyRed.currentContext.findRenderObject();
final RenderBox boxYellow =
_keyYellow.currentContext.findRenderObject();
final result = BoxHitTestResult();
Offset localRed = box.globalToLocal(details.position);
Offset localYellow = boxYellow.globalToLocal(details.position);
if (box.hitTest(result, position: localRed)) {
print("HIT...RED ");
} else if (boxYellow.hitTest(result, position: localYellow)) {
print("HIT...YELLOW ");
}
},
child: Stack(
children: <Widget>[
CustomPaint(
key: _keyYellow,
painter: ShapesPainter(),
child: Container(
height: 400,
width: 400,
),
),
CustomPaint(
key: _keyRed,
painter: ShapesPainter1(),
child: Container(
height: 200,
width: 200,
),
),
],
),
),
],
),
),
);
}
}
Here i have a simple class that i show images from network with Image.network. as i used this class into a Listview, during scrolling that cause of a bit lag and i think i can fix it with Isolate.
after reading some documentation about this feature in Dart i'm not sure how can i put this this class or part of that such as a simple widget into that.
class InistaLikers extends HookWidget {
final List<String> imageUrls;
const InistaLikers({required this.imageUrls});
#override
Widget build(BuildContext context) {
late double _width = 0;
late int count = 4;
final _orientation = MediaQuery.of(context).orientation;
final _screenWidth = MediaQuery.of(context).size.width;
useEffect((){
if(_orientation == Orientation.portrait){
_width = _screenWidth* 0.39;
count = 4;
}else if(_orientation == Orientation.landscape){
_width = (_screenWidth / 2) * 0.39;
count = 3;
}
});
return Container(
width: _width,
height: 35.0,
child: Row(
children: [
Expanded(
child: Stack(
children: List.generate(
count,
(i) {
return Positioned(
right: imageUrls.length + (20.0 * i),
child: ClipOval(
child: Container(
width: 35,
height: 35,
child: Image.network(
imageUrls[i],
),
),
),
);
},
).toList(),
),
),
ClipOval(
child: Container(
width: 35,
height: 35,
child: Image.network(
imageUrls.last,
),
),
),
],
),
);
}
}
You cannot build a widget through an isolate as dart:ui which is used to render your widgets is only available on the main isolate. Moreover, Image.network already uses an ImageStream to manage the recuperation of an online image.
If you have some performance issues you should try to optimize the way you are building your widgets, for example if it was not the case already you should use ListView.builder if you have a lot of widgets to render.
You can find some "Performance best practices" documentation on the flutter website or the article Flutter Performance Tips written by Hasan Basri Bayat.
Here's some of the tips described in this article which you can apply to improve the performances of your app:
Use Widgets Over Functions
// Don't do this
[
_buildHeaderWidget(),
_buildMainWidget(context),
_buildFooterWidget(),
]
// Do this
[
HeaderWidget(),
MainWidget(),
FooterWidget(),
]
Use const where possible
const _myFixedHeight = 48.0;
Use const constructors whenever possible
class CustomWidget extends StatelessWidget {
const CustomWidget();
#override
Widget build(BuildContext context) {
// ...
}
}
Use nil instead of Container()
// Don't do this
Column(
children: [
text != null ? Text(text) : Container(),
],
)
// Do this
Column(
children: [
if (text != null)
Text(text),
],
)
And you'll find some more tips in the article.
I'm working on my first Flutter app (debugging on my Android phone). I have a list with row items. When you long-press the row, it copies the content into the user's clipboard. This is working great!
But I need to let the user know that the content was copied.
I've attempted to follow many tutorials on trying to get the row surrounded by a build method or inside a Scaffold, but I can't get any to work. Is there an alternative method to notifying the user (simply) that something like "Copied!" took place?
Notice the commented out Scaffold.of(... below. It just seems like there must be an easier method to notifying the user other than wrapping everything in a Scaffold. (and when I try, it breaks my layout).
import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';
class RowRule extends StatelessWidget {
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'],
style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'],
style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
// Scaffold.of(context).showSnackBar(SnackBar
// (content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(
vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
)),
)
],
),
),
));
}
}
The goal is to have a page like this (see image), which I have, and it works and scrolls...etc, but I cannot get it to work with a Scaffold, and therefore, haven't been able to use the snackbar. Each "Row" (which this file is for) should show a snackbar on longPress.
You can use GlobalKey to make it work the way you want it.
Since I don't have access to your database stuff, this is how I gave you an idea to do it. Copy and paste this code in your class and make changes accordingly. I also believe there is something wrong in your RowRule class, can you just copy the full code I have given you and run?
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
key: _key,
body: Column(
children: <Widget>[
Container(
color: Color.fromRGBO(52, 56, 245, 1),
height: 150,
alignment: Alignment.center,
child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 120,
itemBuilder: (context, index) {
return Container(
color: Colors.white,
margin: const EdgeInsets.all(4),
child: ListTile(
title: Text("Row #$index"),
onLongPress: () => _key.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
),
);
},
),
),
],
),
);
}
}
These is a simple plugin replacement for the Snackbar named "Flushbar".
You can get the plugin here - https://pub.dartlang.org/packages/flushbar
You don't have to take care of any wrapping of widgets into scaffold also you get a lot of modifications for you like background gradient, adding forms and so on into Snackbar's and all.
Inside your onLongPressed in GestureDetectore you can do this.
onLongPressed:(){
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
Flushbar(
message: "Copied !!",
duration: Duration(seconds: 3),
)..show(context);
}
This will display the snackbar in you app where you would want to see it also you can get a lot of modification available to you so the you can make it look as per your app.
There are couple of things you need to do, like use onPressed property of the FlatButton it is mandatory to allow clicks, wrap your GestureDetector in a Scaffold. I have further modified the code so that it uses GlobalKey to make things easy for you.
Here is the final code (Your way)
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
onPressed: () => print("Handle button press here"),
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
),
),
)
],
),
),
),
),
);
}
}
I made a dropdown banner package on pub that allows you to easily notify users of errors or confirmation of success. It's a work in progress as I continue to add visually rich features.
I am not sure if your build() method is completed or you are yet to change it, because it consist of many widgets which are just redundant. Like there is no need to have Container in Container and further Padding along with a FlatButton which would make complete screen clickable. Also having Column won't be a good idea because your screen may overflow if you have more data. Use ListView instead.
So, if you were to take my advice, use this simple code that should provide you what you are really looking for. (See the build() method is of just 5 lines.
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
if (!ruleGroup['details'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: ListView(children: _buildChildren()),
);
}
}
I read your comments on all answers and here is my conslusion:
You need ScaffoldState object that is just above the widget in tree to show Snackbar. You can either get it through GlobalKey as many have suggested. Fairly simple if the Scaffold is created inside build of the widget, but if it is outside the widget (in your case) then it becomes complicated. You need to pass that key, wherever you need it through Constructor arguments of child widgets.
Scaffold.of(context) is a very neat way to just do that. Just like an InheritedWidget, Scaffold.of(BuildContext context) gives you access of the closest ScaffoldState object above the tree. Else it could be a nightmare to get that instance (by passing it through as constructor arguments) if your tree was very deep.
Sorry, to disappoint but I don't think there is any better or cleaner method than this, if you want to get the ScaffoldState that is not built inside build of that widget. You can call it in any widget that has Scaffold as a parent.
Im trying to create a stack of cards, superimposed, and offset of each other to visualize multiple versions of a card.
I have tried putting cards inside cards, but do not find a way to offset them.
I also tried using the stack class with no luck.
Anyone know how i can achieve this effect?
You were going in the right direction with Stack - you just had to figure out how to offset the widget. The best way to do this for the 'top' of the stack is to use padding, but you don't want to have to specify the size of each of the cards... it's much better if the stack grows/shrinks based on the content which is actually being shown.
To that end, you can use Positioned with all of the sizes specified for the cards. That will make sure that they grow to the appropriate size, without making the stack resize or having to specify each of their sizes.
Here's the code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Container(
color: Colors.grey,
child: ListView(
children: [
StackOfCards(
child: Container(height: 100.0),
num: 5,
),
StackOfCards(
child: Container(height: 100.0),
num: 4,
),
StackOfCards(
child: Container(height: 100.0),
num: 3,
),
StackOfCards(
child: Container(height: 100.0),
num: 2,
),
StackOfCards(
child: Container(height: 100.0),
num: 1,
)
\],
),
),
),
);
}
}
class StackOfCards extends StatelessWidget {
final int num;
final Widget child;
final double offset;
const StackOfCards({Key key, int num = 1, #required this.child, this.offset = 10.0})
: this.num = num > 0 ? num : 1,
assert(offset != null),
super(key: key);
#override
Widget build(BuildContext context) => Stack(
children: List<Widget>.generate(
num - 1,
(val) => Positioned(
bottom: val * offset,
left: val * offset,
top: (num - val - 1) * offset,
right: (num - val - 1) * offset,
child: Card(child: Container()))).toList()
..add(
Padding(
child: Card(child: child),
padding: EdgeInsets.only(bottom: (num - 1) * offset, left: (num - 1) * offset),
),
),
);
}
Hmmm... I guess that build function could probably be explained a bit. What I'm doing is using a generated list to iterate from 0..(num cards - 1) and calculating the appropriate offsets for each Positioned widget (each of which contains an essentially empty card).
Then this gets made from an iterable to a list with .toList(), but doesn't have the top card yet... so I do an inline add (I'm sure there's a better word for that, but I don't know it) of the Padding widget that has the appropriate offsets and contains the child. The ..add just makes it so that I can do it in one line - it returns the list instead of void as .add would. Yay for dart =)!
I made it a little bit flexible, but you could go further and define the offset as two parameters, make it so that you can go different directions etc. Anyways, the code results in this:
This may not be the best approach but according to the sample you posted in the comment you can align it with a Stack and Padding:
Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8.0, right: 8.0),
child: Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Text("Content"),
),
)),
),
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
child: Card(
child: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Text("Content"),
),
)),
),
],
)
Which will look like this:
You can then customize it with setting different paddings etc.