How to specify ListTile height in Flutter - flutter

In this code, I am trying to make a list of buttons or tiles "as buttons do not work well for me " at the very top of the page. Thus, when one is clicked it returns a value in the rest of the page.
The issue is The tile here toke around more than half of the page which makes it looks inconsistent. I want to limit the height of the tile, I have tried putting them in a row and a container and it doesn't work. Any HELP will be appreciated.
the result after running the code is:
this is the error after runing the code :
class HomePage extends StatefulWidget {
// const HomePage({Key key}) : super(key: key);
#override
HomePageState createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
List<String> temp=new List();
List<String> temp1=['Nile University', 'Smart Village', 'Zewail'];
Map<String,String> map1={};
#override
void initState() {
super.initState();
getplaces(temp);
getuser(map1,'1jKpg81YCO5PoFOa2wWR');
}
Future<List> getuser(temp,String place) async{
List<String> userids=[];
QuerySnapshot usersubs= await Firestore.instance.collection('tempSubs').getDocuments();
QuerySnapshot userid= await Firestore.instance.collection('users').where('place',isEqualTo: place).getDocuments();
userid.documents.forEach((DocumentSnapshot doc,){
usersubs.documents.forEach((DocumentSnapshot doc1){
if(doc.documentID==doc1.documentID){
doc1.data['products'].forEach((k,v){
if( DateTime.fromMillisecondsSinceEpoch(v).day==DateTime.now().day){
int x= DateTime.fromMillisecondsSinceEpoch(v).day;
print('keey equal $k and v is $x');
print('dy is $x');
userids.add(
doc.documentID);
}
});
}
} ); }
);
print('doc.documentID');
print (userids);
setState(() {});
return userids;
}
Future<List> getplaces(temp) async{
QuerySnapshot place= await Firestore.instance.collection('places').getDocuments();
place.documents.forEach((DocumentSnapshot doc){
temp.add(
doc.data['name']
);
// print(doc.data['name']);
});
// print(temp);
setState(() {});
return temp;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: !temp.isNotEmpty?
CircularProgressIndicator():
Row(mainAxisSize:MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children:<Widget>[
Container(
height: 100.0,
child:
ListView.builder(
scrollDirection: Axis.horizontal,
itemExtent: 100.0,
itemCount:temp.length,
itemBuilder:(BuildContext context, int index) {
return ListTile(title: Text(temp[index]),onTap:
(){
print(temp[index]);
}
);}
),),
Container(child:Text('data'),)
],),
);
}
}

Applying VisualDensity allows you to expand or contract the height of list tile. VisualDensity is compactness of UI elements. Here is an example:
// negative value to contract
ListTile(
title: Text('Tile title'),
dense: true,
visualDensity: VisualDensity(vertical: -3), // to compact
onTap: () {
// tap actions
},
)
// positive value to expand
ListTile(
title: Text('Tile title'),
dense: true,
visualDensity: VisualDensity(vertical: 3), // to expand
onTap: () {
// tap actions
},
)
The values ranges from -4 to 4 and default is 0 as of writing this answer.
However, you cannot use this method for specific width or height size.

Just remove the Expanded Widget to avoid fill the space available and use a parent Container with a fixed height, the same as the itemExtent value:
Column(
children: <Widget>[
Container(
height: 100.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemExtent: 100.0,
itemCount: temp.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(temp[index]),
onTap: () {
print(temp[index]);
});
}),
),
Container(
child: Text('data'),
)
],
),

You should use a Container or Padding instead of ListTile if you need more customization.
You cannot set the height, but you can make it smaller by setting the dense property to true:
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index].name,style: TextStyle(fontSize: 20.0),),
contentPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0),
dense:true,
);
},
);
ListTile:
A single fixed-height row that typically contains some text as well as
a leading or trailing icon.
To be accessible, tappable leading and trailing widgets have to be at
least 48x48 in size. However, to adhere to the Material spec, trailing
and leading widgets in one-line ListTiles should visually be at most
32 (dense: true) or 40 (dense: false) in height, which may conflict
with the accessibility requirement.
For this reason, a one-line ListTile allows the height of leading and
trailing widgets to be constrained by the height of the ListTile. This
allows for the creation of tappable leading and trailing widgets that
are large enough, but it is up to the developer to ensure that their
widgets follow the Material spec.
https://api.flutter.dev/flutter/material/ListTile-class.html

Since there's no height property in ListTile you can limit the size of a tile by placing it inside a SizedBox:
SizedBox(
height: 32,
child: ListTile(..))

Related

Flutter List View Scrolling Only by Clicking on Edges

I'm Having a List view in my app, and the problem is it does not scroll when touching the middle of the list view but only when touching the edges of the list.
and here is my View Code.
Widget build(BuildContext context) {
const horizontalPadding = EdgeInsets.symmetric(horizontal: 10);
final controller = Get.put(UnitsListController());
return Scaffold(
appBar: AppBar(
),
body: Padding(
padding: horizontalPadding,
child: GetX<UnitsListController>(
builder: (controller) {
return controller.isBusy.value
?const Center(
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
color: Colors.blue,
strokeWidth: 5,
),
): ListView.builder(
shrinkWrap: true,
itemCount: controller.unitsList.length,
itemBuilder: (context, index) {
var item = controller.unitsList[index];
String address ='${item.country},${item.state},${item.area},${item.block},${item.plot},'
'${item.lane},${item.buildingName},${item.buildingNumber}';
return GestureDetector(
onTap: () {
controller.selectedUnit = item;
controller.onUnitTap();
},
child: AppUnitCard(
type: item.type,
address: address,
rooms: item.roomsNum??0,
rent: item.rent,
bathrooms: item.bathsNum,
space: item.unitSpace,
),
);
},
);
}),
),
);
}
Note
that i was wrapping the Scaffold body with a SingleChildScrollView and removed it both ways it didn't work.
Wrap the listview with a SingleChildScrollView and add NeverScrollableScrollPhysics to the listview
SingleChildScrollView (
child : ListView.builder(
shrinkWrap: true,
physics : NeverScrollableScrollPhysics()
)
),
ListView doesn't scroll when wrapped by Column & SingleChildScrollView on all the browsers on Android. For further details and code examples check the link:
https://github.com/flutter/flutter/issues/80794#issuecomment-823961805
If this link didn't help you can find more information on this link:
All solutions for the problem

Flutter How to change container height based on the ListView's item height?

Hello I have a Scaffold wrapped with SingleChildScrollView and child is Column.
Inside Column; Container, TabBar and TabBarView.
First Container is just there for black space.
Container(
color: Colors.black,
height: 300,
),
The second widget of Column which mean TabBar:
(I know we can use it in AppBar but now it is what it is.)
const TabBar(
labelColor: Colors.red,
tabs: [
Tab(text: "Tab1"),
Tab(text: "Tab2"),
Tab(text: "Tab3"),
],
),
Last Column widget is TabBarView. It wrapped by Container that has 300 height.
Container(
height: 300, // here is problem
color: Colors.amber,
child: TabBarView(
children: [
buildContainer(200, Colors.red, 2),
buildContainer(100, Colors.red, 2),
buildContainer(150, Colors.red, 3),
],
),
),
and also this is buildContainer method;
buildContainer(double height, Color color, int count) => ListView.builder(
shrinkWrap: true,
itemCount: count,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: height,
color: color,
child: Center(
child: Text("my height ${height.toString()}"),
),
),
);
});
Here is my question. I have 3 tabs and I have three ListViewBuilder.Each one has own child count. But all of them height limited to 300 because of their parent that is Container. I want to set Tab's height dynamicly with each ListViewBuilder's item count.
How can I do that ? I accept dynamic height without child scrolling. I mean, I can scroll whole page for reach the last child. In example, Instagram profile tab. If I have 30 photo, height is phone height. But I have 300 photo, it scrolling all the way down. But also, I don't want to understand pagenation for this one. I am not going to do Instagram. I just want to that, If I create 5 container, okey show me your max height. If I create 1 container, show me just that without scrolling.
I added a dynamic height calculation depending on the number of objects in the ListView.builder. The formula is not perfect, you can add to it, but the point is to subtract the AppBar height and padding to get a clean screen area that the widget completely occupies.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: mainWidget(),
);
}
Widget mainWidget() {
AppBar appBar = AppBar(
toolbarHeight: 56, //You can manually set the AppBar height
title: const Text("App bar"),
);
print(appBar.preferredSize); // Or you can save this value and use it later, it will not be fixed, but will depend on the screen size
return Scaffold(
appBar: appBar,
body: HelpSO(color: Colors.red, count: 5),
);
}
}
class HelpSO extends StatelessWidget {
late double height;
Color color;
int count;
HelpSO({Key? key, required this.color, required this.count})
: super(key: key);
#override
Widget build(BuildContext context) {
// height = (deviceHeight / itemCount) - padding (top + bottom) - appbar.prefferedSize.height / 2;
height = (MediaQuery.of(context).size.height / count) - 16.0 - 56 / 2;
return ListView.builder(
shrinkWrap: true,
itemCount: count,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0), // Subtract this value
child: Container(
height: height,
color: color,
child: Center(
child: Text("My height ${height.toString()}"),
),
),
);
});
}
}
I'm new at flutter(2 m).I just come accros with this problem.
My solution was juste wrap first or second(base on your logic) container with a SingleChildScrollView
Hope it will be helpful

How to update a single item of a SliverList in Flutter?

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.

Offset origin of tiled Image in Flutter

I'm trying to create a parallax background in a Flutter app, and the most efficient way to build it is to use a Stack with the image filling the screen as a background and then my list on top. The image is tiled with an ImageRepeat set on the Y axis. The plan is to then offset the origin of the tile in sync with the ScrollController I'm using for my list. I can then adjust the origin of the tiled image to create the parallax effect. It should be really simple. Here's some code for context:
Stack(
children: [
SizedBox.expand(
child: Image(
image: AssetImage('assets/images/tiled_background_leaf.jpg'),
repeat: ImageRepeat.repeatY,
),
),
CustomScrollView(
controller: _controller,
slivers: [ ...
My problem is that Image does not have an offset property, or an origin position. I need some advice on the easiest way to do this. I've seen that there are custom painters, canvas methods etc, but they all seem massively over-complicated when there should be a more elegant solution within the Image widget, or possibly within another widget that would give me the same parallax effect.
Thanks to #pskink for the answer to this (see comments above).
Here's some code for a dashboard that has a scrolling list of articles and the parallax scrolling tiled image as a background ...
class DashboardRoot extends StatefulWidget {
DashboardRoot({Key key}) : super(key: key);
#override
_DashboardRootState createState() => _DashboardRootState();
}
class _DashboardRootState extends State<DashboardRoot> {
int _currentIndex = 0;
ScrollController _controller;
double _offsetY = 0.0;
_scrollListener() {
setState(() {
_offsetY = _controller.offset;
});
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
var state = Provider.of<ArticlesState>(context, listen: false);
state.initArticleStream();
});
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: AppBottomNavigationBar(),
body: Stack(
children: [
SizedBox.expand(
child: Image(
image: AssetImage('assets/images/tiled_background_leaf.jpg'),
repeat: ImageRepeat.repeatY,
alignment: FractionalOffset(0, (_offsetY / 1000) * -1),
),
),
CustomScrollView(
controller: _controller,
slivers: [
SliverAppBar(
elevation: 0.0,
floating: true,
expandedHeight: 120,
flexibleSpace: FlexibleSpaceBar(
title: Text(NavigationManager
.instance.menuItems[_currentIndex].title),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () => {
locator<NavigationService>()
.navigateTo(SettingsNavigator.routeName)
},
),
IconButton(
icon: Icon(Icons.menu),
onPressed: () => {RootScaffold.openDrawer(context)},
),
],
),
Consumer<ArticlesState>(
builder: (context, state, child) {
final List<Article> list = state.articles;
if (list == null) {
return SliverToBoxAdapter(
child: Center(
child: CircularProgressIndicator(
backgroundColor: Colors.amber, strokeWidth: 1),
),
);
} else if (list.length > 0) {
return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
Article article = list[index];
return ArticleCell(
article: article,
cellTapHandler: () {
Navigator.pushNamed(
context, ArticleDetail.routeName,
arguments: new ArticleDetailArguments(
article.docId, article.heading));
});
},
childCount: list.length,
),
);
} else {
return Center(
child: Text("No Articles"),
);
}
},
),
],
),
],
));
}
}
Notice the Stack has the background image inside an expanded SizedBox so it fills the screen space. The layer above is the CustomScrollView which has the SliverGrid and other stuff.
The important bit is the Image:
child: Image(
image: AssetImage('assets/images/tiled_background_leaf.jpg'),
repeat: ImageRepeat.repeatY,
alignment: FractionalOffset(0, (_offsetY / 1000) * -1),
),
and also the property _offsetY which is set by the ScrollController listener as the users scroll:
double _offsetY = 0.0;
_scrollListener() {
setState(() {
_offsetY = _controller.offset;
});
}
The Image alignment property is used to set the alignment to top, centre, left etc. but it can also be an arbitrary offset. The FractionalOffset value is a range 0..1 but setting it as a larger number above or below zero is also absolutely fine. Because the image is also tiled using ImageRepeat.repeatY the origin of the tiled image is redrawn using alignment, and by messing around with the number, you can create a nice parallax scrolling effect.
Notice that FractionalOffset(0, (_offsetY / 1000) * -1) has the offset value divided by 1000 (this is your speed, and the higher the value the slower the parallax of the background (think of it as the distance between the two layers). Multiplying a number by -1 switches between a positive and negative number, and changes the direction of the parallax.

Flutter ReorderableListView - how to add divider

I have a list created using ReorderableListView. I want to have a separate each list item with a Divider. I am looking for a clean solution, similar to ListView.separated(), however I can't find anything similar for ReorderableListView. In my code at the moment I am using a column widget to which I add a divider for every item but this is very "hacky". Do you know how this could be implemented in a better way?
I'm looking for divider like here:
My Code:
Main Page:
Widget _buildList(RoutinesState state) {
if (state is RoutinesFetched || state is RoutinesReordered) {
List<CustomCard> cards = [];
state.routines.forEach(
(e) {
cards.add(
CustomCard(
key: PageStorageKey(UniqueKey()),
title: e.name,
onTap: () {
Navigator.pushNamed(
context,
RoutineDetails.PAGE_ROUTE,
arguments: RoutineDetailsArgs(e),
);
},
includeDivider: cards.isNotEmpty,
),
);
},
);
return ReorderableListView(
children: cards,
onReorder: (int from, int to) => {
bloc.add(ReorderRoutine(fromIndex: from, toIndex: to)),
},
);
}
return Container(
child: Text("No routines found yet :( "),
);
}
Custom Card Widget:
#override
Widget build(BuildContext context) {
List<Widget> columnItems = [];
if (this.includeDivider) {
columnItems.add(Divider());
}
columnItems.add(ListTile(
leading: CircleAvatar(
child: Icon(Icons.fitness_center),
),
title: Text(this.title),
subtitle: Text("Weights"),
trailing: ActionChip(
label: Icon(Icons.play_arrow),
backgroundColor: Color(0xffECECEC),
onPressed: () => null,
),
onTap: this.onTap,
));
return Column(
mainAxisSize: MainAxisSize.min,
children: columnItems,
);
}
You're right with using a Divider as it's usually used in ListTiles to serve as its name implies: a divider. Using a Column to define the Widgets in your ListTile isn't "hacky", you're just defining the Widgets in the ListTile the way it can be used. Also, since the Divider is added in the ListTile, when the tile is dragged, the Divider will be moved along with the entire ListTile as expected.
Have you attempted to decorate the container you are using with?
This suggestion by #J. S. worked for me like a charm. No need for a devider when you can just paint the bottom border.
First I tried it with Divider() but that didn't look right because the ListTile is the "tappable" area and when you append a Divider below that, there is space that isn't considered tappable. So there is a white border above the divider that isn't painted with the splash effect. Also when you drag the Tile, it looked as if it would cut some of the lower tile with itself. This is what worked for me:
ReorderableListView.builder(
itemCount: _timeSegments!.length,
itemBuilder: (ctx, index) {
return Container(
key: Key(_timeSegments![index].id),
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Theme.of(context).dividerColor, width: 0.5))),
child: TimeSegmentTile(
timeSegment: _timeSegments![index],
press: () => Navigator.pushNamed(context, '/EditTimeSegmentScreen', arguments: EditTimeSegmentScreenArguments(_timeSegments![index], _timeSegmentRepository)),
),
);
},
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final TimeSegment item = _timeSegments!.removeAt(oldIndex);
_timeSegments!.insert(newIndex, item);
});
},
),