Flutter GridView ListTiles appear below scroll container - flutter

I have a Flutter scrolling GridView builder that looks like it renders the ListTiles below the Container the GridView is placed in. It appears it renders placeholder grid list tiles even when they are not within the Container bounds.
It looks like for some reason that the GridView ListTiles appear outside (below) the parent container.
In this example the GridView is placed within a Container that is then placed as a child under a parent Scrollbar that is a child widget in a Column. The GridView appears to render the grid ListTiles (renders the background Tile color but not the Tile contents) outside its parent container, appearing as Tiles under the other child widgets of the parent Column widget. See screen grabs below.
Grid List tiles under the Container
When I scroll the list up the shadow Tiles appear in the GridView with the Tile contents.
When I scroll back the Tiles below the GridView appear below?/under? the other widgets in the parent Column. See first image the colored area under the "Bye" text widget.
`
import 'package:flutter/material.dart';
import 'package:scroll5/models/task_model.dart';
/// This is the stateful widget that the main application instantiates.
class ScrollListWidget extends StatefulWidget {
final List<TaskCard> _list;
ScrollListWidget({Key? key})
: _list = [],
super(key: key);
const ScrollListWidget.withList(
List<TaskCard> list, {
Key? key,
}) : _list = list,
super(key: key);
#override
State<ScrollListWidget> createState() => _ScrollListStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _ScrollListStatefulWidgetState extends State<ScrollListWidget> {
#override
Widget build(BuildContext context) {
return Container(height: 240, child: GridView.builder(
shrinkWrap: true,
physics: ScrollPhysics(),
itemCount: widget._list.length,
itemBuilder: (context, index) => WordTile(
widget._list[index].taskName(), widget._list[index].daysEffort()),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 4,
),
));
}
}
class WordTile extends StatelessWidget {
final String word;
final int score;
const WordTile(this.word, this.score, {super.key});
#override
Widget build(BuildContext context) {
return ListTile(
tileColor: Colors.green.withOpacity(.4), title: Text(word));
}
}`
GridView within a Container contained within a Scrollbar and Column
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
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(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child:
Column(children: [
Scrollbar(
controller: _scrollController,
thickness: 8,
interactive: true,
thumbVisibility: true,
child:ScrollListWidget.withList(_taskList)
),
Text("Bye",style: TextStyle())])),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add)));
// This trailing comma makes auto-formatting nicer for build methods.
}`

Related

How can i smoothly scroll between NestedScrollView and ListView.builder

I have the following simple full code ..
import 'package:flutter/material.dart';
class Profile extends StatefulWidget {
const Profile({ Key? key, }) : super(key: key);
#override
ProfileState createState() => ProfileState();
}
class ProfileState extends State<Profile>{
#override
Widget build(BuildContext context) {
return SafeArea(
child: NestedScrollView(
headerSliverBuilder: (context,value){
return[
const SliverAppBar(
expandedHeight: 400,
)
];
},
body: ListView.builder(
itemCount: 200,
itemBuilder: (BuildContext context, int index) {
return Center(child: Text(index.toString()));
},
)
),
);
}
}
in the previous code, everything is ok and it shifted the scroll in a smooth way BUT when I provide ScrollController into my ListView.builder the scroll is no longer smooth anymore.
so Please How could I keep the first result (with no providing ScrollController) the same as (with providing ScrollController)? .
I recreated your requirements using CustomScrollView, the API is "harder" to use but it allows you implement more complex features (like nested scrollviews as you are doing) because we have direct access to the Slivers API.
You can see that almost any Flutter scrollable widget is a derivation of either CustomScrollView or ScrollView which makes use of Slivers.
NestedScrollView is a subclass of CustomScrollView.
ListView and GridView widgets are subclasses of ScrollView.
Although seems complicated a Sliver is just a portion of a scrollable area:
CustomScrollView(
controller: _scrollController,
slivers: [
const SliverAppBar(expandedHeight: 400),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Center(child: Text('item $index.'));
},
),
),
],
)
So, instead of creating multiple scrollviews and trying to mix them together (which lead to buggy behaviors), just declare multiple scrollable areas and put them together inside a CustomScrollView, that's it.
See the working example at: https://dartpad.dev/?id=60cb0fa073975f3c80660815ae88af4e.

how to disable scrollview when pinch zoom in image

I am using the lib pinch_zoom_release_unzoom to pinch zoom image. I create it inside SingleChildScrollView but when user use 2 finger to pinch zoom image. it very hard to zoom because sometime page is Scrollable. so I want to solve this problem
this is my example code
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:pinch_zoom_release_unzoom/pinch_zoom_release_unzoom.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Tutorial',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
String imageUrl = 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg';
TransformationController controller = TransformationController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Tutorial'),
),
body: Column(
children: [
Center(
child: ElevatedButton(
onPressed: () {
showMaterialModalBottomSheet(
expand: false,
context: context,
builder: (context) => PinchZoomReleaseUnzoomWidget(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const SizedBox(
height: 100,
),
Image.network(imageUrl),
const SizedBox(
height: 1000,
),
],
),
),
),
);
},
child: const Text(
'showModalBottomSheet',
),
),
),
],
),
);
}
}
You can interact with physics of scrollable widget to make scrolling different. For that purpose, you should change your physic inside SingleChildScrollView , whenever your zooming state changes. For example:
lass ParentWidget extends StatefulWidget {
const ParentWidget ({Key? key,}) : super(key: key);
#override
State<ParentWidget > createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget > {
late bool isScrolling;
#override
void initState() {
isScrolling = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: isScrolling ? NeverScrollableScrollPhysics() : null,
child: YourWidget(function: (currentState) => setState(() {
isScrolling = currentState;
}));
class YourWidget extends StatelessWidget {
const SceneManaging({Key? key, this.callback}) : super(key: key);
final Function(bool isScrolling)? function;
}
In this case, you should call function inside your child widget whenever you use zoom. When it zooms - pass to it a true, to disable your parent widget scroll, whenever zooms actions stops - pass false.
physics: NeverScrollableScrollPhysics()
How the scroll view should respond to user input.
For example, determines how the scroll view continues to animate after the user stops dragging the scroll view.
Defaults to matching platform conventions. Furthermore, if primary is false, then the user cannot scroll if there is insufficient content to scroll, while if primary is true, they can always attempt to scroll.
To force the scroll view to always be scrollable even if there is insufficient content, as if primary was true but without necessarily setting it to true, provide an AlwaysScrollableScrollPhysics physics object, as in:
physics: const AlwaysScrollableScrollPhysics(),
To force the scroll view to use the default platform conventions and not be scrollable if there is insufficient content, regardless of the value of primary, provide an explicit ScrollPhysics object, as in:
physics: const ScrollPhysics(),
The physics can be changed dynamically (by providing a new object in a subsequent build), but new physics will only take effect if the class of the provided object changes. Merely constructing a new instance with a different configuration is insufficient to cause the physics to be reapplied. (This is because the final object used is generated dynamically, which can be relatively expensive, and it would be inefficient to speculatively create this object each frame to see if the physics should be updated.)

How I can update a listview element without set state?

I have a List view. Builder with an image. The image has an opacity depending on a provider element (provider element is true, the opacity is 1, if it false opacity is 0). Then I have another class, from that class I can update the provider of the list view image, however in order to change the effect of the change, I have to set state the list view widget.
Even if it is good and is working properly, I don't want to do that because when I set state the list view widget, the list view position restart and I need to scroll again at the element of the list view, I know that I can save the scroll position (with a controller) but because my list view is too large, it needs a lot of time to get at the position, so I don't like it.
Any idea ?
If you want to rebuild the UI without calling setState, you can use the help of ValueNotifier and the ValueListenableBuilder widget.
Here is a simplified counter app, if you take a look at the console, you'll see that print('build'); is only called initially running the build method and not called again when calling _increment():
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: PageLoadApp()));
final counter = ValueNotifier<int>(0);
class PageLoadApp extends StatefulWidget {
const PageLoadApp({Key? key}) : super(key: key);
#override
State<PageLoadApp> createState() => _PageLoadAppState();
}
class _PageLoadAppState extends State<PageLoadApp> {
void _increment() {
counter.value++;
}
#override
Widget build(BuildContext context) {
print('build');
return Scaffold(
body: Scaffold(
body: ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, _) {
return Center(
child: Text('$value', style: const TextStyle(fontSize: 30)),
);
}),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
)));
}
}
Additionally, this widget can be a StatelessWidget.
Since you haven't provided a code example, you'll have to adapt this provided example for your case.
Here is a YouTube video by the Google team explaining ValueListenableBuilder

Flutter - Using Dismissible on a PageView creates an ugly animation

with the following example code, is get a very ugly animation.
I would even say, it's no animation at all.
The next Page will just appear after the setstate is called.
How can I create a smooth delete animation using PageView?
If it is not possible via PageView, is there any alternative, that has the "snapping cards" feature?
Here is my code:
class SwipeScreen extends StatefulWidget {
const SwipeScreen({Key key}) : super(key: key);
static const routeName = '/swipe';
#override
_SwipeScreenState createState() => _SwipeScreenState();
}
class _SwipeScreenState extends State<SwipeScreen> {
List<String> content = ['one', 'two', 'three', 'four', 'five'];
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
scrollDirection: Axis.vertical,
itemCount: content.length,
controller: PageController(viewportFraction: 0.8),
itemBuilder: (context, index) {
return Dismissible(
key: ValueKey(content[index]),
child: Card(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
child: Text('test'),
),
),
onDismissed: (direction) {
setState(() {
content = List.from(content)..removeAt(index);
});
},
);
},
),
);
}
}
Replacing PageView.builder() with ListView.builder() will create a smoother animation.
Hopefully this is what you're looking for!
Unfortunately, the PageView widget is not intended to be used with the Dismissible widget as the animation when the dismiss is complete is not implemented.
You can still change your PageView to a ListView and set a physics to PageScrollPhysics() to get the animation on dismiss but you will probably encounter some other issues on Widget sizes

How to trigger a function call when sliver child is in the center of the screen?

I have a sliver list in a custom scroll view.
Each child in the sliver list has a dynamic height. I want to trigger a function call in my viewmodel whenever one of the child widgets is crossing the center point of the screen view.
As shown below, I am using MVVM (FilledStack's architecture) which uses the ViewModelProvider to link the view to a viewmodel. This is generating a list of "Post Cards" views.
class HomeView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ViewModelProvider<HomeViewModel>.withConsumer(
viewModel: HomeViewModel(),
onModelReady: (model) => model.initialise(),
builder: (BuildContext context, HomeViewModel model, Widget child) =>
SafeArea(
child: Scaffold(
bottomNavigationBar: _buildBottomAppBar(model),
body: CustomScrollView(
controller: model.scrollController,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int postIndex) => PostCard(
postIndex: postIndex,
),
childCount: model.posts?.length,
),
),
],
);
),
),
);
}
I tried global key inside of the PostCard:
GlobalKey _postKey = GlobalKey();
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
super.initState();
}
This calls a _afterLayout function after the item is rendered. This required me to change it to a stateful widget for the init state and it works to print the location but it feels very much like a hack.
Is there a cleaner way to get the position and size of each child of a sliver list?
I solved this by creating my own CustomSliverList and CustomRenderSliverList.
In the CustomRenderSliverList's performLayout() function you can use the constraints.scrollOffset and constraints.remainingPaintExtent to get the current view port and then using the child RenderBox object to figure out position and size of each child.
From that you can use a callback function to perform any action when it enters the view port.