PageView "jumps" when dragged - dart

Has somebody noticed this?
I have a PageView, and when I drag it to slide to the next page, it first doesn't move, and then after I drag a bit more, the slide suddenly "jumps", as if the distance dragged all happen instantly.
I also run the app in release and profile mode, but it's always the same behaviour. Also the amount of subviews per page doesn't seem to affect it.
import 'package:flutter/material.dart';
import 'TipField.dart';
import '../data/TipFieldData.dart';
import 'package:flutter_ticket/theme.dart';
class Ticket extends StatefulWidget {
#override
_TicketState createState() => _TicketState();
}
class _TicketState extends State<Ticket> {
#override
Widget build(BuildContext context) {
return Container(
width: 400.0,
height:400.0,
child: PageView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return TipField(
index,
7,
TipFieldData(1, 6),
AppColors.redLight,
AppColors.redLightest,
AppColors.red
);
},
itemCount: 12,
)
);
}
}

Related

Flutter carousel image slider open separate page during on tap event is called

Im new to flutter. I would like to ask a question about my code. I have take a look on youtube and some google tutorial on this inkwell and on tap function to open new class activity on flutter.But the result is, when the image is tapped it open different image screen but they share same class file.
How can I have a separate page for different image click. For example,
I have five image in my flutter carousel slider.
Image 1 will open sliderpage 1. Image 2 will open sliderpage 2 and so on.Means they are on separate page instead of different image open same page but only show different images. Im trying this tutorial but they do have same page but different images displayed after on tap event is called. url https://www.youtube.com/watch?v=l9XOUoJsdy4
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
Widget image_carousel = new Container(
height: 345.0,
child: new Carousel(
boxFit: BoxFit.fill,
images: [
AssetImage('assets/s7.jpg'),
AssetImage('assets/s3.jpg'),
AssetImage('assets/s5.jpg'),
AssetImage('assets/s2.jpg'),
AssetImage('assets/s4.jpg'),
],
autoplay: true,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 500),
dotColor: Colors.red[50],
dotSize: 4.0,
indicatorBgPadding: 2.0,
),
);
return Scaffold(
body: new Column(
children: <Widget>[
image_carousel,
//grid view
Container(
height:163.0,
child: Products(),
)
],
),
);
}
}
On this code, this code just display carousel image without any event on click is done , I was expecting to have different page routing by on tap event is happen when image assets is clicked and navigate to different pages.
First of all, you need to install carousel_slider, then create two screens:
The first one will contain carousel_slider when you click on the image it will navigate to the second screen and passing image URL of the image you clicked on, To have on tap event you need to wrap you Image widget with GestureDetector
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
import './image_screen.dart';
void main() => runApp(MaterialApp(home: Demo()));
class Demo extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<Demo> {
#override
Widget build(BuildContext context) {
Widget image_carousel = new Container(
height: 345.0,
child: CarouselSlider(
height: 400.0,
items: [
'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg',
'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg'
].map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
decoration: BoxDecoration(color: Colors.amber),
child: GestureDetector(
child: Image.network(i, fit: BoxFit.fill),
onTap: () {
Navigator.push<Widget>(
context,
MaterialPageRoute(
builder: (context) => ImageScreen(i),
),
);
}));
},
);
}).toList(),
));
return Scaffold(
body: new Column(
children: <Widget>[
image_carousel,
],
),
);
}
}
The second screen will contain only the image you clicked on:
import 'package:flutter/material.dart';
class ImageScreen extends StatefulWidget {
final String url;
ImageScreen(this.url);
#override
_MyImageScreen createState() => _MyImageScreen(url);
}
class _MyImageScreen extends State<ImageScreen> {
final String url;
_MyImageScreen(this.url);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ImageScreen'),
),
body: Image.network(url, width: double.infinity));
}
}

flutter ListView scroll to index not available

What I needed:
I want to scroll a list by some index, how can i do that.
What I know:
scrollToIndex should start from n index, but how can we scroll to any index?
Unfortunately, ListView has no built-in approach to a scrollToIndex() function. You’ll have to develop your own way to measure to that element’s offset for animateTo() or jumpTo(), or you can search through these suggested solutions/plugins or from other posts like Flutter: Scrolling to a widget in ListView
(the general scrollToIndex issue is discussed at flutter/issues/12319 since 2017, but still with no current plans)
But there is a different kind of ListView that does support scrollToIndex (as mentioned by Slashbin):
ScrollablePositionedList
dependency: scrollable_positioned_list
You set it up exactly like ListView and works the same, except you now have access to a ItemScrollController that does:
jumpTo({index, alignment})
scrollTo({index, alignment, duration, curve})
Simplified example:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: _myList.length,
itemBuilder: (context, index) {
return _myList[index];
},
)
_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));
(note that this library is developed by Google but not by the core Flutter team.)
ScrollablePositionedList can be used for this.
https://github.com/google/flutter.widgets/tree/master/packages/scrollable_positioned_list
Pub link - https://pub.dev/packages/scrollable_positioned_list
final ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionListener = ItemPositionsListener.create();
ScrollablePositionedList.builder(
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionListener,
);
One then can scroll to a particular item with:
itemScrollController.scrollTo(
index: 150,
duration: Duration(seconds: 2),
curve: Curves.easeInOutCubic);
Use scroll_to_index lib, here scroll will be always performed to sixth position as its hardcoded below
dependencies:
scroll_to_index: ^1.0.6
Code Snippet:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final scrollDirection = Axis.vertical;
AutoScrollController controller;
List<List<int>> randomList;
#override
void initState() {
super.initState();
controller = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
scrollDirection: scrollDirection,
controller: controller,
children: <Widget>[
...List.generate(20, (index) {
return AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: Container(
height: 100,
color: Colors.red,
margin: EdgeInsets.all(10),
child: Center(child: Text('index: $index')),
),
highlightColor: Colors.black.withOpacity(0.1),
);
}),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
// Scroll listview to the sixth item of list, scrollling is dependent on this number
Future _scrollToIndex() async {
await controller.scrollToIndex(6, preferPosition: AutoScrollPosition.begin);
}
}
Output:
It's simple to scroll to preferred index if you know listview item size like:
var index = 15;
var widthOfItem =
176; //in dp. width needed for horizontallistView;
var heightOfItem =
200; //in dp. height need for vertical listView;
listViewScrollController.jumpTo(index * widthOfItem.toDouble());
or
listViewScrollController.animateTo(
index * widthOfItem.toDouble(),
duration: Duration(milliseconds: 500),
curve: Curves.ease);
Found a gist and its working gist url is https://gist.github.com/debuggerx01/b8ef756ee02b3eb82ec3702f14ba94e6
this gist is using a package which is calculation rect size.
https://pub.dartlang.org/packages/rect_getter
You can use the flutter_scrollview_observer lib to implement your desired functionality without invasivity
Create and use instance of ScrollController normally.
ScrollController scrollController = ScrollController();
ListView _buildListView() {
return ListView.separated(
controller: scrollController,
...
);
}
Create an instance of ListObserverController pass it to ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
controller: observerController,
child: _buildListView(),
...
)
Now you can scroll to the specified index position
// Jump to the specified index position without animation.
observerController.jumpTo(index: 1)
// Jump to the specified index position with animation.
observerController.animateTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);

Prepend list view items while maintaining scroll view offset in Flutter

I'm looking for a way to insert new items into a list view, while maintaining the scroll offset of the user. Basically like a twitter feed after pulling to refresh: the new items get added on top, while the scroll position is maintained. The user can then just scroll up to see the newly added items.
If I just rebuild the list/scroll widget with a couple of new items at the beginning, it -of course- jumps, because the height of the scroll view content increased. Just estimating the height of those new items to correct the jump is not an option, because the content of the new items is variable.
Even the AnimatedList widget which provides methods to dynamically insert items at an arbitrary position jumps when inserting at index 0.
Any ideas on how to approach this? Perhaps calculating the height of the new items beforehand using an Offstage widget?
Ran into this problem recently: I have a chat scroll that async loads previous or next messages depending on the direction of the scroll. This solution worked out for me.
The idea of the solution is the following. You create two SliverLists and put them inside CustomScrollView.
CustomScrollView(
center: centerKey,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
// Here we render elements from the upper group
child: top[index]
)
}
),
SliverList(
// Key parameter makes this list grow bottom
key: centerKey,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
// Here we render elements from the bottom group
child: bottom[index]
)
}
),
)
The first list scrolls upwards while the second list scrolls downwards. Their offset zero points are fixed at the same point and never move. If you need to prepend an item you push it to the top list, otherwise, you push it to the bottom list. That way their offsets don't change and your scroll view does not jump.
You can find the solution prototype in the following dartpad example.
UPD: Fixed null safety issue in this dartpad example.
I don't know if you managed to solve it... Marcin Szalek has posted a very nice solution on his blog about implementing an infinite dynamic list. I tried it and works like a charm with a ListView. I then tried to do it with an AnimatedList, but experienced the same issue that you reported (jumping to the top after each refresh...). Anyway, a ListView is quite powerful and should do the trick for you!
The code is:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(primarySwatch: Colors.blue),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<int> items = List.generate(10, (i) => i);
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false;
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
_getMoreData() async {
if (!isPerformingRequest) {
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(
items.length, items.length + 10); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent -
_scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge - offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;
});
}
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Infinite ListView"),
),
body: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildProgressIndicator();
} else {
return ListTile(title: new Text("Number $index"));
}
},
controller: _scrollController,
),
);
}
}
/// from - inclusive, to - exclusive
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 2), () {
return List.generate(to - from, (i) => i + from);
});
}
A gist containing whole class can be found here.
I think reverse + lazyLoading will help you.
Reverse a list:
ListView.builder(reverse: true, ...);
for lazyLoading refer here.

Flutter TextFormField reloads current screen when focused

I have a TextFormField that reloads the current screen when I tap on it to enter text. When I tap on the formfield the software keyboard is displayed briefly before the entire screen reloads and renders all the widgets again. I am running the app on an Android device.
Container(
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Your input cannot be empty';
}
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
print('validated');
}
},
child: Text('Save'),
),
),
],
),
),
margin: EdgeInsets.only(top:8.0),
),
The problem is that the controller of the TextFormField is rebuild when you click on the field, and that's the reason of your issue.
So to solve that, did you try to create a Statefull widget and then creating a TextEditingController in the State of this widget and passing it as an argument to the TextFormField ?
I had the same Problem. this was my code
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Model model = Model();
#override
Widget build(BuildContext context) {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
var mediaWidth = MediaQuery.of(context).size.width / 2.0;
return Scaffold(
...
and I solved this problem by declaring the _formKey outside of build method. and this worked for me.
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Model model = Model();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
var mediaWidth = MediaQuery.of(context).size.width / 2.0;
return Scaffold(
...
hope it will help you
Yes, that happens because when the keyboard appears, the flutter scaffold gets resize to the current available screen size. So, we can easily handle this by preventing the scaffold size change. I suggest to set scaffold resizeToAvoidBottomInset property false. If it's true the body and the scaffolds floating widgets should size themselves to avoid the onscreen keyboard whose height is defined by the ambient MediaQuery's, MediaQueryData,viewInsets bottom property.
Solution:
resizeToAvoidBottomInset: false,
Complete example:
#override
Widget build(BuildContext context) {
setDisplayData();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: getAppBar(),
body: OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? _buildVerticalLayout()
: _buildHorizontalLayout();
},
),
);
Check if you are using MediaQueries wrongly in your project, I had similar issue and it stopped when I changed the MediaQuery
in my case:
Size _size = MediaQuery.of(context).size;
removing this piece of code fixed my app.
When TextFormField focused the size of screen will changed because of the appearance of keyboard, that cause rebuild of state, you cant prevent re-build of state.
Instead of trying prevent re-build state, you need to solve problems which happen when state do re-build, one of common problem is declaration and initialization variables inside build(BuildContext context){ ... }' function.
The main problem, when you need to get some data related of context (like size of screen), in this case I prefer to pass this value from parent Widget...
For example this code will cause problem when re-build state:
#override
Widget build(BuildContext context) {
double? _screenHeight = MediaQuery.of(context).size.height;
return Container();
}
To solve problem get _screenHeight from parent, to know how to do that look at https://stackoverflow.com/a/50289032/2877427

Flutter NotificationListener with ScrollNotification vs ScrollController

There are two options to retrieve the scroll position for a CustomScrollView. The documentation states the following:
ScrollNotification and NotificationListener, which can be used to watch the scroll position without using a ScrollController.
So we have the following options:
NotificationListener with ScrollNotification
ScrollController
In which situation do you useNotificationListener with ScrollNotification vs ScrollController?
Thank you :)
If you're using NestedScrollView with nested scrollers, using a scrollController on the inner scrollers will break the link with NestedScrollView meaning NestedScrollView will no longer control the complete scrolling experience. To get information about the scroll positions of the inner scrollers in this case you would use a NotificationListener with ScrollNotification.
NotificationListener<ScrollNotification>(
child: ListView.builder(
itemCount: 10
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
},
),
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent) {
onLoadMore();
}
},
);
Related Answer here.
Although you can use both ScrollNotification and ScrollController to listen scroll position changes, and many times it is not going to be difference, you should keep in mind some details in order to better choose the best tool for the job:
ScrollController
To use a ScrollController you should use a Stateful widget, so that it can be properly dispose.
Even if you can use the same ScrollController to control several scrollable widgets, some operations, such as reading the scroll offset, require the controller to be used with a single scrollable widget.
If you want to change the scroll position, you are going to need a ScrollController (jumpTo / animateTo).
When you listen to a ScrollController, you are listening to ScrollPosition changes, what is not exactly the same as ScrollNotification. Using ScrollNotifications you can differentiate among several types of events related to the scrolling action, like scroll start, scroll end, dirección change, etc.
NotificationListener
NotificationListener is just another widget, so yo don't need to create a Stateful widget.
It listens ScrollNotifications from all scrollable widgets below in the widget tree, as notifications propagate upwards. So it is not constraint to listen only one widget.
You cannot change the scroll position from a NotificationListener. It is read only.
You listen to ScrollNotification, and not to changes in a ScrollPosition, so you can recognize easily which king of event has just happened.
it my demo use NotificationListener with ScrollController. After drag a litter left, the blue part will automatic move
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
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) {
List<Widget> list = [];
for (int i = 0; i < 100; i++) list.add(buildContainer());
return Scaffold(
body: ListView(children: list));
}
Widget buildContainer() {
ScrollController _scrollController = ScrollController();
return NotificationListener<ScrollNotification>(
onNotification: (scrollState) {
if (scrollState is ScrollEndNotification && scrollState.metrics.pixels != 160) {
Future.delayed(const Duration(milliseconds: 100), () {}).then((s) {
_scrollController.animateTo(160,
duration: Duration(milliseconds: 500), curve: Curves.ease);
});
}
return false;
},
child: Container(
height: 160,
margin: EdgeInsets.only(bottom: 1),
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
controller: _scrollController,
children: <Widget>[
Container(
width: 360,
height: 20,
color: Colors.red,
),
Container(
width: 160,
height: 20,
color: Colors.blue,
),
],
),
),
);
}
}

Resources