'how to create horizontal and vertical scrollable widgets in flutter
sample Layout image
I am trying to create attached layout. It has two containers.
- First is a fixed size box that scrolls horizontally.
- Second is a card that takes up remaining space with list view inside it.
How can I achieve this layout ?
As you can see, the scroll direction is different for both containers.
The code is working till the Tags view (first box) but as soon as I am adding a second box i.e card, it is not showing anything and getting errors at console as below..
I/flutter ( 9412): AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#85877 ink renderer] ←
I/flutter ( 9412): NotificationListener<LayoutChangedNotification> ← ⋯
I/flutter ( 9412): parentData: offset=Offset(0.0, 0.0); flex=null; fit=null (can use size)
I/flutter ( 9412): constraints: BoxConstraints(0.0<=w<=340.0, 0.0<=h<=Infinity)
I/flutter ( 9412): size: MISSING
I/flutter ( 9412): additionalConstraints: BoxConstraints(biggest)
I/flutter ( 9412): This RenderObject had the following descendants (showing up to depth 5):
I/flutter ( 9412): RenderFlex#93e12 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 9412): RenderRepaintBoundary#977a7 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 9412): RenderCustomPaint#b6be8 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 9412): RenderRepaintBoundary#e449b NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 9412): _RenderExcludableScrollSemantics#293fd NEEDS-LAYOUT NEEDS-PAINT
class _KanbanState extends State<Kanban> {
@override
Widget build(BuildContext context) {
Widget tagList =
new SizedBox(
height: 100.0,
child:
new Column(
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new ActionChip(
backgroundColor: Colors.yellow,
label: new Text('Tag1'),
onPressed: () {
// update board with selection
}),
new ActionChip(
//backgroundColor: Colors.transparent,
label: new Text('Tag2'),
onPressed: () {
// update board with selection
}),
new ActionChip(
label: new Text('Tag3'),
onPressed: () {
// update board with selection
}),
new ActionChip(
label: new Text('Tag4'),
onPressed: () {
// update board with selection
}),
],
)
],
),);
Widget boardView = new Flexible(
// margin: new EdgeInsets.symmetric(vertical: 15.0),
child: new Column(
children: <Widget>[
new ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
onTap: () {
},
title: new Row(
children: <Widget>[
new Expanded(child: new Text("This is item name")),
new Text("12 Dec 18"),
],
),
);
},
),
],
),
);
// int _value=0;
return new Container(
child: new Scaffold(
appBar: new AppBar(
elevation: 1.0,
title: new Text("Test title"),
),
body: new Container(
margin: new EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
tagList,
boardView,
],
)),
));
}
}
Solution 1:[1]
Use this trick:
- Wrap your
Rowin aSingleChildScrollView. - Use
scrollDirection: Axis.horizontalin theSingleChildScrollView. - Wrap
SingleChildScrollViewin the yourListView.
Solution 2:[2]
tl;dr: the following code does what you want =D
import 'package:flutter/material.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'stack overflow',
theme: ThemeData(
primarySwatch: Colors.pink,
),
routes: {},
home: KanbanState(),
);
}
}
class KanbanState extends StatefulWidget {
@override
KanbanStateState createState() {
return KanbanStateState();
}
}
class KanbanStateState extends State<KanbanState> {
@override
Widget build(BuildContext context) {
Widget tagList = Container(
color: Colors.green,
height: 100.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Row(
children: <Widget>[
ActionChip(
backgroundColor: Colors.yellow,
label: Text('Tag1'),
onPressed: () {
// update board with selection
}),
ActionChip(
//backgroundColor: Colors.transparent,
label: Text('Tag2'),
onPressed: () {
// update board with selection
}),
ActionChip(
label: Text('Tag3'),
onPressed: () {
// update board with selection
}),
ActionChip(
label: Text('Tag4'),
onPressed: () {
// update board with selection
}),
ActionChip(
backgroundColor: Colors.yellow,
label: Text('Tag1'),
onPressed: () {
// update board with selection
}),
ActionChip(
//backgroundColor: Colors.transparent,
label: Text('Tag2'),
onPressed: () {
// update board with selection
}),
ActionChip(
label: Text('Tag3'),
onPressed: () {
// update board with selection
}),
ActionChip(
label: Text('Tag4'),
onPressed: () {
// update board with selection
}),
],
)
],
),
);
Widget boardView = Container(
color: Colors.blue,
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 15,
itemBuilder: (BuildContext context, int index) {
return ListTile(
onTap: () {},
title: Row(
children: <Widget>[
Expanded(child: Text("This is item name")),
Text("12 Dec 18"),
],
),
);
},
),
);
// int _value=0;
return Scaffold(
appBar: AppBar(
elevation: 1.0,
title: Text("Test title"),
),
body: Container(
color: Colors.amber,
child: new Column(
children: <Widget>[
tagList,
Expanded(
child: boardView,
)
],
),
margin: EdgeInsets.all(10.0),
));
}
}
Heres the thought process: I started cleaning up each widget and making sure they would be properly displayed. In the taglist widget, notice you have a row as the only widget in a column. In the boardView, LisView is also the only element in a column;
Then I added more items to make sure both scrolls would work. Adding scrollDirection: Axis.horizontal int the tagList made sure of that.
At last, time to put it all together and display both elements. Remove the top Container as Scaffold is enough. Then is was inly a matter of placing the boardView in the Expanded widget.
This was a fun exercise. =D
Solution 3:[3]
Use CustomScrollView with SliverListview then you can achieve any kind of views with vertical scrolling horizontal scrolling.
CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: new SliverChildBuilderDelegate(
(context, index) {
return Container(
child: Row(
children: <Widget>[
buildTitle(),
Expanded(
child: _buildList(),
),
],
),
);
},
childCount: array.length,
),
);,
],
);
Solution 4:[4]
100% custom
import 'package:flutter/material.dart';
import 'dart:math';
class NestedScrolls extends StatefulWidget {
static const listHeader = ['Pakistan', 'China','Iran','Turkey'];
@override
_NestedScrollsState createState() => _NestedScrollsState();
}
class _NestedScrollsState extends State<NestedScrolls> {
var position=0;
var topHeader;
Widget? applyWidget() {
switch(position){
case 0:
setState(() {
topHeader = NestedScrolls.listHeader[0];
});
// return widget if user click over pakistan in tab bar
return grid();
case 1:
setState(() {
topHeader = NestedScrolls.listHeader[1];
});
return list();
case 2:
setState(() {
topHeader = NestedScrolls.listHeader[2];
});
return Container(color: Colors.blue,
child: Center(child: Text(topHeader),),);
case 3:
setState(() {
topHeader = NestedScrolls.listHeader[3];
});
return Container(color: Colors.orange,
child: Center(child: Text(topHeader),),);
}
}
@override
void initState() {
// TODO: implement initState
super.initState();
//initial header name when activity start first time
topHeader = NestedScrolls.listHeader[0];
}
@override
Widget build(BuildContext context) {
topHeader = topHeader;
return Scaffold(
// Persistent AppBar that never scrolls
appBar: AppBar(
title: Text('AppBar'),
elevation: 0.0,
),
body:
Column(
children: <Widget>[
///header
Container(
alignment: Alignment.center,
color: Colors.blueGrey,
height: 90,
child: Text(NestedScrolls.listHeader[position]),
),
/// tabBar
Container(
height: 60,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: NestedScrolls.listHeader.length,
itemBuilder: (con, index) {
return GestureDetector(
onTap: () => setState(() {
position=index;
topHeader = NestedScrolls.listHeader[index];
}),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 10),
child: Container(alignment: Alignment.center,
width: 100,
color: topHeader==NestedScrolls.listHeader[index]?Colors.black26:Colors.transparent,
child: Text(NestedScrolls.listHeader[index])),
),
);
}),
),
///Widget
Expanded(
child: GestureDetector(
// onHorizontalDragStart: (left){
// print('left : ${left.localPosition.direction}');
// // left.globalPosition.dx
//
// },
onHorizontalDragEnd: (start){
print('start : ${start.velocity.pixelsPerSecond.dx}');
if((start.velocity.pixelsPerSecond.dx)<-700){
if(position<NestedScrolls.listHeader.length-1 && position>=0)
setState(() {
position=position+1;
});
}else{}
if((start.velocity.pixelsPerSecond.dx)>900){
if(position<=NestedScrolls.listHeader.length-1 && position>0)
setState(() {
position=position-1;
});
}
print(position);
},
child: applyWidget()),
),
],
),
);
}
list() {
return SingleChildScrollView(scrollDirection: Axis.vertical,
child: Container(
child: Column(children: [
for(var color in Colors.primaries)
Container(color: color, height: 100.0)
],),
),
);
}
grid() {
return GridView.count(
padding: EdgeInsets.zero,
crossAxisCount: 3,
children: Colors.primaries.map((color) {
return Container(color: color, height: 100.0);
}).toList(),
);
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | vbandrade |
| Solution 3 | Sibin |
| Solution 4 | Mehran Ullah |


