The swap widget so that the under widget has been fixed - flutter

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);
},
),

Related

Flutter ListView set 1 complete container at a time

I have a very simple ListView in which I have a container of full height
class TestApp extends StatelessWidget {
final List<MessageItem> items;
TestApp({Key? key, required this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Mixed List';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemCount: 3,
itemBuilder: (context, index) {
return ColorsBox(context);
},
),
),
],
),
),
);
}
Widget ColorsBox(context) {
return Container(
height: MediaQuery.of(context).size.height * 1,
color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0));
}
}
What I need to do is I need to set 1 container only at a time. Mean no half container will be shown only 1 colour container show on 1 time. If scroll then the second container then 3rd so on. Basically trying some TikTok stuff on ListView
You should Use PageView.Builder
PageController controller = PageController();
var currentPageValue = 0.0;
PageView.builder(
scrollDirection: Axis.vertical,
controller: controller,
itemCount: 10,
itemBuilder: (context, position) {
return Container(
color: position % 2 == 0 ? Colors.blue : Colors.pink,
child: Center(
child: Text(
"Page",
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
);
},
)
It's already working Fine.
return Container(
margin: EdgeInsets.only(bottom: 15),
height: MediaQuery.of(context).size.height,
color: Colors.blue
.withOpacity(1.0));
}

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,
),
);
}
}

Flutter/Dart - Dynamic font size to fit Card

I am trying to create some ocassion cards inside a pageview and was wondering if there was a way to make my fontsize dynamic to avoid pixel overflow. Here is a screenshot of a card that works fine:
But when I add an occasion that has more characters...
Here is my code:
class Category {
String name;
IconData icon;
Widget route;
Category(this.name, this.icon, this.route);
}
class CalendarEvents {
String title;
String date;
CalendarEvents(this.title, this.date);
}
class AccountPage extends StatefulWidget {
#override
_AccountPageState createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
List<Category> _categories = [
Category('My History', Icons.history, MyHistory()),
Category('Dates to Remember', Icons.event_note, DatesToRemember()),
Category('Terms and Conditions', Icons.assignment, TermsandConditions()),
Category('Privacy Notice', Icons.security, PrivacyNotice()),
Category('Rate us' , Icons.stars, RateUs()),
Category('Send us Feedback' , Icons.feedback, GiveUsFeedback())
];
DateFormat formatter = DateFormat('dd/MM/yyyy');
List<CalendarEvents> _events = [
CalendarEvents('Christmas Day', "25/12/2020"),
CalendarEvents('New Years Eve', "31/12/2020"),
CalendarEvents('New Years Day',"01/01/2021"),
CalendarEvents('Valentines Day', "14/02/2021"),
CalendarEvents('A very long occasion that needs to be resized','01/01/2021')
];
int _index = 0;
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
Container(
child: SizedBox(
height: 75, // card height
child: PageView.builder(
itemCount: _events.length,
controller: PageController(viewportFraction: 0.5),
onPageChanged: (int index) => setState(() => _index = index),
itemBuilder: (_, i) {
return Transform.scale(
scale: i == _index ? 1 : 0.5,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(formatter.parse(_events[i].date).day.toString()),
Text(DateFormat.MMMM().format(formatter.parse(_events[i].date)).toString()),
Text(
_events[i].title,
style: AppBarTextStyle,
),
],
),
),
);
},
),
),
),
// SizedBox(height: 100.0,),
Container(
height: MediaQuery.of(context).size.height * 0.7,
child: ListView.builder(
itemCount: _categories.length,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
ListTile(
leading: Icon(_categories[index].icon, color: Colors.black,),
title: Text(_categories[index].name),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => _categories[index].route)),
),
Divider(), //
],
);
}),
),
],
),
);
}
}
It would be useful to resize the font depending on the length of occasion. If this is possible, what would be the best way of achieving these results? Thanks
If you want control over the fontsize even after resized, you should use https://pub.dev/packages/auto_size_text package like #Abhishek mentioned
If you just want the text to resize freely, you can use FittedBox and wrap it around Text widget, like this:
FittedBox(
fit: BoxFit.contain,
child: Text()
)
you can use https://pub.dev/packages/auto_size_text package like
title: Text(_categories[index].name),
instead of this
title: AutoSizeText(
_categories[index].name,
minFontSize: 10,
stepGranularity: 10,
maxLines: 4,
overflow: TextOverflow.ellipsis,
)

How to give some space (margin/padding) between pages in PageView?

I am using PageView.builder to create pages.
PageView.builder(
itemCount: _pagesList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
color: _pagesList[index],
);
}
)
What I currently have:
What I want:
i.e. I want to provide some Padding between pages (when they are being scrolled)
Reason: I will display Images in these pages, and since the Images will cover the full width of each page, it doesn't look nice when we scroll pages, since they are knitted together, like this:
How can I solve this?
PageController imagesController =
PageController(initialPage: 0, viewportFraction: 1.1);
PageView(
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(left: 10, right: 10),
child: Container(
color: _pagesList[index],
),
);
}
),
If you want to add padding and still have your pages as wide as the screen:
I needed this exact same thing, also for displaying images. I wanted to add padding but at the same time have each image take up the entire screen width. I figured I could use Fahad Javed's technique and tweaking it a little bit by calculating the viewPortFraction based on the screen width and padding.
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width; // screen width
double screenPad = 16.0; // screen padding for swiping between pages
int _currentPosition = 0;
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: data.length,
controller: PageController(
initialPage: _currentPosition,
viewportFraction:
1 + (screenPad * 2 / screenWidth)), // calculate viewPortFraction
onPageChanged: (int value) {
_currentPosition = value;
},
itemBuilder: (BuildContext context, int position) {
return Padding(
padding: EdgeInsets.only(left: screenPad, right: screenPad),
child: Text('YOUR PAGE CONTENT'),
);
},
);
}
This answer from on the question asked by Amon Kataria Github
final pageController = PageController(viewportFraction: 1.1);
PageView.builder(
controller: pageController,
itemCount: _pagesList.length,
itemBuilder: (BuildContext context, int index) {
return FractionallySizedBox(
widthFactor: 1 / pageController.viewportFraction,
child: Container(
color: _pagesList[index],
),
);
},
);
Thanks #mono0926
Best effort:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: MyPageView()
)
);
}
}
class MyPageView extends StatefulWidget {
MyPageView({Key key}) : super(key: key);
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
#override
Widget build(BuildContext context) {
return PageView(
children: <Widget>[
Container(
color: Colors.black,
child: Card(
color: Colors.red,
)
),
Container(
color: Colors.black,
child: Card(
color: Colors.blue,
),
),
Container(
color: Colors.black,
child: Card(
color: Colors.green,
),
),
],
);
}
}
You just need to add some padding around each page and the width of the page view must be at least the 'card width + the padding from both sides'. This worked for me:
class MyWidget extends StatelessWidget {
final _CARD_WIDTH = 220.0;
final PageController _controller = PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Container(
height: _CARD_WIDTH,
width: _CARD_WIDTH + 32,
child: PageView(
scrollDirection: Axis.horizontal,
controller: _controller,
children: <Widget>[
_buildImageCard("1"),
_buildImageCard("2"),
_buildImageCard("3"),
],
),
);
}
Widget _buildImageCard(String text) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16),
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(15),
),
width: _CARD_WIDTH,
height: _CARD_WIDTH,
child: Center(
child: Text(text),
),
),
);
}
}