how to hide a widget in flutter? - flutter

I am trying to hide a widget which has List array and List array gets _images.length. For Example, if image.length is Null, like if there are no images so I want to hide the container which takes space.Not sure what I am missing. I tried the code below. Help me out pls, Thanks. or if there is any other way to do it pls let me know. I am just a beginner.
class PortfolioGallarySubPage extends StatefulWidget{
PortfolioGallarySubPage({Key key,#required this.urls,#required this.currentIndex})
:super(key:key);
#override
_PortfolioGallarySubPage createState() => _PortfolioGallarySubPage();
}
class _PortfolioGallarySubPage extends State<PortfolioGallarySubPage>
with SingleTickerProviderStateMixin{
final GlobalKey<FormState> formKey = new GlobalKey<FormState>();
final GlobalKey<ScaffoldState> key = new GlobalKey<ScaffoldState>();
List<File> _images = [];
final picker = ImagePicker();
Future getImage() async {
final pickedFile = await picker.getImage(source: ImageSource.gallery);
setState(() {
if (pickedFile != null) {
_images.add(File(pickedFile.path));
}
else {
print('No image selected.');
}
});
}
#override
void initState() {
super.initState();
}
#override void dispose()
{
super.dispose();
}
bool isVisible = true;
void changeVisibility(){
setState(() {
if(_images.length ==null ){
isVisible = !isVisible;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: key,
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
ElevatedButton(
child: Text("DONE",style: TextStyle(fontSize: 15)),
onPressed: (){
_uploadImages();
},
style: ElevatedButton.styleFrom(padding: EdgeInsets.fromLTRB(25.0, 15.0, 25.0, 10.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0))),
),
],
),
body: Container(
width: _maxScreenWidth,
child: SafeArea(
child:Form(
key: formKey,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: isVisible,
child: Container(
height: 150.0,
padding: EdgeInsets.symmetric(vertical: 15.0,horizontal: 15),
child: ListView(
scrollDirection: Axis.horizontal,
children: [
SizedBox(width: 15.0),
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: _images.length,
itemBuilder: (BuildContext context,int index){
//return Padding(padding: const EdgeInsets.only(top: 0.0,bottom: 0.0),
return InkWell(
onTap: () => print('tapped'),
child: Card(
elevation: 10,
child: SizedBox(height:150, width: 150,child: Image.file(_images[index], fit: BoxFit.cover,)) ,
),
);
},
),
],
),
),
),
],
),
),
],
),
),
),
),
),
);
}
}

_images array length will always return 0 if the list is empty, so you need to set the condition as
setState(() {
isVisible = _images.length > 0;
});
Instead of taking variable isVisible set the _images.length > 0 like
visible: _images.length > 0
And remove the isVisible variable.... it will update the visibility when _images list is updated

there is another solution of this without using visible widget :
class Mywidget extends StatefulWidget {
#override
_MywidgetState createState() => _MywidgetState();
}
class _MywidgetState extends State<Mywidget> {
double width;
double heigth;
void changeVisibility() {
setState(() {
if (_images.length == null) {
width=any width you want ;
heigth = any height you want ;
}else{
setState(() {
width=0;
heigth=0;
});
}
});
}
#override
Widget build(BuildContext context) {
// the contanier that you want to be visble
return Container(
height: heigth,
width: width,
// the list view that has the images
child: ListView(),
);
}
}
if there is an image the height and the width of the widget will be not zero
but if not the widget will be visible because the width and the height will be equal to zero

As I can see in the snippet, you are not calling the changeVisibility method anywhere. Hence, isVisible will always remain true
So make a call to changeVisibility wherever you are calling getImage method.
Also, the logic is inherently wrong,
initialize isVisible to false initially, this way you can make it true when there is an image.

Related

Flutter: Use AbsorbPointer without rebuilding entire widget tree

I have a Stateful home page which has a list of Stateful widget children. When I click on a child, I'm gonna call its setState() to add a CircularProgressIndicator to it. That's all fine and dandy; clicking on a child only rebuilds that child.
However, I also have my home page wrapped inside an AbsorbPointer, and I want to set absorbing = true when I click on a child widget. The goal is to stop the user from clicking around while the app is doing some async work in the background. The problem now is that if I call setState() in the home page to set "absorbing" to true, it will rebuild all of the child widgets.
I could pass some parameters into the child widgets so that only the one I clicked on will have a CircularProgressIndicator, but even then all the other children will still be rebuilt.
I guess this boils down to the fact that I can't call setState() on a parent widget without rebuilding all the children, even though the parameter I pass to that setState() (absorbing) has nothing to do with those children.
Is there a workaround for this?
Thanks!
// home_screen.dart
class HomeScreen extends StatefulWidget {
static const String routeName = "homeScreen";
final MyUser? user;
const HomeScreen({Key? key, required this.user}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final MyDatabase _db = MyDatabase();
MyUser? _me;
int _currentPage = -1;
bool _isLoading = false;
...
#override
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: _isLoading,
child: Container(
decoration: BoxDecoration(
// color: Color(0xFF0d0717),
image: DecorationImage(
image: Image.asset(
'assets/background.png',
).image,
fit: BoxFit.cover,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: ...,
bottomNavigationBar: ...,
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 12.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StreamBuilder<QuerySnapshot>(
stream: _db.getLiveChannels(),
builder: (_, snapshot) {
if (!snapshot.hasData) {
// print("Has no data");
return Center(
child: CircularProgressIndicator(),
);
}
_channels.addAll(List.generate(
snapshot.data!.docs.length,
(index) => Channel.fromSnapshot(
snapshot.data!.docs[index])));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Now Playing',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w700,
),
),
SizedBox(width: 8.0),
LiveIndicator(),
],
),
SizedBox(height: 8.0),
Container(
height: 250,
child: PageView.builder(
physics: PageScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: PageController(
viewportFraction: .9,
),
itemCount: _channels.length,
itemBuilder: (context, index) {
Channel channel =
_channels[_channels.length - 1 - index];
return ChildWidget(
callback: _callback;
loading: (_isLoading && _currentPage == index),
key: UniqueKey(),
);
},
),
),
...,
],
);
},
),
...,
],
),
),
),
),
),
);
}
Future<void> _callback(params) async {
if (_isLoading == false) {
setState(() {
_isLoading = true;
_currentPage = index;
});
}
someAsyncMethod().then((_) => setState(() {
_isLoading = false;
_currentPage = -1;
}));
}
}
// child_widget.dart
class ChildWidget extends StatefulWidget {
final Future<void> Function(params) callback;
final bool loading;
const ChildWidget({
Key? key,
required this.callback,
required this.loading,
}) : super(key: key);
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
late Future<void> Function(params) callback;
late bool loading;
#override
void initState() {
super.initState();
callback = widget.callback;
loading = widget.loading;
}
#override
Widget build(BuildContext context) {
return Padding(
child: Column(
children: [
Expanded(
child: CustomClickableWidget(
onPressed: callback,
child: Expanded(
child: Container(
child: Stack(
children: [
...,
if (loading) ...[
Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
],
],
),
),
),
),
),
...,
],
),
);
}
}
Screenshot
The SetState function triggers the Build() function, so all the code present in the Build() function will be executed again. I don't quite see why this is a problem for you ?
On the other hand in your code I see that for your child you have defined a key: UniqueKey (). When the build function will run after SetState (), it will create a new child without keeping the state of the previous child. You shouldn't define the UniqueKey () in your function but rather as an instance variable of your state
ChildWidget(callback: _callback;
loading: (_isLoading && _currentPage == index),
key: UniqueKey(),
)
You should define you key here
class _ChildWidgetState extends State<ChildWidget> {
UniqueKey myKey = UniqueKey();
and you function
ChildWidget(callback: _callback;
loading: (_isLoading && _currentPage == index),
key: myKey,
)

How to create a card in a listview with button press on Flutter?

Listview will be empty after the click I would like to create a card in the ListView on flutter.
Is it also possible to create a dynamic home page? For example when there will be no any card on the list it is going to write on the background there is no any card yet. But if card created this indication will be deleted.
Could you please support me regarding this topic?
import 'package:flutter/material.dart';
class ListScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
bool _isLoading = true;
List<String> _items = [];
#override
void initState() {
super.initState();
_getListData();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
margin: EdgeInsets.all(10),
child: !_isLoading && _items.isEmpty
? Center(
child: Text("No data found"),
)
: (_isLoading && _items.isEmpty)
? Container(
color: Colors.transparent,
child: Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.pink),
),
),
)
: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return _createListRow(_items[index], index);
},
),
),
),
);
}
_createListRow(String item, int index) {
return Card(
elevation: 3,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(item),
FlatButton(
child: Text("Delete"),
onPressed: () {
setState(() {
_items.removeAt(index);
});
},
)
],
),
);
}
_getListData() {
// Create dynamic list
Future.delayed(Duration(milliseconds: 500));
setState(() {
_items.add("First");
_items.add("Second");
_items.add("Third");
_isLoading = false;
});
}
}
You should check the official documentation. It's not so hard to learn with it :
ListView
Card
InkWell

Flutter: ScrollController initialScrollOffset not working

I'm trying to initialize a SingleChildScrollView to start at a certain position with a custom ScrollController. I thought I could use initialScrollOffset and set an initial value in the init method. But somehow when the SingleChildScrollView renders, it only jumps to initialOffset at first build, then when I navigate to another instance of this Widget it doesn't jump to the initialOffset position.
I don't know why, and if I'm lucky maybe one of you have the answer.
Here's my code:
class Artiklar extends StatefulWidget {
final String path;
final double arguments;
Artiklar({
this.path,
this.arguments,
});
#override
_ArtiklarState createState() => _ArtiklarState(arguments: arguments);
}
class _ArtiklarState extends State<Artiklar> {
final double arguments;
_ArtiklarState({this.arguments});
ScrollController _scrollController;
double scrollPosition;
#override
void initState() {
super.initState();
double initialOffset = arguments != null ? arguments : 22.2;
_scrollController = ScrollController(initialScrollOffset: initialOffset);
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final bool isAdmin = Provider.of<bool>(context) ?? false;
var pathElements = widget.path.split('/');
String tag;
if (pathElements.length == 2) {
tag = null;
} else if (pathElements.length == 3) {
tag = pathElements[2];
} else {
tag = null;
}
return StreamBuilder<List<ArtikelData>>(
stream: DatabaseService(tag: tag).artiklarByDate,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return GlobalScaffold(
body: SingleChildScrollView(
child: Container(
child: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GradientHeading(text: "Artiklar", large: true),
isAdmin
? NormalButton(
text: "Skapa ny artikel",
onPressed: () {
Navigator.pushNamed(
context, createNewArtikelRoute);
},
)
: Container(),
SizedBox(height: 10),
SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
child: TagList(path: tag),
),
SizedBox(height: 10),
LatestArtiklar(
snapshot: snapshot,
totalPosts: snapshot.data.length,
numberOfPosts: 10,
),
],
),
),
),
),
),
);
} else if (!snapshot.hasData) {
return GlobalScaffold(
body: SingleChildScrollView(
child: Container(
child: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GradientHeading(text: "Artiklar", large: true),
SizedBox(height: 10),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: TagList(path: tag),
),
SizedBox(height: 10),
LatestArtiklar(hasNoPosts: true)
],
),
),
),
),
),
);
} else {
return GlobalScaffold(
body: Center(child: CircularProgressIndicator()),
);
}
},
);
}
}
That's because that widget is already built on the tree and thus, initState won't be called again for that widget.
You can override the didUpdateWidget method that will trigger each time that widget is rebuilt and make it jump on there, for example.
#override
void didUpdateWidget(Widget old){
super.didUpdateWidget(old);
_scrollController.jumpTo(initialOffset);
}
keepScrollOffset: false
If this property is set to false, the scroll offset is never saved and initialScrollOffset is always used to initialize the scroll offset.

Flutter CustomScrollView slivers stacking

I am trying to create a scrollView using CustomScrollView.
The effect that I need, is very similar to this one.
I need the SliverList to be stacked above the SliverAppbar, without the list taking the whole screen and hiding the SliverAppbar.
The reason I want to do this, is that i need to attach a persistent Positioned widget on top of that list, and it won't appear unless the list is stacked above the SliverAppbar.
Here's my code.
Step one:
Use ListView inside SliverAppBar widget. To make css overflow:hidden effect.
Step two:
Add controller to NestedScrollView and move the button on scrolling in a stack. Plus calculate where you want to stop button moving.
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController scrollController;
final double expandedHight = 150.0;
#override
void initState() {
super.initState();
scrollController = new ScrollController();
scrollController.addListener(() => setState(() {}));
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
double get top {
double res = expandedHight;
if (scrollController.hasClients) {
double offset = scrollController.offset;
if (offset < (res - kToolbarHeight)) {
res -= offset;
} else {
res = kToolbarHeight;
}
}
return res;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
NestedScrollView(
controller: scrollController,
headerSliverBuilder: (context, value) {
return [
SliverAppBar(
pinned: true,
expandedHeight: expandedHight,
flexibleSpace: ListView(
physics: const NeverScrollableScrollPhysics(),
children: [
AppBar(
title: Text('AfroJack'),
elevation: 0.0,
),
Container(
color: Colors.blue,
height: 100,
alignment: Alignment.center,
child: RaisedButton(
child: Text('folow'),
onPressed: () => print('folow pressed'),
),
),
],
),
),
];
},
body: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: 80,
itemBuilder: (BuildContext context, int index) {
return Text(
'text_string'.toUpperCase(),
style: TextStyle(
color: Colors.white,
),
);
},
),
),
Positioned(
top: top,
width: MediaQuery.of(context).size.width,
child: Align(
child: RaisedButton(
onPressed: () => print('shuffle pressed'),
child: Text('Suffle'),
),
),
),
],
),
);
}
}

Flutter: Scrolling to a widget in ListView

How can I scroll to a special widget in a ListView?
For instance I want to scroll automatically to some Container in the ListView if I press a specific button.
ListView(children: <Widget>[
Container(...),
Container(...), #scroll for example to this container
Container(...)
]);
By far, the easiest solution is to use Scrollable.ensureVisible(context). As it does everything for you and work with any widget size. Fetching the context using GlobalKey.
The problem is that ListView won't render non-visible items. Meaning that your target most likely will not be built at all. Which means your target will have no context ; preventing you from using that method without some more work.
In the end, the easiest solution will be to replace your ListView by a SingleChildScrollView and wrap your children into a Column. Example :
class ScrollView extends StatelessWidget {
final dataKey = new GlobalKey();
#override
Widget build(BuildContext context) {
return new Scaffold(
primary: true,
appBar: new AppBar(
title: const Text('Home'),
),
body: new SingleChildScrollView(
child: new Column(
children: <Widget>[
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
// destination
new Card(
key: dataKey,
child: new Text("data\n\n\n\n\n\ndata"),
)
],
),
),
bottomNavigationBar: new RaisedButton(
onPressed: () => Scrollable.ensureVisible(dataKey.currentContext),
child: new Text("Scroll to data"),
),
);
}
}
NOTE : While this allows to scroll to the desired item easily, consider this method only for small predefined lists. As for bigger lists you'll get performance problems.
But it's possible to make Scrollable.ensureVisible work with ListView ; although it will require more work.
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 ListView scroll to index not available
(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:
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));
Please not that although the scrollable_positioned_list package is published by google.dev, they explicitly state that their packages are not officially supported Google products. - Source
Screenshot (Fixed height content)
If your items have fixed height, then you can use the following approach.
class HomePage extends StatelessWidget {
final ScrollController _controller = ScrollController();
final double _height = 100.0;
void _animateToIndex(int index) {
_controller.animateTo(
index * _height,
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_downward),
onPressed: () => _animateToIndex(10),
),
body: ListView.builder(
controller: _controller,
itemCount: 20,
itemBuilder: (_, i) {
return SizedBox(
height: _height,
child: Card(
color: i == 10 ? Colors.blue : null,
child: Center(child: Text('Item $i')),
),
);
},
),
);
}
}
For people are trying to jump to widget in CustomScrollView.
First, add this plugin to your project.
Then look at my example code below:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
AutoScrollController _autoScrollController;
final scrollDirection = Axis.vertical;
bool isExpaned = true;
bool get _isAppBarExpanded {
return _autoScrollController.hasClients &&
_autoScrollController.offset > (160 - kToolbarHeight);
}
#override
void initState() {
_autoScrollController = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection,
)..addListener(
() => _isAppBarExpanded
? isExpaned != false
? setState(
() {
isExpaned = false;
print('setState is called');
},
)
: {}
: isExpaned != true
? setState(() {
print('setState is called');
isExpaned = true;
})
: {},
);
super.initState();
}
Future _scrollToIndex(int index) async {
await _autoScrollController.scrollToIndex(index,
preferPosition: AutoScrollPosition.begin);
_autoScrollController.highlight(index);
}
Widget _wrapScrollTag({int index, Widget child}) {
return AutoScrollTag(
key: ValueKey(index),
controller: _autoScrollController,
index: index,
child: child,
highlightColor: Colors.black.withOpacity(0.1),
);
}
_buildSliverAppbar() {
return SliverAppBar(
brightness: Brightness.light,
pinned: true,
expandedHeight: 200.0,
backgroundColor: Colors.white,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: BackgroundSliverAppBar(),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(40),
child: AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: isExpaned ? 0.0 : 1,
child: DefaultTabController(
length: 3,
child: TabBar(
onTap: (index) async {
_scrollToIndex(index);
},
tabs: List.generate(
3,
(i) {
return Tab(
text: 'Detail Business',
);
},
),
),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _autoScrollController,
slivers: <Widget>[
_buildSliverAppbar(),
SliverList(
delegate: SliverChildListDelegate([
_wrapScrollTag(
index: 0,
child: Container(
height: 300,
color: Colors.red,
)),
_wrapScrollTag(
index: 1,
child: Container(
height: 300,
color: Colors.red,
)),
_wrapScrollTag(
index: 2,
child: Container(
height: 300,
color: Colors.red,
)),
])),
],
),
);
}
}
Yeah it's just a example, use your brain to make it this idea become true
This solution improves upon other answers as it does not require hard-coding each elements' heights. Adding ScrollPosition.viewportDimension and ScrollPosition.maxScrollExtent yields the full content height. This can be used to estimate the position of an element at some index. If all elements are the same height, the estimation is perfect.
// Get the full content height.
final contentSize = controller.position.viewportDimension + controller.position.maxScrollExtent;
// Index to scroll to.
final index = 100;
// Estimate the target scroll position.
final target = contentSize * index / itemCount;
// Scroll to that position.
controller.position.animateTo(
target,
duration: const Duration(seconds: 2),
curve: Curves.easeInOut,
);
And a full example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Test",
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = ScrollController();
final itemCount = 1000;
return Scaffold(
appBar: AppBar(
title: Text("Flutter Test"),
),
body: Column(
children: [
ElevatedButton(
child: Text("Scroll to 100th element"),
onPressed: () {
final contentSize = controller.position.viewportDimension + controller.position.maxScrollExtent;
final index = 100;
final target = contentSize * index / itemCount;
controller.position.animateTo(
target,
duration: const Duration(seconds: 2),
curve: Curves.easeInOut,
);
},
),
Expanded(
child: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return ListTile(
title: Text("Item at index $index."),
);
},
itemCount: itemCount,
),
)
],
),
);
}
}
You can use GlobalKey to access buildercontext.
I use GlobalObjectKey with Scrollable.
Define GlobalObjectKey in item of ListView
ListView.builder(
itemCount: category.length,
itemBuilder: (_, int index) {
return Container(
key: GlobalObjectKey(category[index].id),
You can navigate to item from anywhere
InkWell(
onTap: () {
Scrollable.ensureVisible(GlobalObjectKey(category?.id).currentContext);
You add scrollable animation changing property of ensureVisible
Scrollable.ensureVisible(
GlobalObjectKey(category?.id).currentContext,
duration: Duration(seconds: 1),// duration for scrolling time
alignment: .5, // 0 mean, scroll to the top, 0.5 mean, half
curve: Curves.easeInOutCubic);
You can just specify a ScrollController to your listview and call the animateTo method on button click.
A mininmal example to demonstrate animateTo usage :
class Example extends StatefulWidget {
#override
_ExampleState createState() => new _ExampleState();
}
class _ExampleState extends State<Example> {
ScrollController _controller = new ScrollController();
void _goToElement(int index){
_controller.animateTo((100.0 * index), // 100 is the height of container and index of 6th element is 5
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: new Column(
children: <Widget>[
new Expanded(
child: new ListView(
controller: _controller,
children: Colors.primaries.map((Color c) {
return new Container(
alignment: Alignment.center,
height: 100.0,
color: c,
child: new Text((Colors.primaries.indexOf(c)+1).toString()),
);
}).toList(),
),
),
new FlatButton(
// on press animate to 6 th element
onPressed: () => _goToElement(6),
child: new Text("Scroll to 6th element"),
),
],
),
);
}
}
Here is the solution for StatefulWidget if you want to made widget visible right after building the view tree.
By extending Remi's answer, you can achieve it with this code:
class ScrollView extends StatefulWidget {
// widget init
}
class _ScrollViewState extends State<ScrollView> {
final dataKey = new GlobalKey();
// + init state called
#override
Widget build(BuildContext context) {
return Scaffold(
primary: true,
appBar: AppBar(
title: const Text('Home'),
),
body: _renderBody(),
);
}
Widget _renderBody() {
var widget = SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: 1160.0, width: double.infinity, child: new Card()),
SizedBox(height: 420.0, width: double.infinity, child: new Card()),
SizedBox(height: 760.0, width: double.infinity, child: new Card()),
// destination
Card(
key: dataKey,
child: Text("data\n\n\n\n\n\ndata"),
)
],
),
);
setState(() {
WidgetsBinding.instance!.addPostFrameCallback(
(_) => Scrollable.ensureVisible(dataKey.currentContext!));
});
return widget;
}
}
Output:
Use Dependency:
dependencies:
scroll_to_index: ^1.0.6
Code: (Scroll will always perform 6th index widget as its added below as hardcoded, try with scroll index which you required for scrolling to specific widget)
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);
}
}
I found a perfect solution to it using ListView.
I forgot where the solution comes from, so I posted my code. This credit belongs to other one.
21/09/22:edit. I posted a complete example here, hope it is clearer.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class CScrollToPositionPage extends StatefulWidget {
CScrollToPositionPage();
#override
State<StatefulWidget> createState() => CScrollToPositionPageState();
}
class CScrollToPositionPageState extends State<CScrollToPositionPage> {
static double TEXT_ITEM_HEIGHT = 80;
final _formKey = GlobalKey<FormState>();
late List _controls;
List<FocusNode> _lstFocusNodes = [];
final __item_count = 30;
#override
void initState() {
super.initState();
_controls = [];
for (int i = 0; i < __item_count; ++i) {
_controls.add(TextEditingController(text: 'hello $i'));
FocusNode fn = FocusNode();
_lstFocusNodes.add(fn);
fn.addListener(() {
if (fn.hasFocus) {
_ensureVisible(i, fn);
}
});
}
}
#override
void dispose() {
super.dispose();
for (int i = 0; i < __item_count; ++i) {
(_controls[i] as TextEditingController).dispose();
}
}
#override
Widget build(BuildContext context) {
List<Widget> widgets = [];
for (int i = 0; i < __item_count; ++i) {
widgets.add(TextFormField(focusNode: _lstFocusNodes[i],controller: _controls[i],));
}
return Scaffold( body: Container( margin: const EdgeInsets.all(8),
height: TEXT_ITEM_HEIGHT * __item_count,
child: Form(key: _formKey, child: ListView( children: widgets)))
);
}
Future<void> _keyboardToggled() async {
if (mounted){
EdgeInsets edgeInsets = MediaQuery.of(context).viewInsets;
while (mounted && MediaQuery.of(context).viewInsets == edgeInsets) {
await Future.delayed(const Duration(milliseconds: 10));
}
}
return;
}
Future<void> _ensureVisible(int index,FocusNode focusNode) async {
if (!focusNode.hasFocus){
debugPrint("ensureVisible. has not the focus. return");
return;
}
debugPrint("ensureVisible. $index");
// Wait for the keyboard to come into view
await Future.any([Future.delayed(const Duration(milliseconds: 300)), _keyboardToggled()]);
var renderObj = focusNode.context!.findRenderObject();
if( renderObj == null ) {
return;
}
var vp = RenderAbstractViewport.of(renderObj);
if (vp == null) {
debugPrint("ensureVisible. skip. not working in Scrollable");
return;
}
// Get the Scrollable state (in order to retrieve its offset)
ScrollableState scrollableState = Scrollable.of(focusNode.context!)!;
// Get its offset
ScrollPosition position = scrollableState.position;
double alignment;
if (position.pixels > vp.getOffsetToReveal(renderObj, 0.0).offset) {
// Move down to the top of the viewport
alignment = 0.0;
} else if (position.pixels < vp.getOffsetToReveal(renderObj, 1.0).offset){
// Move up to the bottom of the viewport
alignment = 1.0;
} else {
// No scrolling is necessary to reveal the child
debugPrint("ensureVisible. no scrolling is necessary");
return;
}
position.ensureVisible(
renderObj,
alignment: alignment,
duration: const Duration(milliseconds: 300),
);
}
}
To achieve initial scrolling at a particular index in a list of items
on tap of the floating action button you will be scrolled to an index of 10 in a list of items
class HomePage extends StatelessWidget {
final _controller = ScrollController();
final _height = 100.0;
#override
Widget build(BuildContext context) {
// to achieve initial scrolling at particular index
SchedulerBinding.instance.addPostFrameCallback((_) {
_scrollToindex(20);
});
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => _scrollToindex(10),
child: Icon(Icons.arrow_downward),
),
body: ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _height,
child: Card(child: Center(child: Text("Item $i"))),
),
),
);
}
// on tap, scroll to particular index
_scrollToindex(i) => _controller.animateTo(_height * i,
duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn);
}
I am posting a solution here in which List View will scroll 100 pixel right and left . you can change the value according to your requirements. It might be helpful for someone who want to scroll list in both direction
import 'package:flutter/material.dart';
class HorizontalSlider extends StatelessWidget {
HorizontalSlider({Key? key}) : super(key: key);
// Dummy Month name
List<String> monthName = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"July",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
ScrollController slideController = new ScrollController();
#override
Widget build(BuildContext context) {
return Container(
child: Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
// Here monthScroller.position.pixels represent current postion
// of scroller
slideController.animateTo(
slideController.position.pixels - 100, // move slider to left
duration: Duration(
seconds: 1,
),
curve: Curves.ease,
);
},
child: Icon(Icons.arrow_left),
),
Container(
height: 50,
width: MediaQuery.of(context).size.width * 0.7,
child: ListView(
scrollDirection: Axis.horizontal,
controller: slideController,
physics: ScrollPhysics(),
children: monthName
.map((e) => Padding(
padding: const EdgeInsets.all(12.0),
child: Text("$e"),
))
.toList(),
),
),
GestureDetector(
onTap: () {
slideController.animateTo(
slideController.position.pixels +
100, // move slider 100px to right
duration: Duration(
seconds: 1,
),
curve: Curves.ease,
);
},
child: Icon(Icons.arrow_right),
),
],
),
);
}
}
The simplest way is to call this method inside your InitState method. (not the build to evict unwanted errors)
WidgetsBinding.instance.addPostFrameCallback((_) => Scrollable.ensureVisible(targetKey.currentContext!))
WidgetsBinding.instance.addPostFrameCallback will guarantee that the list is builded and the this automatic search for your target and move the scroll to it. You can then customize the animation of the scroll effect on the Scrollable.ensureVisible method
Note: Remember to add the targetKey (a GlobalKey) to the widget you want to scroll to.
Adding with Rémi Rousselet's answer,
If there is a case you need to scroll past to end scroll position with addition of keyboard pop up, this might be hided by the keyboard. Also you might notice the scroll animation is a bit inconsistent when keyboard pops up(there is addition animation when keyboard pops up), and sometimes acts weird. In that case wait till the keyboard finishes animation(500ms for ios).
BuildContext context = key.currentContext;
Future.delayed(const Duration(milliseconds: 650), () {
Scrollable.of(context).position.ensureVisible(
context.findRenderObject(),
duration: const Duration(milliseconds: 600));
});
You can also simply use the FixedExtentScrollController for same size items with the index of your initialItem :
controller: FixedExtentScrollController(initialItem: itemIndex);
The documentation : Creates a scroll controller for scrollables whose items have the same size.
Simply use page view controller.
Example:
var controller = PageController();
ListView.builder(
controller: controller,
itemCount: 15,
itemBuilder: (BuildContext context, int index) {
return children[index);
},
),
ElevatedButton(
onPressed: () {
controller.animateToPage(5, //any index that you want to go
duration: Duration(milliseconds: 700), curve: Curves.linear);
},
child: Text(
"Contact me",),
You can use the controller.jumpTo(100) after the loading finish