Scroll to an off-screen widget? ListView Flutter - flutter

How do I scroll to an off-screen element? ListView.builder uses pagination, and hidden items are not embedded in the tree
Example:
import 'package:flutter/material.dart';
class ScrollView1 extends StatefulWidget {
#override
_ScrollView1State createState() => _ScrollView1State();
}
class _ScrollView1State extends State<ScrollView1> {
final dataKey = new GlobalKey();
List<String> list1 = [
'button1',
'button2',
'button3',
'button4',
'button5',
'button6',
'button7'
];
List<GlobalKey> listKey = [];
List<GlobalKey> listKeyBody = [];
void addKey() {
for (var i = 0; i < list1.length; i++) {
final key = GlobalKey();
listKey.add(key);
final key2 = GlobalKey();
listKeyBody.add(key2);
}
}
#override
void initState() {
addKey();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
height: 100,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (context, i) {
return FlatButton(
key: listKey[i],
child: Text('${list1[i]} - $i ${listKeyBody[i]}'),
onPressed: () {
Scrollable.ensureVisible(listKeyBody[i].currentContext);
},
);
},
itemCount: list1.length,
),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (context, i) {
return Container(
key: listKeyBody[i],
height: 200,
child: Center(
child:
Text('Hello ${list1[i]} $i - ${listKeyBody[i]}')));
},
itemCount: list1.length,
),
),
],
),
);
}
}

Related

How to scroll two SingleChildScrollView same time in Flutter?

I want to scroll two SingleChildScrollView same time vertically.
This code works for both SingleChildScrollViews.
import 'package:flutter/material.dart';
class ScrollTestPage extends StatefulWidget {
const ScrollTestPage({Key? key}) : super(key: key);
#override
State<ScrollTestPage> createState() => _ScrollTestPageState();
}
class _ScrollTestPageState extends State<ScrollTestPage> {
final _controller1 = ScrollController();
final _controller2 = ScrollController();
#override
void initState() {
super.initState();
_controller1.addListener(listener1);
_controller2.addListener(listener2);
}
var _flag1 = false;
var _flag2 = false;
void listener1() {
if (_flag2) return;
_flag1 = true;
_controller2.jumpTo(_controller1.offset);
_flag1 = false;
}
void listener2() {
if (_flag1) return;
_flag2 = true;
_controller1.jumpTo(_controller2.offset);
_flag2 = false;
}
#override
void dispose() {
super.dispose();
_controller1.removeListener(listener1);
_controller2.removeListener(listener2);
_controller1.dispose();
_controller2.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller1,
itemBuilder: (context, i) {
return Container(
margin: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Text('First List View $i'),
);
},
itemCount: 100,
),
),
Expanded(
child: ListView.builder(
controller: _controller2,
itemBuilder: (context, i) {
return Container(
margin: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Text('Second List View $i'),
);
},
itemCount: 100,
),
),
],
),
);
}
}
You can set the controller: parameter of both SingleChildScrollView widgets to the same controller. The controller has to be initialized like this:
final ScrollController _scrollController = ScrollController();

How to find out where the click was in a dynamic list?

I have a list and I need to set the container's background when clicking on it. However, what I have now does not work. When clicked, the color of the entire list changes, not the selected one. It seems to me that I need to add an index somewhere. I can't put it in a separate widget, because I'm attached to the list. Tell me how to do it?
setState -
Color? _textColor;
Color? _bgColor;
void initState() {
_bgColor = configColors.orange;
_textColor = Colors.white;
super.initState();
}
List
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: HomeStore.storage.length,
itemBuilder: (BuildContext ctx, index) {
return Row (
// mainAxisAlignment: MainAxisAlignment.start,
children: <Widget> [
InkWell(
onTap: () {
setState(() {
if (_bgColor ==
configColors
.orange) {
_bgColor =
Colors.white;
_textColor =
configColors
.textStorage;
} else {
_bgColor =
configColors.orange;
_textColor =
Colors.white;
}
}
);
},
child: Container(
width: 71.4,
height: 30.3,
decoration: BoxDecoration(
color: _bgColor,
borderRadius: BorderRadius.circular(10)
),
child: Align(
alignment: Alignment.center,
child: Text(HomeStore.storage[index], style: TextStyle(color: _textColor,),),
)
),
),
SizedBox(
width: 18,
),
],
);
}),
For single item selection, you can use a int variable, this snippet will help you to understand the concept.
int? selectedIndex;
onTap: () {
setState(() {
selectedIndex = index;
});
},
And to select color
color:selectedIndex == index ? Colors.red : Colors.blue
Test snippet
class Sg extends StatefulWidget {
Sg({Key? key}) : super(key: key);
#override
State<Sg> createState() => _SgState();
}
class _SgState extends State<Sg> {
int? selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: 4,
itemBuilder: (BuildContext ctx, index) {
return Row(
// mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
setState(() {
selectedIndex = index;
});
},
child: Container(
width: 71.4,
height: 30.3,
decoration: BoxDecoration(
color:
selectedIndex == index ? Colors.red : Colors.blue,
borderRadius: BorderRadius.circular(10)),
child: Align(
alignment: Alignment.center,
child: Text(
"HomeStore.storage[index]",
),
)),
),
],
);
}),
);
}
}
sharing one of my code demo
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
late int tappedIndex;
#override
void initState() {
super.initState();
tappedIndex = 0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 4,
itemBuilder: (context, index) {
return Container(
color: tappedIndex == index ? Colors.blue : Colors.grey,
child: ListTile(
title: Center(
child: Text('${index + 1}'),
),onTap:(){
setState((){
tappedIndex=index;
});
}));
})
]));
}
}
taped index will solve problem

I want to scroll up after the all childs of ListView have been scrolled up, parent widget of listview is SingleChildScrollView in flutter

I have SingleChildScrollView as a parent and in that, I have two listviews each list view is wrapped with SizedBox with a specific height (like 700), what I want is, when I scroll up all the views that are in the first list, the first Listview should scroll up and then I'll be able to scroll next Listview, Please have a look into the code below.
Your help means a lot to me.
Thank you in advance.
Note: I'm getting this required behavior in chrome but not on a mobile device
SingleChildScrollView( child: Column(children: [
SizedBox(
height: 700,
child:ListView.builder(
itemCount:
20, itemBuilder: (context, index) {
return const ListTile(leading: Icon(Icons.icecream,
color: Colors.amber,), title: Text("Ice Cream"),);
},),
),
SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20, itemBuilder: (context, index) {
return const ListTile(
leading: Icon(Icons.cake, color: Colors.red,),
title: Text("Cake"),);
},),
),
],),)
You Can Do something like this on the Controllers:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ScrollingBehaviourInDart extends StatefulWidget {
const ScrollingBehaviourInDart({Key? key}) : super(key: key);
#override
State<ScrollingBehaviourInDart> createState() =>
_ScrollingBehaviourInDartState();
}
class _ScrollingBehaviourInDartState extends State<ScrollingBehaviourInDart> {
late ScrollController _sc1;
late ScrollController _sc2;
late ScrollController _sc3;
#override
void initState() {
_sc1 = ScrollController();
_sc2 = ScrollController();
_sc3 = ScrollController();
var _pr = Provider.of<MyScrollProvider>(context, listen: false);
_sc1.addListener(() {
log("SC1::::::::::: " + _sc1.position.pixels.toString());
if (_sc1.position.pixels == _sc1.position.minScrollExtent) {
print("OK");
_pr.changePhysics(enableScrolling: true);
}
});
_sc2.addListener(() {
if (_sc2.offset == _sc2.position.maxScrollExtent) {
_pr.changePhysics(enableScrolling: false);
log("YAAA");
}
});
_sc3.addListener(() {
log("SC3::::::::::: " + _sc3.position.pixels.toString());
});
super.initState();
}
#override
void dispose() {
_sc1.dispose();
_sc2.dispose();
_sc3.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var _size = MediaQuery.of(context).size;
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white.withOpacity(0.8),
body: SizedBox(
height: _size.height,
child: Consumer<MyScrollProvider>(
builder: (context, myScrollProvider, _) => SingleChildScrollView(
controller: _sc1,
child: Column(
children: [
SizedBox(
height: _size.height * 0.5,
child: ListView.builder(
controller: _sc2,
physics: myScrollProvider.enablePrimaryScroll
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics(),
itemCount: 20,
shrinkWrap: true,
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.icecream,
color: Colors.amber,
),
title: Text("Ice Cream"),
);
},
),
),
ListView.builder(
itemCount: 20,
controller: _sc3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.cake,
color: Colors.red,
),
title: Text("Cake"),
);
},
),
],
),
),
),
),
),
);
}
}
class MyScrollProvider extends ChangeNotifier {
var enablePrimaryScroll = true;
changePhysics({required bool enableScrolling}) {
enablePrimaryScroll = enableScrolling;
notifyListeners();
}
}
maybe you can use Stickyheader.
import 'package:sticky_headers/sticky_headers.dart';
ListView(
shrinkwarp:true,
children:[
StickyHeader(
head: Text('List 1 '),
content : ListView.builder(
physics: const ClampingScrollPhysics(), // use this for clamping scroll
itemBuilder: (context, idx) => Container(),
itemCount:5,
)
StickyHeader(
head: Text('List 2 '),
content : ListView.builder(
physics: const ClampingScrollPhysics(), // use this for clamping scroll
itemBuilder: (context, idx) => Container(),
itemCount:5,
)
]
}
There are many easy ways to handle this situation as stated by many other developers. I have created an Example class with ScrollController and AbsordPointer classes to achieve the required behavior.
Sample
class Example extends StatefulWidget {
const Example({super.key});
#override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
late ScrollController scrollController;
var reachedAtEnd = false;
#override
void initState() {
super.initState();
scrollController =ScrollController()..addListener(() {
if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
reachedAtEnd = true;
setState(() {
});
}
},);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
children: [
SizedBox(
height: 700,
child: ListView.builder(
controller: scrollController,
itemCount: 20,
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.icecream,
color: Colors.amber,
),
title: Text("Ice Cream"),
);
},
),
),
SizedBox(
height: 300,
child: AbsorbPointer(
absorbing: !reachedAtEnd,
child: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.cake,
color: Colors.red,
),
title: Text("Cake"),
);
},
),
),
),
],
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
// this variable determnines whether the back-to-top button is shown or not
bool _showBackToTopButton = false;
// scroll controller
late ScrollController _scrollController;
#override
void initState() {
_scrollController = ScrollController()
..addListener(() {
setState(() {
print(_scrollController.offset);
if (_scrollController.offset >= 400) {
_showBackToTopButton = true;
// _scrollToTop();
// show the back-to-top button
} else {
_showBackToTopButton = false; // hide the back-to-top button
}
});
});
super.initState();
}
#override
void dispose() {
_scrollController.dispose(); // dispose the controller
super.dispose();
}
// This function is triggered when the user presses the back-to-top button
void _scrollToTop() {
_scrollController.animateTo(0,
duration: const Duration(seconds: 3), curve: Curves.linear);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('com'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 40,
itemBuilder: (context, index) {
print(index);
if (index == 39) {
_scrollToTop();
}
return const ListTile(
leading: Icon(
Icons.icecream,
color: Colors.amber,
),
title: Text(' Ice Cream'),
);
},
),
);
}
}

Unable to scroll on widget even after wrapping column() with SingleChildScrollView()

Here is the root widget . The widget has a child BuyerPostList() which is a ListView type of widget. I removing the SingleChildScrollView() gives a render exception .After adding it the error no longer appears but the page is still not scrollable.
class PostsPage extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return StreamProvider<List<BuyerPost>>.value(
value: BuyerDatabaseService().buyerPosts,
child:Scaffold(
backgroundColor: Colors.white,
appBar: header(context,isAppTitle: true),
body:Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
BuyerPostList(),
SizedBox(height: 100,),
],
),
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewPost()),
);
},
label: Text(
'New',
style: TextStyle(fontWeight:FontWeight.w900,color:Color.fromRGBO(0, 0, 0, 0.4),),
),
icon: Icon(Icons.add,color:Color.fromRGBO(0, 0, 0, 0.4),),
backgroundColor:Colors.white,
),
),
);
}
}
Here is the List View BuyerPostList widget()
class BuyerPostList extends StatefulWidget {
#override
_BuyerPostListState createState() => _BuyerPostListState();
}
class _BuyerPostListState extends State<BuyerPostList> {
#override
Widget build(BuildContext context) {
final posts = Provider.of<List<BuyerPost>>(context) ?? [];
return ListView.builder(
shrinkWrap: true,
itemCount: posts.length,
itemBuilder: (context, index) {
return BuyerPostTile(post: posts[index]);
},
);
}
}
I hope i've been clear enough by my explanation. How will i make it scrollable?.
Thanks in advance.
Add physics: NeverScrollableScrollPhysics(), inside ListView.builder(
Code:
#override
Widget build(BuildContext context) {
final posts = Provider.of<List<BuyerPost>>(context) ?? [];
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: posts.length,
itemBuilder: (context, index) {
return BuyerPostTile(post: posts[index]);
},
);
}

Dynamic content PageView is not working in flutter

I tried to add dynamic content in pageview but the view of pageview is not showing.
class HomeFragment extends StatefulWidget {
#override
_HomeFragmentState createState() => _HomeFragmentState();
}
class _HomeFragmentState extends State<HomeFragment> {
List<String> pagerItems = new List();
#override
void initState() {
populatePager();
}
void populatePager() {
pagerItems.add(
"https://ecouponshop.com/wp-content/uploads/2016/04/20-848x470.jpg");
pagerItems.add(
"https://rukminim1.flixcart.com/flap/960/960/image/eb7785.jpg?q=50");
pagerItems.add(
"https://zamroo.s3.ap-south-1.amazonaws.com/images/product-images/home-garden/washing-machines/medium/20170803092151-23218.jpg");
setState(() {
pagerItems;
});
}
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
height: 200.0,
child: pager(),
)
],
),
),
);
}
Widget pager() {
return PageView.builder(
itemCount: pagerItems.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
pagerBody(context, index);
});
}
}
There are couple of mistakes in your code, like you forgot super.initState(), you were not returning any widget from itemBuilder. And you were unnecessarily using pager.items inside setState. Here is the correct code.
class HomeFragment extends StatefulWidget {
#override
_HomeFragmentState createState() => _HomeFragmentState();
}
class _HomeFragmentState extends State<HomeFragment> {
List<String> pagerItems = new List();
#override
void initState() {
super.initState(); // you forgot this
populatePager();
}
void populatePager() {
setState(() {
pagerItems.add("https://ecouponshop.com/wp-content/uploads/2016/04/20-848x470.jpg");
pagerItems.add("https://rukminim1.flixcart.com/flap/960/960/image/eb7785.jpg?q=50");
pagerItems.add("https://zamroo.s3.ap-south-1.amazonaws.com/images/product-images/home-garden/washing-machines/medium/20170803092151-23218.jpg");
});
}
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
height: 200.0,
child: pager(),
)
],
),
),
);
}
Widget pager() {
return PageView.builder(
itemCount: pagerItems.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return pagerBody(context, index); // you forgot this
},
);
}
}