Flutter TabBar-like Indicator - flutter

I need help identifying a Flutter widget that could do things as demonstrated by this GIF:
I am interested in the circular indicator that allows me to browse through the resort locations like above. So far the closest thing that I have found was TabBar, but that isn't what I am exactly looking for. It seems far too complex to modify it to do the things that the GIF does, and I am very certain that there is a simpler solution, but after hours of exhaustive search through the list of widgets, I am not sure what to look for.
Could someone please help me?

Based on your UI, you can use dots_indicator or better smooth_page_indicator
class TestSnippet extends StatefulWidget {
const TestSnippet({super.key});
#override
State<TestSnippet> createState() => _TestSnippetState();
}
class _TestSnippetState extends State<TestSnippet> {
final PageController controller = PageController();
int itemCount = 44;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: PageView.builder(
itemCount: itemCount ~/ 3,
controller: controller,
itemBuilder: (context, index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
index.toString(),
),
Text("${index + 1}"),
Text("${index + 2}"),
],
),
),
),
SmoothPageIndicator(
count: itemCount ~/ 3,
controller: controller,
)
],
),
);
}
}

Related

PageView, inside ListView, inside Column: `Null check operator used on a null value`

I have a PageView, inside a ListView, inside a Column. It seems necessary because of the layout (top green widget should be scrollable, but bottom widget shouldn't be).
class MyWidget extends StatefulWidget {
Widget createPage(int page, Color color) {
return Container(
color: color,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 200.0),
child: Text("Page: $page. Swipe right to go to next page"),
),
);
}
late final List<Widget> pages = [
createPage(1, Colors.purple),
createPage(2, Colors.red)
];
#override
createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final PageController _pageController;
int page = 0;
#override
initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: ListView(
children: [
Container(
color: Colors.green,
child: const Text("Top of screen, scrollable.")),
// This works
Column(
children: widget.pages,
),
// PageView tries to take the full height. Without Flexible, PageView is unbounded.
// Flexible(
// child: PageView.builder(
// itemCount: widget.pages.length,
// controller: _pageController,
// onPageChanged: (int index) => setState(() => page = index),
// itemBuilder: (context, index) => widget.pages[index],
// ),
// )
],
),
),
Container(
color: Colors.green, child: const Text("Bottom of screen, always")),
],
);
}
}
However, I get various error based on what I try. This is an unhelpful error, since ListView doesn't have a problem when it's children is a Column:
The following _CastError was thrown during performLayout():
Null check operator used on a null value
The relevant error-causing widget was:
ListView ListView:file:///path/to/project/lib/main.dart:63:18
Code
I've created a Dartpad pad to reproduce this. I've commented out the PageView and replaced it with a column so you can see the app. The issue is replacing Column with 2 pages to PageView.
I've tried various things:
Wrapping ListView in things:
SizedBox.expand(child: ListView(...))
Expanded(child: ListView(...)) following this answer
Flexible(child: ListView(...))
ListView.builder(shrinkWrap: true,...),
Changing mainAxisSize of Column

Flutter page comes too slow. when it contain long data

My first page contains an flat button. when I press on that button, navigate to another page(MyApp) too slow(It contains long data). How to resolve it.Here is my code.
class MyApp extends StatefulWidget {
final List<String> items;
const MyApp({ this.items});
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
final ScrollController scrollcontroller=ScrollController();
var scaffoldkey=GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
const title = 'Long List';
return Scaffold(
appBar: AppBar(
title: const Text(title),
),
body:Stack(children: [Column(children:[ListView(shrinkWrap:true,children:[ Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical:0.0),
child: Container(
width: 300,
height: 70,
decoration: BoxDecoration(border:Border.all(color:Colors.brown[100],),color: Colors.orange[50], borderRadius:BorderRadius.all(Radius.circular(5.0))),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children:<Widget> [
Container(height: 35,
decoration: BoxDecoration(color: Colors.white,
border: Border.all(color: Colors.brown[300],width: 1),
borderRadius: BorderRadius.circular(5),),),],),),),),],)
]),Expanded(child:ListView(shrinkWrap:true,children:[ListView.builder(
shrinkWrap: true, physics: ScrollPhysics(), scrollDirection: Axis.vertical, itemCount:widget.items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(widget.items[index]),);},),]))])]) );}} ```
The best way is probably to load parts of your data as the user scrolls.
final ScrollController scrollcontroller=ScrollController();
List<String> items;
int numberOfItemsLoadedPerScroll = 15;
List<String> loadedItems = items.sublist(0, 15);
scrollcontroller.addListener(() {
setState(() {
loadedItems = items.sublist(0, loadedItems.Legth+numberOfItemsLoadedPerScroll);
});
});
It would be better to use a Bloc system and update the Stream instead of calling setState. But I think something like this should work.
The list view you are using is extremely inefficient and it takes some amount of time to build and render all the widgets inside it. I would suggest for you to use a list view builder instead as it would render your view way faster and it is the optimized way to have a list view in the first place. https://flutter.dev/docs/cookbook/lists/long-lists
Here are the docs if you want to check them out and it goes without saying that it is a best practice to use a list view builder instead of a regular old list view!
**Edit: for further explanation the list view builder renders items as you scroll down or up the list(15 items required to fill the screen for example) while the list view renders the entirety of the list all at once which may cause the app to be quiet slow and sometimes render it unresponsive by the managing OS (ios or android) if it takes too long.

PageView rebuilding while animateToPage is in progress

I'm creating a social media feed where each post is an image of a different size. The user can swipe right to like, left to dislike, up to skip to the next post, or down to go back. To do that, I'm using a Dismissible widget within a PageView, where each page contains a post/image. I used "animateToPage" in the Dismissible to automatically animate to the next page once the user swipes right or left.
The problem is that when the PageView animates to the next page, the image that was dismissed suddenly reappears on the previous page while the animation is happening. I want it to reappear only if the user swipes down to go back to the previous post, but not while the PageView is animating.
Here's a video showing what is going wrong
And here's an animation showing what I need
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin{
int pageIndex = 0;
PageController _pageController = PageController(
initialPage: 0,
);
#override
Widget build(BuildContext context) {
List images = [
'assets/1.jpg', 'assets/2.jpg', 'assets/3.jpg', 'assets/4.jpg', 'assets/5.jpg',
];
return MaterialApp(
home: Scaffold(
backgroundColor: Color.fromRGBO(250, 250, 250, 1),
body: LayoutBuilder(
builder: (context, constraints) => PageView.builder(
controller: _pageController,
itemCount: 5,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return images.map((image) => Dismissible(
onResize: () {
setState(() {
_pageController.animateToPage(index+1, duration: Duration(milliseconds: 300), curve: Curves.ease);
});
},
onDismissed: (direction) {},
key: UniqueKey(),
child: Container(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 100),
child: Container(
alignment: Alignment.center,
child: Image(
image: AssetImage(image)
),
),
),
),
),
))
.toList()[index];
}
),
),
),
);
}
}
I assume this is happening because PageView is rebuilding the other pages while the animation is in progress. I'm still a beginner in Flutter and wasn't able to find a solution. Any ideas of how to fix this?
Everytime setState is called, the widget is redrawn. Try to put your animated code outside of setState method.
Documentation

Flutter - Screen focus on a certain widget

I need help to do the following: when I press List 1, the screen focuses on List 1; I need the same for the rest of the options
This is the code for the example:
code
This behavior already exists in web pages but I haven't found this same behavior at the mobile app level. Thank you
Here is a small code snippet of something similar which might help you achieve you desired results.
By clicking the fab icon it will scroll down to item 35 within the ListView.
class MyHomePage extends StatelessWidget {
final _scrollController = ScrollController();
final _cardHeight = 200.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () => _animateToIndex(35),
child: Icon(Icons.add),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _cardHeight,
child: Card(
color: Colors.lightBlue,
child: Center(
child: Text("Scroll Item $i", style: TextStyle(fontSize: 28.0),),
),
),
),
),
);
}
_animateToIndex(index) {
_scrollController.animateTo(_cardHeight * index,
duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn);
}
}
You'll need to have a scrollable Widget (like ListView, SingleScrollableWidget) instead of a Column in ListSecondPage.
Then add a ScrollController to it and ListSecondPage should receive which button was tapped. Based on that selection you can scroll to the desired location with the ScrollController

Flutter - How to flip the previous card back using FlipCard

After days of search I'm getting help.
I work on a flutter application.
Context:
A grid view feeded with Json
-childs : GridTile with Flipcard in (https://pub.dev/packages/flip_card)
-On tap on GridTile there is a callback to get the selected Item and an animation because of the flipcard onTap
What I would:
When an item is aleready selected (flipcard flipped so we show the back of the card),
And I selected another item of the grid te(so flipcard of this itme also flipped)
I would like to flip back the old selected item Flipcard without rebuild the tree because I would lost the state of the new selected item.
I tried many thing. For example I tried to use GlobalKey on GridTiles to interract with after build but currentState is always null when I want to interact with.
I wonder what is the good practice in this case ?
I hope I was clear :) (I'm french)
Thank you the community!
.
Something to know...
It is possible to interract with the flipcard (child of gridtile) like this
(GlobalKey)
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
#override
Widget build(BuildContext context) {
return FlipCard(
key: cardKey,
flipOnTouch: false,
front: Container(
child: RaisedButton(
onPressed: () => cardKey.currentState.toggleCard(),
child: Text('Toggle'),
),
),
back: Container(
child: Text('Back'),
),
);
}
I'm not sure if I understood your question, but here is an example of how you could use a GridView with FlipCards:
var cardKeys = Map<int, GlobalKey<FlipCardState>>();
GlobalKey<FlipCardState> lastFlipped;
Widget _buildFlipCard(String text, Color color, int index) {
return SizedBox(
height: 120.0,
child: Card(
color: color,
child: Center(
child:
Text(text, style: TextStyle(color: Colors.white, fontSize: 20.0)),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FlipCards")),
body: GridView.builder(
itemCount: 20,
itemBuilder: (context, index) {
cardKeys.putIfAbsent(index, () => GlobalKey<FlipCardState>());
GlobalKey<FlipCardState> thisCard = cardKeys[index];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlipCardWithKeepAlive(
child: FlipCard(
flipOnTouch: false,
key: thisCard,
front: _buildFlipCard("$index", Colors.blue, index),
back: _buildFlipCard("$index", Colors.green, index),
onFlip: () {
if (lastFlipped != thisCard) {
lastFlipped?.currentState?.toggleCard();
lastFlipped = thisCard;
}
},
),
),
RaisedButton(
child: Text("Flip Card"),
onPressed: () => cardKeys[index].currentState.toggleCard(),
)
],
);
},
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
),
);
}
class FlipCardWithKeepAlive extends StatefulWidget {
final FlipCard child;
FlipCardWithKeepAlive({Key key, this.child}) : super(key: key);
#override
State<StatefulWidget> createState() => FlipCardWithKeepAliveState();
}
class FlipCardWithKeepAliveState extends State<FlipCardWithKeepAlive>
with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}
#override
bool get wantKeepAlive => true;
}
You need to use a different key for each element of the list, I used a Map in this case.
I also wrapped the FlipCard with a custom FlipCardWithKeepAlive stateful widget that uses AutomaticKeepAliveClientMixin to keep alive the FlipCard while scrolling.
Edit: I updated the code so when you flip one card, the previous card flipped gets flipped back. Basically you need to save the last flipped card and when a new one is flipped, flip the last one and put the new one as last flipped.
The code will make both cards flip at the same time, if you want one card to wait the other use onFlipDone() instead of onFlip(), like this:
onFlipDone: (isFront) {
bool isFlipped = !isFront;
if (isFlipped && lastFlipped != thisCard) {
lastFlipped?.currentState?.toggleCard();
lastFlipped = thisCard;
}
}