Flutter: ScrollSnapList-Item not shrinking - need dynamic sizing of elements - flutter

I am building a horizontal ScrollSnapList. My problem is that I cannot shrink it on the y-axis to the size of the elements height. I tried to give a maxHeight with LimitedBox but the ScrollSnapList takes alle the vertical space available. I have also tried for test purposes to build it with a ListView.builder() - same result:
Hers is my Code:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarProfile(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: 20,),
LimitedBox(
maxHeight: 140,
child:
ScrollSnapList(
shrinkWrap: true,
key: sslKey,
initialIndex: 0,
//shrinkWrap: true,,
//duration: 1,
scrollDirection: Axis.horizontal,
onItemFocus: (index){
_currentIndex = index;
},
itemSize: MediaQuery. of(context). size. width - 32,
itemBuilder: _buildItem,
itemCount: 10,
scrollPhysics: ClampingScrollPhysics(),
dynamicItemSize: true,
dynamicItemOpacity: 0.7,
dynamicSizeEquation: (distance) {
if (distance > 0){
return 1 - 0.1*distance /MediaQuery. of(context). size. width / 2;
}else{
return 1 + 0.1*distance /MediaQuery. of(context). size. width / 2 ;
}
},
),
),
Expanded(child: ListView(
children: [
Container(height: 200, color: Colors.black,),
],
))
],
),
);
}
Widget _buildItem(BuildContext context, int index) {
return
SizedBox(
width: MediaQuery. of(context). size. width - 32,
child: Item(),
);
}
}
Here is the code of the element being called:
class Item extends StatelessWidget {
const Item({Key? key,}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
splashColor: Colors.transparent,
onTap: () {
},
child: Column(
children: [
const SizedBox(
height: 8,
),
Container(height:10, width: 20, color: Colors.black)
],
),
);
}
}
Result of code above
As you can see, the space around the items of ScrollSnapList is expanded to the size of the maxHeight of LimitedBox(). I placed another black container beneath to show that it is fully expanding. How do I fix this?

You can do a trick wrapping with Center widget.
Widget _buildEmployeeItem(BuildContext context, int index) {
return SizedBox(
width: MediaQuery.of(context).size.width - 32,
child: Center(
child: Container(
height: 10,
width: 20,
color: Colors.black,
),
),
);
}
You can find more about constraints

Related

The swap widget so that the under widget has been fixed

I have a create so simple slidable view pager with CarouselSlider:
return Scaffold(
body: CarouselSlider(
options: CarouselOptions(
viewportFraction: 1,
// aspectRatio: 1,
height: double.maxFinite,
// enlargeCenterPage: true,
),
items: List.generate(
10,
(i) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: (i % 2 == 0) ? Colors.red : Colors.green,
),
),
Text('text $i', style: TextStyle(fontSize: 16.0)),
],
)),
));
This is its result:
But as you can see next container connects to the first widget, I want when the first widget to be swapped to the left, the next widget appears under the first widget Not next to it. It looks like the following widget is fixed and we remove the top widget.
You can use a package called stacked_page_view, it is very simple, lightweight, and similar to the same original PageView in usage.
Example Snippet:
PageView.builder(
itemCount: 10,
scrollDirection: Axis.vertical,
controller: pageController,
itemBuilder: (context, index) {
return StackPageView(
controller: pageController,
index: index,
child: Container(
color: (colors..shuffle()).first,
child: Center(
child: Text(
'$index',
style: TextStyle(
color: Colors.white,
fontSize: 25,
),
),
),
),
);
},
)
Note: You can control the scroll axis with the property scrollDirection inside PageView.builder() with values of Axis.vertical or Axis.horizontal.
I finally find a way to create stack page view, This is a full codes:
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/material.dart';
import 'dummy_data.dart';
import 'page_view_item.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
/// The current page of the page view
double _page = 0;
/// The index of the leftmost element of the list to be displayed
int get _firstItemIndex => _page.toInt();
/// Controller to get the current position of the page view
final _controller = PageController(
viewportFraction: 0.5,
);
/// The width of a single item
late final _itemWidth =
MediaQuery.of(context).size.width * _controller.viewportFraction;
#override
void initState() {
super.initState();
_controller.addListener(() => setState(() {
_page = _controller.page!;
}));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("LV Scroll"),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Positioned.fill(
child: Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: _itemWidth,
child: FractionallySizedBox(
child: PageViewItem(
index: _firstItemIndex,
width: _itemWidth,
url: model[_firstItemIndex],
),
),
),
),
),
SizedBox(
height: 250,
child: PageView.builder(
padEnds: false,
controller: _controller,
itemBuilder: (context, index) {
return Opacity(
opacity: index <= _firstItemIndex ? 0 : 1,
child: PageViewItem(
index: index,
width: _itemWidth,
url: model[index],
),
);
},
itemCount: model.length,
),
),
],
),
],
),
);
}
}
it's result :
and its reference;
You can use a package called expandable_page_view, it is a PageView widget adjusting its height to currently displayed page. It accepts the same parameters as classic PageView.
ExpandablePageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return Container(color: Colors.blue);
},
),

CupertinoSliverRefreshControl with horizontal ListView

I have horizontal ListView.builder and CupertinoSliverRefreshControl, so when it reaches the end, I want to display Loading indicator, but for some reason I am getting error
Null check operator used on a null value
The relevant error-causing widget was
CustomScrollView
lib/sliver_loading.dart:19
The most unclear part is that CupertinoSliverRefreshControl works fine with Vertical ListView.builder, but when I change Axis on horizontal it rises this above error.
Here is a code :
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(15),
child: CustomScrollView(
scrollDirection: Axis.horizontal, // Here is when Error rise
slivers: [
SliverToBoxAdapter(
child: SizedBox(
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
primary: false,
shrinkWrap: true,
itemCount: 4,
itemBuilder: (context, index) {
return Container(
width: 100,
height: 200,
color: colors[index],
);
},
),
),
),
CupertinoSliverRefreshControl(
onRefresh: () async {
await Future.delayed(Duration(seconds: 3));
print('loaded');
},
),
],
),
),
);
}
Can anyone explain me, why is this happening and what are the solutions?
There is a workaround with current snippet instead of using CupertinoSliverRefreshControl return row with loading widget for last item. Also wrap Container with Center.
itemBuilder: (context, index) {
return index == 13 // items length-1
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.cyanAccent,
),
CircularProgressIndicator(),
],
)
:Center( child: Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.amber,
));
},
If you do use ListView, you can use ScrollController with listener and get position to load data using controller.position.maxScrollExtent* .9 ;load more on 90% scroll.
Also, using the same directional multi-scrollabe widgets is not necessary. We can skip using ListView and use SliverList. While the width is fixed, we can compare the items' length and current scroll position to using the controller.
final ScrollController controller = ScrollController();
#override
void initState() {
super.initState();
controller.addListener(() {
print(controller.offset);
//14 total item , I am using 90%
if (controller.offset > 100 * 14 * .9) {
// you may encounter multiple call use another flag or null to handle this
print("load more");
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(15),
child: CustomScrollView(
scrollDirection: Axis.horizontal,
controller: controller,
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => index == 13 // items length-1
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.cyanAccent,
),
CircularProgressIndicator(),
],
)
: Center(
child: Container(
margin: EdgeInsets.all(20),
width: 100,
height: 200,
color: Colors.amber,
)),
childCount: 14,
),
),
],
),
),
);
}
}
Okay, so here is a way how I solved this problem. Since CupertinoSliverRefreshControl does not work with horizontal ListView.builder, I decided to use CupertinoActivityIndicator and CupertinoActivityIndicator.partiallyRevealed.
When ListView reaches to the end, I am calculating distance between ListView.builder() and int distance and updating double progress for CupertinoActivityIndicator.partiallyRevealed, next when progress reaches 1.0 I just replace CupertinoActivityIndicator.partiallyRevealed with CupertinoActivityIndicator changing bool isActive value to true.
Finally it works like CupertinoSliverRefreshControl, just without slivers :).
Code Example
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HorizontalLoader extends StatefulWidget {
const HorizontalLoader({Key? key}) : super(key: key);
static final colors = [
Colors.red,
Colors.indigoAccent,
Colors.purple,
Colors.amberAccent,
Colors.orange,
Colors.purple,
Colors.cyanAccent,
Colors.red,
Colors.indigoAccent,
Colors.purple,
];
#override
State<HorizontalLoader> createState() => _HorizontalLoaderState();
}
class _HorizontalLoaderState extends State<HorizontalLoader> {
int distance = 70; // offset
bool isActive = false;
double progress = 0.0;
// Base logic. you can also use this logic with ScrollController()
bool _handleNotification(ScrollNotification notify) {
double outRangeLoading = distance + notify.metrics.maxScrollExtent;
double currentPixel = notify.metrics.pixels;
if (notify.metrics.extentAfter <= 0.0) {
if (currentPixel >= outRangeLoading) {
networkLoader();
}
calculateProgress(outRangeLoading, currentPixel);
}
return true;
}
// Some math
void calculateProgress(outRangeLoading, currentPixel) {
double current, currentAsPrecent;
current = outRangeLoading - currentPixel;
currentAsPrecent = (100 * current) / distance;
setState(() {
progress = (100 - currentAsPrecent) * 0.01;
});
}
// To simulate loading data from Network
void networkLoader() async {
isActive = true;
await Future.delayed(Duration(seconds: 3));
isActive = false;
setState(() {
progress = 0.0;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 200, bottom: 200),
child: Stack(
children: [
Positioned(
right: 15,
top: 210,
child: isActive
? CupertinoActivityIndicator()
: CupertinoActivityIndicator.partiallyRevealed(
progress: progress,
),
),
NotificationListener<ScrollNotification>(
onNotification: _handleNotification,
child: ListView.builder(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
itemCount: HorizontalLoader.colors.length + 1,
itemBuilder: (context, index) {
if (index == HorizontalLoader.colors.length) {
return isActive ? SizedBox(width: 50) : SizedBox();
}
return Container(
width: 100,
height: 100,
color: HorizontalLoader.colors[index],
);
},
),
),
],
),
),
);
}
}

Is there a listener that can call a function to move on to next set of data in Carousel (Page View)?

I've set up this Carousel using a PageView.builder. It displays 5 tiles at a time.
Once the user has swiped all the way over to the right & pulls on the last tile (see image)...I'd like to move onto the next set of 5 tiles in an array.
Is there an event handler for this? I've managed to set up a listener that can determine when the user has swiped to the last tile, but cannot figure out how to tell when they're pulling on this so it can be refreshed.
Appreciate any help I can get on this. Code below :)
import 'package:flutter/material.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class RecommendationPanel extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _buildRecommendationPanel();
}
}
class _buildRecommendationPanel extends State<RecommendationPanel> {
PageController _pageController = PageController();
#override
void initState() {
_pageController = PageController(viewportFraction: 1.0);
_pageController.addListener(_scrollListener);
super.initState();
}
void dispose() {
_pageController.dispose();
super.dispose();
}
_scrollListener() {
if (_pageController.offset >= _pageController.position.maxScrollExtent &&
!_pageController.position.outOfRange) {
setState(() {
//This is working in the sense that it tells when they're on the final tile
//I want it so knows when you drag to the right
print('Final tile');
//I could refresh the list and then just move everything back to #1 in the view...i.e. the last card [index 4] can now shift to 5
//_pageController.jumpToPage(0);
});
}
if (_pageController.offset <= _pageController.position.minScrollExtent &&
!_pageController.position.outOfRange) {
setState(() {
//Need to figure out how to work this - there's going to have to be another variable checking what place in the top N recommended array it is, and then adjust accordingly
print('Back to first tile');
//_pageController.jumpToPage(3);
});
}
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
//You may want to use aspect ratio here for tablet support
height: 270.0,
child: PageView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 5,
scrollDirection: Axis.horizontal,
controller: _pageController,
itemBuilder: (BuildContext context, int itemIndex) {
//I could potentially call something here to update the slider index
return _buildCarouselItem(context, itemIndex);
},
),
),
Container(
height: 30,
child: Center(
child: SmoothPageIndicator(
controller: _pageController,
count: 5,
effect: WormEffect(
spacing: 8.0,
dotHeight: 10,
dotWidth: 10,
activeDotColor: Colors.orange,
),
),
),
),
],
);
}
Widget _buildCarouselItem(BuildContext context, int itemIndex) {
List<String> labels = [
'Pub',
'Bar',
'Football Match',
'Nightclub',
'Book Festival',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0),
child: Container(
height: double.infinity,
//color: Colors.red,
child: Column(
children: [
Container(
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
child: Container(
// In here is where I should build each individual tile
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Container(
width: double.infinity,
height: 260,
child: Text(labels[itemIndex]),
),
),
),
),
],
),
),
);
}
Widget buildIndicator(BuildContext context, int itemIndex, int count) {
return AnimatedSmoothIndicator(
activeIndex: itemIndex,
count: count,
effect: WormEffect(
dotColor: Colors.grey,
activeDotColor: Colors.orange,
),
);
}
}

fl_chart custom Legend gets pushed down when section is empty

I'm using the PieChart of fl_chart to display the distribution of locally saved documents. The percentages displayed in the chart are the result of the length of the two document type lists (See image below).
But when one List is empty I have a weird bug were my custom Legend gets pushed downwards. The PieChart and the Legend are positioned inside of a Row with flex factors on each children (2 for the PieChart and 4 for the Legend).
I really don't understand what pushes the Legend downwards because my Expanded widgets are always positioned inside of Rows so that the PieChart and Legend only take up the available, horizontal space and not the vertical space which happens in the bug (image 2).
PieChart widget:
class PersonalFilesCircularGraph extends StatefulWidget {
const PersonalFilesCircularGraph();
#override
_PersonalFilesCircularGraphState createState() =>
_PersonalFilesCircularGraphState();
}
class _PersonalFilesCircularGraphState
extends State<PersonalFilesCircularGraph> {
late List<FileTypeData> data;
List<PieChartSectionData> getSections() => data
.asMap()
.map<int, PieChartSectionData>((index, data) {
final value = PieChartSectionData(
color: data.color,
value: data.percent,
showTitle: false,
radius: 3,
);
return MapEntry(index, value);
})
.values
.toList();
#override
void initState() {
/* Example getFileTypeData result
[
FileTypeData(
"Patient Questionnaire",
patientQuestionnaires.length /
(patientQuestionnaires.length +
receivedPatientQuestionnaires.length) *
100,
const Color(0xFF3861FB),
),
FileTypeData(
"Received Patient Questionnaire",
receivedPatientQuestionnaires.length /
(receivedPatientQuestionnaires.length +
patientQuestionnaires.length) *
100,
Colors.teal.shade400,
),
];
*/
data = context.read<SessionBloc>().state.getFileTypeData;
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocConsumer<SessionBloc, SessionState>(
listenWhen: (previous, current) {
final bool listenWhen = previous.patientQuestionnaires.length !=
current.patientQuestionnaires.length ||
previous.receivedPatientQuestionnaires.length !=
current.receivedPatientQuestionnaires.length;
return !listenWhen;
},
listener: (context, state) {
data = context.read<SessionBloc>().state.getFileTypeData;
},
builder: (context, state) {
return Row(
children: [
Expanded(
flex: 2,
child: Container(
constraints: const BoxConstraints(
maxWidth: 60,
maxHeight: 60,
),
child: PieChart(
PieChartData(
sections: getSections(),
),
),
),
),
const SizedBox(
width: kMediumPadding,
),
Expanded(
flex: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: data
.map(
(data) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: buildLegend(
percent: data.percent,
text: data.fileName == "Patient Questionnaire"
? L.of(context).patientQuestionnaires
: L.of(context).receivedPatientQuestionnaire,
color: data.color,
),
),
)
.toList(),
),
),
],
);
},
);
}
Widget buildLegend({
required double percent,
required String text,
required Color color,
}) =>
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Container(
width: 10,
height: 10,
color: color,
),
const SizedBox(
width: kSmallPadding,
),
Expanded(
child: Text(
text,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
Text(
"${percent.toStringAsFixed(0)}%",
overflow: TextOverflow.ellipsis,
)
],
);
}
I display the chart widget inside a CustomScrollView, wrapped with a SliverToBoxAdapter inside of my home screen:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
elevation: 0.0,
floating: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(
"Home",
style: Theme.of(context).textTheme.headline5,
),
centerTitle: true,
),
const SliverPadding(
padding: EdgeInsets.symmetric(
vertical: kSmallPadding,
horizontal: kMediumPadding,
),
sliver: SliverToBoxAdapter(
child: PersonalFilesCircularGraph(),
),
)
],
);
}
}
EDIT:
I just did some more investigation on this bug and placed a colored Container in my CustomScrollView, below the SliverPadding of the CircularGraph to check if the Column of labels expands downwards. But as you can see the colored Container is not effected. It just looks like the Legend is inside a Stack and positioned without effecting other widgets above and below.
const SliverPadding(
padding: EdgeInsets.symmetric(
vertical: kSmallPadding,
horizontal: kMediumPadding,
),
sliver: SliverToBoxAdapter(
child: PersonalFilesCircularGraph(),
),
),
SliverToBoxAdapter(
child: Container(
width: double.infinity,
height: 60,
color: Colors.green,
),
)

Flutter: How to create a simple custom bar chart / slider

I have a JSON like this:
[
{
"column": "Column 1",
"slider1": 86,
"slider2": 43,
"slider3": 25
},
{
"column": "Column 2",
"slider1": 64,
"slider2": 72,
"slider3": 39
}
]
I want to make a Bar chart or Slider like this:
Max length of bar chart = 100
The length of the bar chart = its value (Ex slider1 = 86)
I thought of using Stack but I don't know how to do. So pls help me, this is the main file:
import 'dart:convert';
import 'package:ask/model/data_model.dart';
import 'package:flutter/material.dart';
class Page4 extends StatefulWidget {
#override
_Page4State createState() => _Page4State();
}
class _Page4State extends State<Page4> {
List<Data> _data = [];
Future<List<Data>> getDataFromJson(BuildContext context) async {
String jsonString = await DefaultAssetBundle.of(context).loadString('assets/data.json');
List<dynamic> raw = jsonDecode(jsonString);
return raw.map((e) => Data.fromJson(e)).toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Simple Slider')),
body: FutureBuilder(
future: getDataFromJson(context),
builder: (context, snapshot) {
List<Data> _data = snapshot.data;
return ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: _data.length,
itemBuilder: (context, index) {
Data data = _data[index];
return Column(children: [
Row(children: [
Expanded(child: Text(data.column)),
Expanded(
child: Column(children: [
SizedBox(height: 5),
Container(
width: double.infinity,
color: Colors.grey[300],
child: Text(data.slider1.toString(), style: TextStyle(color: Colors.blue[(data.slider1 / 10).round() * 100])), //How to put a bar chart here
),
SizedBox(height: 5),
Container(
width: double.infinity,
color: Colors.grey[300],
child: Text(data.slider2.toString(), style: TextStyle(color: Colors.blue[(data.slider2 / 10).round() * 100])), //How to put a bar chart here
),
SizedBox(height: 5),
Container(
width: double.infinity,
color: Colors.grey[300],
child: Text(data.slider3.toString(), style: TextStyle(color: Colors.blue[(data.slider3 / 10).round() * 100])), //How to put a bar chart here
),
]),
)
])
]);
});
}));
}
}
.......................................................................................
here is a solution which can help you but these are static bars, in case you want to have an interaction with your ui , you should use slider widgetmore info.
code sample
#override
Widget build(BuildContext context) {
final double sliderWidth = MediaQuery.of(context).size.width;
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomSlider(
percentage: 12 / 100,
width: sliderWidth,
),
CustomSlider(
percentage: 90 / 100,
width: sliderWidth,
),
CustomSlider(
percentage: 50 / 100,
width: sliderWidth,
),
],
),
),
);
}
}
class CustomSlider extends StatelessWidget {
final double percentage;
final double width;
CustomSlider({
this.percentage,
this.width,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: [
Container(
width: double.infinity,
height: 20,
color: Colors.grey,
),
Container(
color: Colors.blue,
width: percentage == null ? 0 : width * percentage,
height: 20,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
percentage == null ? "0" : (percentage * 100).toString(),
),
),
),
],
),
);
}
}
good luck.