I'm a newbie at Flutter. I'm trying to create a Draggable but I don't want to use a DragTarget. I want it to drag and drop wherever I want. When I did that after the drop operation object disappeared. I don't want it to disappear. How can I do that? I checked the resources and YouTube, however, I cannot find anything useful. Thanks in advance.
Following up to my comment, here is a code example.
You have a Stack, with your Draggable element, you then rebuild it to the new position using a Positioned widget and an offset.
You could also recreate a DragTarget following the same exemple, just repositioning it if you need its features.
You might need to adapt a bit the offset in order to make it react to your target element size.
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _started = false;
Offset _position = Offset(20, 20);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
Positioned(
top: _position.dy - 30, // Size of widget to reposition correctly
left: _position.dx,
child: Draggable(
child: Chip(label: Text('Element')),
feedback: Material(
child: Chip(
label: Text('Element'),
)),
onDragEnd: (details) {
print(details.offset);
setState(() {
if (!_started) _started = true;
_position = details.offset;
});
},
),
),
],
),
),
);
}
}
Related
I need to listen the overScroll Offset Values. When I scroll either upward or downward in a fast speed, I didn't get all offset values.
Basically I'm facing issue when I scroll Fast so I don't get all offset values from 0.0 to XYZ value. I get this kind of values in my console(as I printed them).
0.0,
20.3345,
90.355,
400.354,
As you can see there is a difference in these value.
I figured out that scrollController's addListener method is not listening all offset values.
You can try running my code as well.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
late ScrollController scrollController;
#override
void initState() {
scrollController = ScrollController();
scrollController.addListener(() {
print(scrollController.position.pixels.toString() + " ----Pixels");
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:CustomScrollView(
controller: scrollController,
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 22, vertical: 20),
child:Column(
children: List.generate(
50,
(index) => ListTile(
leading: Icon(Icons.ac_unit),
title: Text('demo'),
)),
)
)
)]));
}
`
This is my code. Please have a look at this issue, I want to thank you in advance.
I am trying to build a gridview nested in a listview with flutter, based on the attached minimal viable example, while trying to leverage the inner gridviews cacheExtent feature, which shall prevent items to be built all at once. In the full application i do kind of heavy stuff, so i want as few items to be loaded at the same time as possible, but unfortunately it looks like cacheExtent is ignored for the inner list, when nesting multiple list. For the surrounding list cacheExtent works as expected.
Does anyone have a solution for this and can explain me, while it won't work for the inner list? I am not only looking for a copy&paste solution, i am really trying to understand whats going on in flutter here, since i guess it is caused by any fundamental layouting policy which i don't know yet.
Environment:
[✓] Flutter (Channel stable, 2.10.0, on macOS 12.2.1 21D62 darwin-arm, locale en-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
Example:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
// works as expected, second instance of InnerList will not be loaded on startup
cacheExtent: 1,
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return InnerList(index);
},
),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
),
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
#override
Widget build(BuildContext context) {
// indicates all widgets are getting build at once, ignoring cacheExtent
log("building widget " + index.toString());
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Foobar " + index.toString()),
GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemCount: 100,
primary: false,
// this is ignored, all items of type ItemWidget will be loaded at once
cacheExtent: 1,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ItemWidget(index);
})
],
);
}
}
// Update
The accepted answer pointed me into the right direction, leading my search to a video explaining why it won't work - the simple answer is: shrinkWrap forces lists to evaluate all childs, to determine their height. It is even shown on a very similar example, using shrinkWrap and physics properties of the list. The solution with Slivers now looks similar like follows, even if i am still kind of skeptic constructing the outer list used by CostumScrollView in a loop.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> innerLists = [];
#override
void initState() {
super.initState();
// construct lists
for(var i=0; i<10; i++) {
innerLists.add(SliverToBoxAdapter(
child: Text("Foobar " + i.toString())
));
innerLists.add(InnerList(i));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(slivers: innerLists),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
)
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
#override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
#override
Widget build(BuildContext context) {
return
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ItemWidget(index);
},
childCount: 100
)
);
}
}
shrinkWrap makes GridView/ListView decides height itself.
Without shrinkWrap, ListView expands to main axis infinitely. So its height is constrained by parent box constraint, and fills available space. In that case render space is definite. ListView only build childs on the visible space.
With shrinkWrap, ListView try to shrink, as short as possible unless it can contain all childs, so its height is sum of child's height (roughly). In this case ListView doesn't see parent constraint so ListView doesn't know how many childs it should build, so it builds all elements.
Now let's see your example. With replacing log to print, we get the following.
building widget 0
building widget 1
...
building widget 99
building widget 0
building widget 1
...
building widget 99
Although I can't see the second InnerList, ListView built the second child. Now with commenting out ListView.builder's shrinkWrap
building widget 0
building widget 1
...
building widget 99
If you scroll down, log will be same with the first.
GridView is same. But you can't comment out GridView's shrinkWrap due to layout error. It is because ListView gives infinite height to its children (GridView). If GridView expands, its height will be infinity.
Of course using SizedBox will solve this partially, but maybe it is not what you want.
If you really want on demand load here, you may use sliver.
Add: cacheExtent doesn't mean item count. It is pixel count.
I have a Flutter app which in one of the pages it presents a list of more than 200 items. How can I make it so that when the app is running in a desktop environment for example it displays more than one column dynamically and that when it runs on phones it displays a single columns or when it run on tablet in vertically orientation it displays one column and more than one column when the device is on horizontal orientation?
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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'something here',
),
Text(
'something here',
),
Text(
'something here',
),
Text(
'something here',
),
],
),
),
);
}
}
This will produce a single column no matter the device or orientation.
Try using GridView instead of Column, in that way you can adjust number of columns with crossAxisCount property of GridView. I am coming to that in a while.
Lets talk about how to adjust the number of columns dynamically depending upon the screen size. For example, I want single column in mobile, 2 columns in tab and 4 columns in desktop.
I'll create an enum named LayoutSize that represents different layout sizes. Also create an extension that returns number of columns depending upon the LayoutSize.
enum LayoutSize { small, medium, large }
extension LayoutSizeX on LayoutSize {
int get noOfColumns {
switch (this) {
case LayoutSize.small:
return 1;
case LayoutSize.medium:
return 2;
case LayoutSize.large:
return 4;
}
}
}
Now it's time to think about Breakpoints that will represent maximum width for different LayoutSize. You can change the numbers according to your requirements.
abstract class Breakpoints {
static const double small = 760;
static const double medium = 1644;
static const double large = 1920;
}
Finally wrap up all these logic and create the UI. Here LayoutBuilder will serve the purpose of changing the number of columns whenever there is a change in constraints.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
final screenWidth = MediaQuery.of(context).size.width;
if (screenWidth <= Breakpoints.small) {
return _buildMyWidget(LayoutSize.small);
}
if (screenWidth <= Breakpoints.medium) {
return _buildMyWidget(LayoutSize.medium);
}
if (screenWidth <= Breakpoints.large) {
return _buildMyWidget(LayoutSize.large);
}
return _buildMyWidget(LayoutSize.small);
});
}
Widget _buildMyWidget(LayoutSize size) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: size.noOfColumns,
),
itemBuilder: (context, index) {
return Container();
},
);
}
}
I know this solution requires a little bit of extra coding, but believe me this is going to be very much efficient in long run, even in building the whole app responsive, not only a particular widget.
This is the minimal code you need to write in this particular case. But if you're interested in much generic way, here is a link to a gist.
You can add your list Items conditionally in two ways:
Using if() statement:
Column(
children: [
ListTile(
...
),
if (_condition)
Container(
...
),
],
),
Using condition like this:
Column (
children: [
x == 1 ? Widget1() : Widget2(),
],
)
What you need here is to figure out the OS and also the screen type, the Platform Class will give you some information about the OS:
import 'dart:io' show Platform;
if (Platform.isWindows) ...
and the MediaQuery class will give you useful information about the screen and sizes:
final isLandscape = (MediaQuery.of(context).orientation == Orientation.landscape);
There is no direct way to figure out that the Device is a Tablet or a Phone, but there are some tricks to get device type, here are some examples
You need a combination of these approaches to achive what you want
Hello Guys im new to flutter.
To understand Flutter I watched a lot of videos and read blog entries.
But there is always a problem:
Each video is about a specific topic and all of them start with a new Flutter project. As long as I want to continue working on the code I can't change the code.
Below I have added a code by Hanz Müller as an example. Topic NavigationBar.
But now I want to delete the text under the icons and edit the different app pages (body) with text and images.
I can't delete the text under the icons because text can't be ''null''.
And I can't edit the diffrent body pages because I can't find the position.
i only know html and css because it is a hobby and now i search for the place where i find the body container :)
Thanks a lot for your help
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class Destination {
const Destination(this.title, this.icon, this.color);
final String title;
final IconData icon;
final MaterialColor color;
}
const List<Destination> allDestinations = <Destination>[
Destination('Home', Icons.home, Colors.teal),
Destination('Business', Icons.business, Colors.cyan),
Destination('School', Icons.school, Colors.orange),
Destination('Flight', Icons.flight, Colors.blue)
];
class DestinationView extends StatefulWidget {
const DestinationView({ Key key, this.destination }) : super(key: key);
final Destination destination;
#override
_DestinationViewState createState() => _DestinationViewState();
}
class _DestinationViewState extends State<DestinationView> {
TextEditingController _textController;
#override
void initState() {
super.initState();
_textController = TextEditingController(
text: 'sample text: ${widget.destination.title}',
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widget.destination.title} Text'),
backgroundColor: widget.destination.color,
),
backgroundColor: widget.destination.color[100],
body: Container(
padding: const EdgeInsets.all(32.0),
alignment: Alignment.center,
child: TextField(controller: _textController),
),
);
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin<HomePage> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
index: _currentIndex,
children: allDestinations.map<Widget>((Destination destination) {
return DestinationView(destination: destination);
}).toList(),
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
items: allDestinations.map((Destination destination) {
return BottomNavigationBarItem(
icon: Icon(destination.icon),
backgroundColor: destination.color,
title: Text(destination.title)
);
}).toList(),
),
);
}
}
void main() {
runApp(MaterialApp(home: HomePage(), debugShowCheckedModeBanner: false));
}
If you want to remove the Text under the icon Check the code where the Text widget is place.
So you have the relevant Text widget in BottomNavigationBarItem
title: Text(destination.title)
So if you don't need the Text widget you can simply replace it with Container to display nothing.
title: Text(destination.title)
I would suggest you read the code and understand it will. The better you understand how your widgets are built and rendered it will be easier to modify them.
I am flutter beginner.
I have my main page split into 2 custom widgets, top widget and page widget. And I have a drawer with a few buttons to change the content of the page widget.
I followed this tutorial and try to swap the widget as per click on the buttons.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
var currentpage = 'landing';
Widget pageWidget;
switch (currentpage) {
case 'landing':
pageWidget = new Landing();
break;
case 'visitors':
pageWidget = new Visitors();
break;
default:
}
return Scaffold(
body:new Container(
child: Container(
child: Column(
children: <Widget>[
Header(), ///<-- widget declared in separated .dart file
pageWidget
],
)
)
drawer: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text(
"UserN3",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black54),
),
),
new GestureDetector(
onTap: (){
print("landing");
Future.delayed(Duration.zero,(){
setState(() {
currentpage = 'landing';
});
});
},
child: new Text("landing"),
),
new GestureDetector(
onTap: (){
print("visitors");
Future.delayed(Duration.zero,(){
setState(() {
currentpage = 'visitors';
});
});
},
child: new Text("Visitors"),
),
],
),
),
);
}
so the gist is:
I have switch case in the build, which will change the pageWidget accordingly based on currentpage, which will be changed by buttons in the drawer.
But keep getting the same error:
Another exception was thrown: setState() or markNeedsBuild() called when widget tree was locked.
As you can see I added Future.delayed() based on this suggestion but the error persist.
After some researching, I got a feeling that it related to how drawer deal with setState(), but I am not sure how.
Obviously I still not getting how flutter work in terms of build and state.
What seems to be the problem and how can I resolve this?
You should declare your currentPage variable outside your build method, because every time you call setState the build method is called again and the variable doesn't change.
So move this :
Widget build(BuildContext context) {
var currentpage = 'landing';
To this:
var currentpage = 'landing';
Widget build(BuildContext context) {