How to calculate the distance of pointer in VerticalDragDown Gesture in Flutter? - dart

In my app, when the user drags down, I want to execute a method. The problem is even when the user only taps on the screen, the VerticalDragDown event is fired. My guess is the tap event fired a drag event with a small distance. So I want to calculate the distance that users have dragged, if it's larger than a certain amount, then call the method.
Surprisingly there was no such info in the DragStartDetails, DragEndDetails, DragDownDetails classes. The details.globalPosition.distance property seems like the distance from the touched point to the screen edge, not dragged distance because I have tried to drag with different length but the result is almost the same. And only onVerticalDragDown callback was called.
Here is my code:
Widget _buildScanGuide() {
return GestureDetector(
onVerticalDragStart: (details) {
print('DragStart: $details');
},
onVerticalDragDown: (details) {
print('DragDown: $details');
},
onVerticalDragEnd: (details) {
print('DragEnd: $details');
},
onVerticalDragUpdate: (details) {
print('DragUpdate: $details');
},
child: ListView(
padding: EdgeInsets.only(top: 50.0),
children: <Widget>[
Center(
child: Text('No device founded!',
style: TextStyle(fontSize: 24.0, color: kTextColor))),
Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Icon(
Icons.arrow_downward,
size: 96.0,
color: kTextColor,
),
),
Center(
child: Text('Drag down to scan for devices.',
style: TextStyle(fontSize: 24.0, color: kTextColor))),
],
),
);
}
And in debug console:
I/flutter (20427): DragDown: DragDownDetails(Offset(268.0, 192.5))
I/flutter (20427): DragDown: DragDownDetails(Offset(288.4, 173.2))
I/flutter (20427): DragDown: DragDownDetails(Offset(92.7, 235.4))
I/flutter (20427): DragDown: DragDownDetails(Offset(264.5, 168.1))
I/flutter (20427): DragDown: DragDownDetails(Offset(264.5, 173.2))
I/flutter (20427): DragDown: DragDownDetails(Offset(280.3, 176.6))
I/flutter (20427): DragDown: DragDownDetails(Offset(267.6, 178.8))
I/flutter (20427): DragDown: DragDownDetails(Offset(63.9, 250.6))
I/flutter (20427): DragDown: DragDownDetails(Offset(277.8, 191.4))
I/flutter (20427): DragDown: DragDownDetails(Offset(279.2, 206.9))
I'm using flutter v0.5.7 dev channel.

I had to use Listener which is not recommended but it solves the problem
return Listener(
onPointerDown: (details) {
_pointerDownPosition = details.position;
},
onPointerUp: (details) {
if (details.position.dy - _pointerDownPosition.dy > 50.0) {
_dragHandler(pf10Bloc);
}
},
child: //...
More details could be found in docs https://flutter.io/gestures/

The onVerticalDragDown callback is called when
A pointer has contacted the screen and might begin to move vertically.
If you want to detect an vertical drag, I suggest you to handle the onVerticalDragUpdate callback. The DragUpdateDetails argument contains a primaryDelta property which is:
The amount the pointer has moved along the primary axis since the
previous update.
If only onVerticalDragDown is called, I think it's because of the ListView which captures the vertical drag event to scroll.
In your example you should use a Column widget if you don't need the scrolling feature, otherwise you will have to listen to the scroll position updates.

Related

How to use PrimaryScrollController in multiple views at the same time

I am using the PrimaryScrollController to in a custom stateful widget which is basically a ListView with code to load items from the server as the user scrolls. I need the PrimaryScrollController so that tapping the top of the app bar on iOS scrolls back to the top.
I am using this same widget in multiple pages, which are controlled by a BottomNavigationBar. I am also trying to persist state, so that when the user scrolls, then changes to another tab in the bottom nav bar, and then returns to the first tab, the scroll position and all the items loaded from the server will still be there.
To persist the state of the pages I'm using an IndexedStack. However, since both pages are using the PrimaryScrollController at the same time I'm getting a long chain of the same error message in the console: flutter: Another exception was thrown: ScrollController attached to multiple scroll views.
I'm understanding that this is telling me that I shouldn't use the same ScrollController in multiple views. The simple solution would be to just create a new instance for each view. However, I must use the same ScrollController because I need the PrimaryScrollController for the tap the app bar on iOS to work.
If I just ignore the errors, the actual code works the way I want it to, each page retains its own state.
Actually, it doesn't work the way I want it to. The listeners don't work when they are on sperate pages.
I created a sample app to show the problem:
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
List<Widget> _pages = [
BodyWidget(key: PageStorageKey(1)),
BodyWidget(key: PageStorageKey(2)),
];
class _MyHomePageState extends State<MyHomePage> {
int _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: IndexedStack(
index: _currentPage,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentPage,
onTap: (int index) {
setState(() {
_currentPage = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.star_border), title: Text('page 1')),
BottomNavigationBarItem(
icon: Icon(Icons.crop_square), title: Text('page 2'))
]),
);
}
}
class BodyWidget extends StatefulWidget {
BodyWidget({Key key}) : super(key: key);
#override
_BodyWidgetState createState() => _BodyWidgetState();
}
class _BodyWidgetState extends State<BodyWidget> {
ScrollController _scrollController;
List<int> numbers = [];
void dataGenerator() {
// simulate loading items from server
var rng = new Random();
for (var i = 0; i < 100; i++) {
if (this.mounted) {
setState(() {
numbers.add(rng.nextInt(100));
});
}
}
print('loaded more items');
}
#override
void initState() {
super.initState();
// This delay is required to get the build context
Future.delayed(
Duration.zero,
() {
_scrollController = PrimaryScrollController.of(context);
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
(_scrollController.position.maxScrollExtent - 50)) {
dataGenerator();
}
});
dataGenerator();
},
);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: numbers.length,
controller: _scrollController,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Card(
child: Column(
children: <Widget>[
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('Random number: ' + numbers[index].toString()),
Text('Index: ' + index.toString())
],
),
SizedBox(
height: 15,
),
],
),
),
);
},
);
}
}
This is the error that is thrown:
flutter: ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
flutter: The following assertion was thrown while dispatching notifications for ScrollController:
flutter: ScrollController attached to multiple scroll views.
flutter: 'package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 111 pos 12:
flutter: '_positions.length == 1'
flutter:
flutter: Either the assertion indicates an error in the framework itself, or we should provide substantially
flutter: more information in this error message to help you determine and fix the underlying cause.
flutter: In either case, please report this assertion by filing a bug on GitHub:
flutter: https://github.com/flutter/flutter/issues/new?template=BUG.md
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #2 ScrollController.position
package:flutter/…/widgets/scroll_controller.dart:111
flutter: #3 _BodyWidgetState.initState.<anonymous closure>.<anonymous closure>
package:primaryscrollcontroller_test/main.dart:97
flutter: #4 ChangeNotifier.notifyListeners
package:flutter/…/foundation/change_notifier.dart:206
flutter: #5 ChangeNotifier.notifyListeners
package:flutter/…/foundation/change_notifier.dart:206
flutter: #6 ScrollPosition.notifyListeners
package:flutter/…/widgets/scroll_position.dart:696
flutter: #7 ScrollPosition.setPixels
package:flutter/…/widgets/scroll_position.dart:218
flutter: #8 ScrollPositionWithSingleContext.setPixels
package:flutter/…/widgets/scroll_position_with_single_context.dart:84
flutter: #9 ScrollPositionWithSingleContext.applyUserOffset
package:flutter/…/widgets/scroll_position_with_single_context.dart:127
flutter: #10 ScrollDragController.update
package:flutter/…/widgets/scroll_activity.dart:372
flutter: #11 ScrollableState._handleDragUpdate
package:flutter/…/widgets/scrollable.dart:496
flutter: #12 DragGestureRecognizer.handleEvent.<anonymous closure>
flutter: #13 GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:166
flutter: #14 DragGestureRecognizer.handleEvent
package:flutter/…/gestures/monodrag.dart:182
flutter: #15 PointerRouter._dispatch
package:flutter/…/gestures/pointer_router.dart:73
flutter: #16 PointerRouter.route
package:flutter/…/gestures/pointer_router.dart:101
flutter: #17 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent
package:flutter/…/gestures/binding.dart:221
flutter: #18 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent
package:flutter/…/gestures/binding.dart:199
flutter: #19 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent
package:flutter/…/gestures/binding.dart:156
flutter: #20 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue
package:flutter/…/gestures/binding.dart:102
flutter: #21 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket
package:flutter/…/gestures/binding.dart:86
flutter: #25 _invoke1 (dart:ui/hooks.dart:233:10)
flutter: #26 _dispatchPointerDataPacket (dart:ui/hooks.dart:154:5)
flutter: (elided 5 frames from class _AssertionError and package dart:async)
flutter:
flutter: The ScrollController sending notification was:
flutter: ScrollController#be798(2 clients)
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: ScrollController attached to multiple scroll views.
flutter: Another exception was thrown: ScrollController attached to multiple scroll views.
flutter: Another exception was thrown: ScrollController attached to multiple scroll views.
flutter: Another exception was thrown: ScrollController attached to multiple scroll views.
The line flutter: Another exception was thrown: ScrollController attached to multiple scroll views. just keeps repeating many more times
How do I fix this issue?
Old, and there are lots of answers out there. I had similar issue, a few months ago:
The problem as the issue states, you have multiple positions (several scrollable views).
Declare as usual:
ScrollController scrollController;
//Note, Position, contains the información regarding the scroll offset, physics etc, this "positions" is an iterable., you might guess, here its where all scrolls "position" are stored (one for each scrollable view attached for this #scrollController" .
final scrollPositions = scrollController.positions;
//given the positions length I grabbed the latest one, which in my use case was always the last one (going back and forward doing new lists), you can adapt it to grab the position information depending on your needs.
final scrollPositionImInterested = scrollPositions
.elementAt(scrollController.positions.length - 1);
hope this helps someone in the same needs

Placing a widget in a column gives RenderIndexedStack object was given an infinite size during layout

I am placing the following in column like this
Widget _getDropDownCombo()
{
Widget combo = new DropdownButton(
value: _currentItem,
items:_dropDownMenuItems,
onChanged: _changedDropDownItem
);
return Flexible(child:combo);
}
and _getDropDownCombo is being called from a row somewhere like this
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_getDropDownCombo(),
getIcon(),
],)
This gives me the error
flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following assertion was thrown during performLayout():
flutter: RenderIndexedStack object was given an infinite size during layout.
flutter: This probably means that it is a render object that tries to be as big as possible, but it was put
flutter: inside another render object that allows its children to pick their own size.
flutter: The nearest ancestor providing an unbounded width constraint is:
flutter: RenderFlex#dfcbf relayoutBoundary=up17 NEEDS-LAYOUT NEEDS-PAINT
Why am I getting this error with DropDownButton ? Any suggestions ?
Their might be problem because of the items inside your DropdownButton.
I've tried using this in items and it works just fine:
items: ['Foo', 'Bar'].map((String value) {
return new DropdownMenuItem(
value: value,
child: new Text(value),
);
}).toList(),

AnimatedCrossFade with ListView got unbounded height error

here is the code. what I want is showing progress when requesting result, and display the result as a list when requesting completed.
I choose the AnimatedCrossFade cause it had convinient transition animation;
Widget SearchResultPage() {
return AnimatedCrossFade(
firstChild: Center(
child: CircularProgressIndicator(),
),
secondChild: ListView.builder(
itemCount: _searchResult.length,
itemBuilder: (BuildContext context, int index) {
return SearchListItem(_searchResult[index]);
}),
crossFadeState: _searchResult.isEmpty
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: Duration(milliseconds: 500));
}
Widget SearchListItem(BookFuzzySearchDetail detail) {
return Container(
decoration:
BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey))),
child: ListTile(
contentPadding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 4.0),
leading: Image.network(
detail.cover,
width: 50.0,
height: 50.0,
),
title: Text(
detail.title,
),
),
);
}
and I got the error:
I/flutter ( 6281): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 6281): The following assertion was thrown during performResize():
I/flutter ( 6281): Vertical viewport was given unbounded height.
I/flutter ( 6281): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter ( 6281): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter ( 6281): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter ( 6281): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter ( 6281): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter ( 6281): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter ( 6281): the height of the viewport to the sum of the heights of its children.
New to flutter and stucked here for a few days, appreciate for any advice please.
Try adding the property shrinkWrap to your ListView.builder
ListView.builder(
shrinkWrap: true,
itemCount: _searchResult.length,
itemBuilder: (BuildContext context, int index) {
return SearchListItem(_searchResult[index]);
}),
AnimatedCrossFade not only animates the opacity change of both widgets, but also the size transition; since the size of the list is unknown at build time (I mean build lifecycle method, not building the project) without shrinkWrap: true it throws an exception.
As #nickolay-savchenko pointed out in the comment to the accepted answer, shrinkWrap: true may cause significant performance issues and is definitely an unnecessary overkill for this case.
I'd advise using AnimatedSwitcher instead of AnimatedCrossFade, which will only do the opacity change (default behaviour).
Arguably, you don't need to animate the size change between a large possibly screen-overflowing list and "empty search results" widget.
Here is how I used it in one of my projects to animate transition between the list and "loading" widget:
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: items.isEmpty
? const LoadingWidget()
: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ...
),
),

how to distribute space between multiple items

I am trying to add an image that covers the top 20% of the screen and the other 80% should be a grid of cards. The image needs to be in the body and not on the Appbar. I made the grid of cards, and then I tried to put the image and the grid in a column. The implementation is the following.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
primary: true,
appBar: AppBar(
elevation: 4.0,
backgroundColor: Color(0xfff8f8f8),
title: Center(child: titleLogo,),
),
//------------ PROBLEM BELOW ----------------
body: new Column(
children: <Widget>[
titleLogo, //Image object
TheGridView().build() //Returns a GridView object
],
),
),
);
}
I am getting the following error
I/flutter (21751): The following assertion was thrown during performResize():
I/flutter (21751): Vertical viewport was given unbounded height.
I/flutter (21751): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter (21751): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter (21751): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter (21751): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter (21751): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter (21751): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter (21751): the height of the viewport to the sum of the heights of its children.
Any suggestion is highly appreciated. Thank you in advance.
for distributing space between multiple items you should use the Expanded widget like this:
return new Column(
children: <Widget>[
new Expanded(
flex: 2,
child: new Container(
color: Colors.red,
),
),
new Expanded(
flex: 8,
child: new Container(//use your Gridview instead
color: Colors.green,
)
)
],
);

How to pass multiple widgets as children in Flutter? [duplicate]

This question already has answers here:
Can't add a ListView in Flutter
(3 answers)
Closed 4 years ago.
I recently started learning Flutter and have been going through the documentation. I am working on this small app, where the screen has a button on the top of the screen and a list below it.
Whenever I pass RaisedButton with a ListView widget into another ListView or Column Widget, its throwing error.
I/flutter ( 4734): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 4734): The following assertion was thrown during performResize():
I/flutter ( 4734): Vertical viewport was given unbounded height.
I/flutter ( 4734): Viewports expand in the scrolling direction to fill their container.
////MORE LINES OF ERRORS/////
Here's the code I have been working on:
import 'package:flutter/material.dart';
void main() {
runApp(ListDemo(
items: new List<ListItem>.generate(
100,
(i) => i % 6 == 0
? new HeadingItem("Heading $i")
: new MessageItem("Sender $i", "Message body $i"),
),
));
}
// The base class for the different types of items the List can contain
abstract class ListItem {}
// A ListItem that contains data to display a heading
class HeadingItem implements ListItem {
final String heading;
HeadingItem(this.heading);
}
// A ListItem that contains data to display a message
class MessageItem implements ListItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
}
class ListDemo extends StatelessWidget {
final List<ListItem> items;
ListDemo({Key key, #required this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
final ListView listView = ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
if (item is HeadingItem) {
return new ListTile(
title: new Text(
item.heading,
style: Theme.of(context).textTheme.headline,
),
);
} else if (item is MessageItem) {
return new ListTile(
title: new Text(item.sender),
subtitle: new Text(item.body),
);
}
},
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Lists'),
),
body: ListView( //Tried using ListView, Column.. None of them help solving the issue
children: <Widget>[
RaisedButton(
onPressed: null,
child: Text('Sample Button'),
),
Container(
child: listView,
)
]
)
)
);
}
}
Please help me solve this issue of letting know, how to pass multiple children, and also please make understand the concept as well.
EDITED
One of the possible solutions suggested wrapping ListView with Expanded class. When I did it threw me an error as below:
I/flutter ( 4190): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4190): The following assertion was thrown building NotificationListener<KeepAliveNotification>:
I/flutter ( 4190): Incorrect use of ParentDataWidget.
I/flutter ( 4190): Expanded widgets must be placed inside Flex widgets.
I/flutter ( 4190): Expanded(no depth, flex: 1, dirty) has no Flex ancestor at all.
So I wrapped the entire Widget code in Flex as below:
Flex(
direction: Axis.vertical,
children: <Widget>[
ListView(
children: <Widget>[
RaisedButton(
onPressed: null,
child: Text('Snackbar'),
),
Expanded(
child: listView
)
],
)
],
)
but then it threw me this error:
I/flutter ( 4388): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 4388): The following assertion was thrown during performResize():
I/flutter ( 4388): Vertical viewport was given unbounded height.
I/flutter ( 4388): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter ( 4388): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter ( 4388): typically happens when a scrollable widget is nested inside another scrollable widget.
This question is already answered here
Can't add a ListView in Flutter
If you use a scrollable view(Listview) inside another scrollable view, the inner scrollable view doesn't know how much height it should occupy. You can tell the inner scrollable view about how much height it should take using an Expanded widget.

Resources