i am trying to recreate App bar scrolling with overlapping content in Flexible space using flutter.
the behavior is demonstrated here:
http://karthikraj.net/2016/12/24/scrolling-behavior-for-appbars-in-android/
I created collapsing AppBar using SliverAppBar already, using the code I pasted here, I am trying to create THIS
i cant use Stack for it because i cant find any onScroll callback, so far i created appbar with flexibleSpace, the app bar collapse on scroll:
Scaffold(
body: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) => <Widget>[
SliverAppBar(
forceElevated: innerBoxIsScrolled,
pinned: true,
expandedHeight: 180.0,
),
],
body: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) => Text(
"Item $index",
style: Theme.of(context).textTheme.display1,
),
),
),
);
edit: Example of what i want to create
ScrollViews take a ScrollController which is a Listenable that notifies on scroll offset updates.
You can listen to the ScrollController and use a Stack to achieve the effect you're interested in based on the scroll offset.
Here's a quick example:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Scroll demo',
home: new Scaffold(
appBar: new AppBar(elevation: 0.0),
body: new CustomScroll(),
),
);
}
}
class CustomScroll extends StatefulWidget {
#override
State createState() => new CustomScrollState();
}
class CustomScrollState extends State<CustomScroll> {
ScrollController scrollController;
double offset = 0.0;
static const double kEffectHeight = 100.0;
#override
Widget build(BuildContext context) {
return new Stack(
alignment: AlignmentDirectional.topCenter,
children: <Widget> [
new Container(
color: Colors.blue,
height: (kEffectHeight - offset * 0.5).clamp(0.0, kEffectHeight),
),
new Positioned(
child: new Container(
width: 200.0,
child: new ListView.builder(
itemCount: 100,
itemBuilder: buildListItem,
controller: scrollController,
),
),
),
],
);
}
Widget buildListItem(BuildContext context, int index) {
return new Container(
color: Colors.white,
child: new Text('Item $index')
);
}
void updateOffset() {
setState(() {
offset = scrollController.offset;
});
}
#override
void initState() {
super.initState();
scrollController = new ScrollController();
scrollController.addListener(updateOffset);
}
#override
void dispose() {
super.dispose();
scrollController.removeListener(updateOffset);
}
}
I think the SliverAppbar widget is what you are looking for.
Take a look at this article which shows you how to achieve your goal.
https://flutterdoc.com/animating-app-bars-in-flutter-cf034cd6c68b
Your should also take a look at https://medium.com/#diegoveloper/flutter-collapsing-toolbar-sliver-app-bar-14b858e87abe
Related
I'm unable to access the scrollContoller in the ListView.builder. I think its because it's below SingleChildScrollView (when I remove SingleChildScrollView I start getting print statements from the listener attached to the scrollController ). I need to access the offset from scrollController in the ListView.builder but not sure how to do so. Here's a minimum example. How would I get the listener printing from the controller in the listView..
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ScrollController scrollController = ScrollController();
#override
void initState() {
scrollController.addListener(() {
print("Why am I not getting print statements....");
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
),
body: SingleChildScrollView(
child: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: 400,
color: Colors.orange,
),
ListView.builder(
controller: scrollController,
physics: ScrollPhysics(),
shrinkWrap: true,
itemCount: 30,
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.5),
child: ListTile(
title: Text('Item $index'),
),
);
},
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: HomePage()));
}
I've a initialScrollOffset in my ScrollController set to 100. This allows the top of my ListView in my Scaffold body to appear under my appBar. (Desired behaviour, my appbar is transparent). This works fine when the page first loads but I'd like my ScrollController to prevent my listView from ever scrolling between 0 and 100 and always have an initialScrollOffset of 100. ScrollController has a boolean property keepScrollOffset` but that doesn't prevent the scroll going from 0-100. Here is my code so far:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ScrollController _scrollController;
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Container(
color: Colors.blue.withOpacity(0.25),
child: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.5),
child: ListTile(
title: Text('Item $index'),
),
);
},
controller: _scrollController,
),
),
);
}
#override
void initState() {
_scrollController = ScrollController(initialScrollOffset: 100, keepScrollOffset: true)
..addListener(() {
print("offset = ${_scrollController.offset}");
});
super.initState();
}
}
void main() {
runApp(MaterialApp(home: HomePage()));
}
If you want to add padding to the inner content that scrolls up and down with the items, you can just add padding to the ListView.
padding: EdgeInsets.only(top: 100)
Not necessary to use ScrollController.
My current project has a scrollable row which works fine, but I can't find a way to make some kind of static scroll, where when you scroll to an item, the item is centered automatically.
This is what I currently have:
When the cards are scrolled they stay in the position when the scroll finish:
The effect I want to accomplish is the card aligning itself in the center when scrolling to the next card.
Is there a widget that actually works like that?
Try this,
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final List<String> images = ["Me", "You", "Foo", "Baa"];
final PageController controller = PageController(viewportFraction:0.8);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: PageView.builder(
controller: controller,
itemCount: images.length,
itemBuilder: (context,index){
return Container(
margin: EdgeInsets.all(16),
child: Center(child:Text(images[index])),
color: Colors.grey.shade200
);
}
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text('Hello, World!', style: Theme.of(context).textTheme.headline4);
}
}
The solution was simple:
Padding(
padding: const EdgeInsets.symmetric(vertical: 80),
child: Container(
child: PageView(
controller: controller,
scrollDirection: Axis.horizontal,
pageSnapping: true,
physics: BouncingScrollPhysics(),
children: [CardMovie(), CardMovie(), CardMovie()],
),
),
)
using PageView widget and giving a padding top because the height of the widget was the full screen.
I'm making an app divided to two sections: one(upper section) is PageView widget area, another(lower section) is Container widget area. I want the lower section to show 'we are in X page' when I change pages in the upper section.
I tried to use index of PageView widget in Container widget, but console said "undefined name 'index'".
So I declared like int index; as a global variable, and tried again, but it doesn't work. I think this index is different from index of PageView widget.
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int index;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${index} page!'),
),
),
)
],
),
);
}
}
I'm a beginner of programming, and doing this as a hobby.
But I really like it. Actually I gave up my own study and career and stick to programming now. I hope you help me solve this problem.
Thank you. I love you.
yes. like controller.page for the current page.
class Sample extends StatelessWidget{
final int value;
Sample(this.value);
build(context) => Text("you are in $value");
}
and use Sample(controller.page)
EDIT: your code should be
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int currentPage=0;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
onPageChanged: (page){
setState(() {
currentPage=page;
});
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${currentPage} page!'),
),
),
)
],
),
);
}
}
Just add listener to PageController like that:
#override
void initState() {
super.initState();
index = 0;
controller.addListener(() {
setState(() {
index = controller.page.toInt();
});
});
}
I am trying to reproduce the following example from the earlier Material design specifications (open for animated demo):
Until now I was able to produce the scrolling effect, but the overlap of the content is still missing. I couldn't find out how to do this properly.
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Title'),
expandedHeight: 200.0,
primary: true,
pinned: true,
),
SliverFixedExtentList(
itemExtent: 30.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) => Text('Item $i')
),
),
],
),
);
}
}
I managed to get this functionality, using the ScrollController and a couple of tricks:
Here's the code:
ScrollController _scrollController;
static const kHeaderHeight = 235.0;
double get _headerOffset {
if (_scrollController.hasClients) if (_scrollController.offset > kHeaderHeight)
return -1 * (kHeaderHeight + 50.0);
else
return -1 * (_scrollController.offset * 1.5);
return 0.0;
}
#override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(() => setState(() {}));
}
#override
Widget build(BuildContext context) {
super.build(context);
return StackWithAllChildrenReceiveEvents(
alignment: AlignmentDirectional.topCenter,
children: [
Positioned(
top: _headerOffset,
child: Container(
height: kHeaderHeight,
width: MediaQuery.of(context).size.width,
color: Colors.blue,
),
),
Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0),
child: Feed(controller: _scrollController, headerHeight: kHeaderHeight),
),
],
);
}
To make the Feed() not overlap the blue container, I simply made the first child of it a SizedBox with the required height property.
Note that I am using a modified Stack class. That is in order to let the first Widget in the stack (the blue container) to detect presses, so it will fit my uses; unfortunately at this point the default Stack widget has an issue with that, you can read more about it over https://github.com/flutter/flutter/issues/18450.
The StackWithAllChildrenReceiveEvents code can be found over https://github.com/flutter/flutter/issues/18450#issuecomment-575447316.
I had the same problem and could not solve it with slivers. This example from another stackoverflow question solved my problem.
flutter - App bar scrolling with overlapping content in Flexible space
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Scroll demo',
home: new Scaffold(
appBar: new AppBar(elevation: 0.0),
body: new CustomScroll(),
),
);
}
}
class CustomScroll extends StatefulWidget {
#override
State createState() => new CustomScrollState();
}
class CustomScrollState extends State<CustomScroll> {
ScrollController scrollController;
double offset = 0.0;
static const double kEffectHeight = 100.0;
#override
Widget build(BuildContext context) {
return new Stack(
alignment: AlignmentDirectional.topCenter,
children: <Widget> [
new Container(
color: Colors.blue,
height: (kEffectHeight - offset * 0.5).clamp(0.0, kEffectHeight),
),
new Positioned(
child: new Container(
width: 200.0,
child: new ListView.builder(
itemCount: 100,
itemBuilder: buildListItem,
controller: scrollController,
),
),
),
],
);
}
Widget buildListItem(BuildContext context, int index) {
return new Container(
color: Colors.white,
child: new Text('Item $index')
);
}
void updateOffset() {
setState(() {
offset = scrollController.offset;
});
}
#override
void initState() {
super.initState();
scrollController = new ScrollController();
scrollController.addListener(updateOffset);
}
#override
void dispose() {
super.dispose();
scrollController.removeListener(updateOffset);
}
}
Change the list to a grid and its what you want