I'm trying to call list view inside the body with a list view its shows nothing "empty"
How I can fix that by calling another list view inside list view? It's hard to explain that.
body: new ListView(
children: <Widget>[
HomePageListView(),
],
),
The following is Nested ListView example. please notice shrinkWrap: true and Container height
You can refactor inner part to another widget
code snippet original
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return ExpansionTile(
title: Text(litems[Index]),
children: <Widget>[
Text(litems[Index]),
Container(
height : 300,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: litems1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems1[Index]);
}),
)
],
);
}),
)
code snippet refactor to widget
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return ExpansionTile(
title: Text(litems[Index]),
children: <Widget>[
Text(litems[Index]),
new innerOneWidget(litems1: litems1)
],
);
}),
),
...
class innerOneWidget extends StatelessWidget {
const innerOneWidget({
Key key,
#required this.litems1,
}) : super(key: key);
final List<String> litems1;
#override
Widget build(BuildContext context) {
return Container(
height : 300,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: litems1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems1[Index]);
}),
);
}
}
full code
import 'package:flutter/material.dart';
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(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
List<String> litems = ['1', '2', '3', '4'];
List<String> litems1 = [' hi ', ' this ', ' is ', ' long ', ' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long ',' long '];
#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(
// Column is also layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return ExpansionTile(
title: Text(litems[Index]),
children: <Widget>[
Text(litems[Index]),
new innerOneWidget(litems1: litems1)
],
);
}),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class innerOneWidget extends StatelessWidget {
const innerOneWidget({
Key key,
#required this.litems1,
}) : super(key: key);
final List<String> litems1;
#override
Widget build(BuildContext context) {
return Container(
height : 300,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: litems1.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems1[Index]);
}),
);
}
}
demo picture, outer ListView scroll vertical and inner ListView scroll horizontal
just true shrinkWrap
shrinkWrap : true
On your HomePageListView set
shrinkWrap : true
to listview
Use bellow properties on the second List
shrinkWrap: true,
physics: NeverScrollableScrollPhysics()
Related
I needed a floating SliverAppBar with TabBarView. Each tab has a CustomScrollView to scroll it's Childs. If i put PageView as a child and i scroll fast then the PageView starts with last page instead of first page. I found a sample code here in flutter docs:
https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html
And i just changed the ListTile inside the SliverChildBuilderDelegate with a PageView. But when i test this code and scroll fast the pageView starts with last page.
Here's a demo of that
https://gyazo.com/79709286236fa3dbf1f88b1e92f5cee3
And here's my code:
// This example shows a [NestedScrollView] whose header is the combination of a
// [TabBar] in a [SliverAppBar] and whose body is a [TabBarView]. It uses a
// [SliverOverlapAbsorber]/[SliverOverlapInjector] pair to make the inner lists
// align correctly, and it uses [SafeArea] to avoid any horizontal disturbances
// (e.g. the "notch" on iOS when the phone is horizontal). In addition,
// [PageStorageKey]s are used to remember the scroll position of each tab's
// list.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final List<String> _tabs = <String>['Tab 1', 'Tab 2'];
return DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title:
const Text('Books'), // This is the title in the app bar.
pinned: true,
floating: true,
expandedHeight: 150.0,
// The "forceElevated" property causes the SliverAppBar to show
// a shadow. The "innerBoxIsScrolled" parameter is true when the
// inner scroll view is scrolled beyond its "zero" point, i.e.
// when it appears to be scrolled below the SliverAppBar.
// Without this, there are cases where the shadow would appear
// or not appear inappropriately, because the SliverAppBar is
// not actually aware of the precise position of the inner
// scroll views.
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return PageView(
/// [PageView.scrollDirection] defaults to [Axis.horizontal].
/// Use [Axis.vertical] to scroll vertically.
scrollDirection: Axis.horizontal,
children: const <Widget>[
Center(
child: Text('First Page'),
),
Center(
child: Text('Second Page'),
),
Center(
child: Text('Third Page'),
)
],
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 100,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
How can i make the pageView to starts with first page? I tried setting this to pageView controller: PageController(initialPage: 0), But it didn't work.
Do you guys know how can I update a single item of a sliver list without having to invoke a setState() ?
In my case I have a SliverList and i want to click into an item and change it's color, the problem using setState() is that it rebuilds the whole UI in a not smooth way and also mess up with Custom Scroll position.
The funny thing is that this SliverList behaviour does not occur when using normal ListView, when use setState() on a ListView the load is smooth and it doesn't break the scroll state. Looks like the ListView can implicitely handle state better than SliverList.
But since I have a Custom Scroll I can't use ListVew it has to be SliverList
Any options ? Providers ?Notifiers ? Stream ? Bloc ?
Ok, after all, I could solve my need using a simple ChangeNotifier combined with an AnimatedBuilder inside each item of the SliverList, I'll post some high-level code of the solution - it works just fine for my need!
class ChangeColorSliverListItemNotifier extends ChangeNotifier {
int index;
Color current_label_color;
ChangeColorSliverListItemNotifier()
{
this.current_label_color = Colors.white;
}
void onTap(int selected_index)
{
index = selected_index;
this.current_label_color = Colors.yellow;
notifyListeners();
}
}
// code block inside SliverList items binding- changing only the color of the selected Widget
SliverList(
delegate: SliverChildBuilderDelegate((context, index)
{
return GestureDetector(
onTap:() {
changeColorSliverListItemNotifier.onTap(index);
},
child:AnimatedBuilder(
animation: changeColorSliverListItemNotifier,
builder: (_, __) =>
Row(children:[
Container(
margin: EdgeInsets.only(right:8),
child: Icon(Icons.edit,color:changeColorSliverListItemNotifier.index==index?changeColorSliverListItemNotifier.current_label_color:default_color,size: 20,)
),
])
)
)
....
Just in case you want to use the Riverpod state management library, I made the following.
class SliverScreen extends StatelessWidget {
const SliverScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_appBar(),
_list(),
],
),
);
}
SliverFixedExtentList _list() {
return SliverFixedExtentList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListItem(index: index),
),
itemExtent: 100,
);
}
SliverAppBar _appBar() {
return SliverAppBar(
title: Text("Slivering..."),
backgroundColor: Colors.teal[900],
expandedHeight: 200,
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.teal[100]!,
Colors.teal[600]!,
],
),
),
),
),
);
}
}
final colorStateProvider = StateProvider.family<Color, int>((ref, key) {
return Colors.blue[100]!;
});
class ListItem extends HookConsumerWidget {
final int index;
const ListItem({Key? key, required this.index}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final colorState = ref.watch(colorStateProvider(index));
return InkWell(
onTap: () => colorState.state = Colors.blue,
child: Container(
color: colorState.state,
child: Center(
child: Text("Item $index"),
),
),
);
}
}
If you click an item, it updates the color without rebuilding the whole list, only the item itself.
My requirement is to create a page with collapsing toolbar and two tabs.
To do this, I am using the code below.
SafeArea(
child: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverPersistentHeader(
pinned: true,
delegate: _Header(),
)
];
},
body: TabBarView(
children: <Widget>[
Container1(),
Container2(),
],
),
),
),
)
Everything works, but once the scrolling starts and the header collapses, the body keeps on scrolling even if all the widgets are visible.
How do I make it behave like Android native, where if the list is small in size, the list doesn't scroll after the header collapses.
For anyone who is facing the same issue, this is how I fixed it.
You can just copy the entire code and run it to check and modify accordingly.
The code is taken from the official documentation.
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_module/app_constants.dart';
import 'package:flutter_module/data/local/shared_pref.dart';
import 'package:flutter_module/data/remote/api_end_points.dart';
import 'package:flutter_module/string_localization.dart';
import 'package:flutter_module/ui/router.dart';
import 'package:flutter_module/util.dart';
import 'package:http/http.dart' as http;
import 'data/local/dao/sync_dao.dart';
import 'data/remote/response_pojo/video_challenges_response.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainCollapsingToolbar(),
);
}
}
class MainCollapsingToolbar extends StatefulWidget {
#override
_MainCollapsingToolbarState createState() => _MainCollapsingToolbarState();
}
class _MainCollapsingToolbarState extends State<MainCollapsingToolbar> {
#override
Widget build(BuildContext context) {
var _tabs = ["One", "Two"];
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(minHeight: 100, maxHeight: 150, child: Container(
color: Colors.blue,
)),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return ListTile(
title: Text('Item $index'),
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 1,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
wrap your body with Expanded widget
After days of search I'm getting help.
I work on a flutter application.
Context:
A grid view feeded with Json
-childs : GridTile with Flipcard in (https://pub.dev/packages/flip_card)
-On tap on GridTile there is a callback to get the selected Item and an animation because of the flipcard onTap
What I would:
When an item is aleready selected (flipcard flipped so we show the back of the card),
And I selected another item of the grid te(so flipcard of this itme also flipped)
I would like to flip back the old selected item Flipcard without rebuild the tree because I would lost the state of the new selected item.
I tried many thing. For example I tried to use GlobalKey on GridTiles to interract with after build but currentState is always null when I want to interact with.
I wonder what is the good practice in this case ?
I hope I was clear :) (I'm french)
Thank you the community!
.
Something to know...
It is possible to interract with the flipcard (child of gridtile) like this
(GlobalKey)
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
#override
Widget build(BuildContext context) {
return FlipCard(
key: cardKey,
flipOnTouch: false,
front: Container(
child: RaisedButton(
onPressed: () => cardKey.currentState.toggleCard(),
child: Text('Toggle'),
),
),
back: Container(
child: Text('Back'),
),
);
}
I'm not sure if I understood your question, but here is an example of how you could use a GridView with FlipCards:
var cardKeys = Map<int, GlobalKey<FlipCardState>>();
GlobalKey<FlipCardState> lastFlipped;
Widget _buildFlipCard(String text, Color color, int index) {
return SizedBox(
height: 120.0,
child: Card(
color: color,
child: Center(
child:
Text(text, style: TextStyle(color: Colors.white, fontSize: 20.0)),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FlipCards")),
body: GridView.builder(
itemCount: 20,
itemBuilder: (context, index) {
cardKeys.putIfAbsent(index, () => GlobalKey<FlipCardState>());
GlobalKey<FlipCardState> thisCard = cardKeys[index];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlipCardWithKeepAlive(
child: FlipCard(
flipOnTouch: false,
key: thisCard,
front: _buildFlipCard("$index", Colors.blue, index),
back: _buildFlipCard("$index", Colors.green, index),
onFlip: () {
if (lastFlipped != thisCard) {
lastFlipped?.currentState?.toggleCard();
lastFlipped = thisCard;
}
},
),
),
RaisedButton(
child: Text("Flip Card"),
onPressed: () => cardKeys[index].currentState.toggleCard(),
)
],
);
},
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
),
);
}
class FlipCardWithKeepAlive extends StatefulWidget {
final FlipCard child;
FlipCardWithKeepAlive({Key key, this.child}) : super(key: key);
#override
State<StatefulWidget> createState() => FlipCardWithKeepAliveState();
}
class FlipCardWithKeepAliveState extends State<FlipCardWithKeepAlive>
with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}
#override
bool get wantKeepAlive => true;
}
You need to use a different key for each element of the list, I used a Map in this case.
I also wrapped the FlipCard with a custom FlipCardWithKeepAlive stateful widget that uses AutomaticKeepAliveClientMixin to keep alive the FlipCard while scrolling.
Edit: I updated the code so when you flip one card, the previous card flipped gets flipped back. Basically you need to save the last flipped card and when a new one is flipped, flip the last one and put the new one as last flipped.
The code will make both cards flip at the same time, if you want one card to wait the other use onFlipDone() instead of onFlip(), like this:
onFlipDone: (isFront) {
bool isFlipped = !isFront;
if (isFlipped && lastFlipped != thisCard) {
lastFlipped?.currentState?.toggleCard();
lastFlipped = thisCard;
}
}
By default, flutter adds a glowing effect on ListView/GridView/... to overscrolls on android phones
I would like to remove this effect entirely or on one specific scrollable.
I know that I can change ScrollPhysics to change between Bounce/Clamp. But this doesn't actually remove the glow effect.
What can I do ?
The glow effect comes from GlowingOverscrollIndicator added by ScrollBehavior
To remove this effect, you need to specify a custom ScrollBehavior. For that, simply wrap any given part of your application into a ScrollConfiguration with the desired ScrollBehavior.
The following ScrollBehavior will remove the glow effect entirely :
class MyBehavior extends ScrollBehavior {
#override
Widget buildOverscrollIndicator(
BuildContext context, Widget child, ScrollableDetails details) {
return child;
}
}
To remove the glow on the whole application, you can add it right under MaterialApp :
MaterialApp(
builder: (context, child) {
return ScrollConfiguration(
behavior: MyBehavior(),
child: child,
);
},
home: new MyHomePage(),
);
To remove it on a specific ListView, instead wrap only the desired ListView :
ScrollConfiguration(
behavior: MyBehavior(),
child: ListView(
...
),
)
This is also valid if you want to change the effect. Like adding a fade when reaching borders of the scroll view.
The glow will disappear by changing the ListView's physics property to BouncingScrollPhysics to imitate the List behavior on iOS.
ListView.builder(
physics: BouncingScrollPhysics(),
}
The above solution did not work for me. I did this from another solution.
Wrap it with this widget to remove the shadow completely:
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (overscroll) {
overscroll.disallowGlow();
},
child: new ListView.builder(
//Your stuff here.
),
),
You can try BouncingScrollPhysics with all list or grid or scrollview:
//ScrollView:
SingleChildScrollView(
physics: BouncingScrollPhysics(),
)
//For ListView:
ListView.builder(
physics: BouncingScrollPhysics(),
}
//GridView
GridView.Builder(
physics: BouncingScrollPhysics(),
)
You can wrap your SingleChildScrollView or ListView.
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
return;
},
child: SingleChildScrollView()
)
Update on 2021
as buildViewportChrome is deprecated on March `21, we may have new way to implement this
A. Working Solution
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
return child;
}
}
class MainApp extends StatelessWidget {
const MainApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
title: 'App Title',
home: HomeUI(),
);
}
}
B. Explanation
By default, Flutter wraps any child widget into GlowingOverscrollIndicator as below code.
#override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return GlowingOverscrollIndicator(
axisDirection: details.direction,
color: Theme.of(context).colorScheme.secondary,
child: child, // < ---------- our Child Widget is wrapped by Glowing Indicator
);
}
}
So we can easily override it, by directly return child without wrapping it to GlowingOverscrollIndicator
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Widget buildOverscrollIndicator(
BuildContext context, Widget child, ScrollableDetails details) {
return child;
}
}
You don't need to build your own custom ScrollBehavior class. Instead, just wrap your scrollable widget in a ScrollConfiguration widget and set the behavior property to:
const ScrollBehavior().copyWith(overscroll: false).
Full code example:
ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(overscroll: false),
child: PageView(
physics: const PageScrollPhysics(),
controller: model.pageController,
children: [
PageOne(),
PageTwo(),
PageThree(),
PageFour(),
],
),
),
try this work for me mybe work for you to
ScrollConfiguration(
behavior: new ScrollBehavior()..buildViewportChrome(context, null, AxisDirection.down),
child: SingleChildScrollView()
);
You can also try
SingleChildScrollView(
physics: ClampingScrollPhysics(),
)
If you migrated to null safety, you might get issues with the behavior. You can use this method that works with null safety:
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification? overscroll) {
overscroll!.disallowGlow();
return true;
},
child: child,
),
The currently accepted answer is outdated in the current version of Flutter.
Scroll behavior's ScrollBehavior.copyWith() method has an overscroll flag which can be set to false to avoid having to create your own ScrollBehavior class.
For example:
ScrollConfiguration(
behavior: MaterialScrollBehavior().copyWith(overscroll: false),
child : someScrollableWidget
)
`
It isn't good practice to just change the scroll behavior, as you may lose the native scrolling feel when running your app on different devices.
I have used below one for Scroll body without Scroll glow effect
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScrollConfiguration(
behavior: new ScrollBehavior()
..buildViewportChrome(context, null, AxisDirection.down),
child: SingleChildScrollView(
After Flutter 2.10 update Previous NotificationListener parameter code has been removed/deprecated.
New Code
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (overscroll) {
overscroll.disallowIndicator(); //previous code overscroll.disallowGlow();
return true;
},
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 15),
scrollDirection: Axis.horizontal,
children: List.generate(
items.length,
(index) => Padding(
padding: const EdgeInsets.only(right: 15),
child: AspectRatio(
aspectRatio: 13 / 9,
child:
LayoutBuilder(builder: (context, boxcon) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black12,
spreadRadius: 5,
blurRadius: 12)
],
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(items[index])),
color: greengradientcolor,
),
);
}),
))),
),
),