Display multiple pages at the same time - flutter

I have an app with two features, that have routes such as:
/feature1
/feature1/a
/feature2
/feature2/a
/feature2/a/b
/feature2/c
I can use GoRouter and its ShellRoute to switch between these one at a time using context.goNamed('feature2'), which would replace the entire screen with feature 2 (when tapping a tab in a tab bar for example). Here's a diagram of just the top level routes using tabs:
However, I would like to have an overview style menu which displays multiple destinations at once, so the user can see where they will be going before they go there (for example the preview page tabs in a mobile web browser). Here's a diagram:
and then tapping on either of the two pages would make them full screen:
Pressing the menu button at the bottom would return you to the overview menu page.
One way I have thought about solving this would be to make static preview images out of the routes when the menu button is tapped, and just display the previews. But these won't be live, and I would like a more elegant approach that actually displays the live contents of the route if possible.
Another way I have thought about solving this would be to use a top level GoRouter and then two descendant GoRouters each containing just one branch of the routes. I'm not sure if multiple GoRouters would lead to problems with things like if I wanted to context.go() to another branch.
If the ShellRoute.builder gave me access to all of the child page's widgets, I could display them however I wanted, but it just provides a single child.

I have not worked with 'go_router' or 'ShellRoute.builder', but I like to make custom animated widgets like this for apps. It's also hard to explain how it would work in your app, but here is my take on this.
Try copy pasting this in an empty page. I have written some notes in code comments that might help explain things a little bit. And, this is not perfect but with more polishing according to the needs it could work.
class CustomPageView extends StatefulWidget {
const CustomPageView({Key? key}) : super(key: key);
#override
State<CustomPageView> createState() => _CustomPageViewState();
}
class _CustomPageViewState extends State<CustomPageView> {
// Scroll Controller required to control scroll via code.
// When user taps on the navigation buttons, we will use this controller
// to scroll to the next/previous page.
final ScrollController _scrollController = ScrollController();
// Saving screen width and height to use it for the page size and page offset.
double _screenWidth = 0;
double _screenHeight = 0;
// A bool to toggle between full screen mode and normal mode.
bool _viewFull = false;
#override
void initState() {
super.initState();
// Get the screen width and height.
// This will be used to set the page size and page offset.
// As of now, this only works when page loads, not when orientation changes
// or page is resized. That requires a bit more work.
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_screenWidth = MediaQuery.of(context).size.width;
_screenHeight = MediaQuery.of(context).size.height;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
// 'Column' to wrap the 'Body' and 'BottomNavigationBar'
body: Column(
children: [
// 'Expanded' to take up the remaining space after the 'BottomNavigationBar'
Expanded(
// A 'Container' to wrap the overall 'Body' and aligned to center.
// So when it resizes, it will be centered.
child: Container(
alignment: Alignment.center,
// 'AnimatedContainer' to animate the overall height of the 'Body'
// when user taps on the 'Full Screen' button.
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: _viewFull ? 200 : _screenHeight,
// A 'ListView' to display the pages.
// 'ListView' is used here because we want to scroll horizontally.
// It also enables us to use 'PageView' like functionality, but
// requires a bit more work, to make the pages snap after scrolling.
child: ListView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
children: [
// A 'Container' to display the first page.
AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: _viewFull ? (_screenWidth / 2) - 24 : _screenWidth,
margin: _viewFull ? const EdgeInsets.all(12) : const EdgeInsets.all(0),
color: Colors.blue,
),
// A 'Container' to display the second page.
AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: _viewFull ? (_screenWidth / 2) - 24 : _screenWidth,
margin: _viewFull ? const EdgeInsets.all(12) : const EdgeInsets.all(0),
color: Colors.yellow,
),
],
),
),
),
),
// 'BottomNavigationBar' to show the navigation buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 'Feature 1' button
GestureDetector(
onTap: () {
// Scroll to the first page
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Container(
height: 60,
alignment: Alignment.center,
color: Colors.red,
padding: const EdgeInsets.all(12),
child: const Text('Feature 1'),
),
),
// 'Feature 2' button
GestureDetector(
onTap: () {
// Scroll to the second page
_scrollController.animateTo(
_screenWidth,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Container(
height: 60,
alignment: Alignment.center,
color: Colors.green,
padding: const EdgeInsets.all(12),
child: const Text('Feature 2'),
),
),
// 'Full Screen' button
GestureDetector(
onTap: () {
// Toggle between full screen mode and normal mode
setState(() {
_viewFull = !_viewFull;
});
},
child: Container(
height: 60,
alignment: Alignment.center,
color: Colors.purple,
padding: const EdgeInsets.all(12),
child: const Text('View Full'),
),
),
],
),
],
),
);
}
}

Related

Custom splash screen with animation-Flutter

I am trying to show a splash screen with an animation, and I found this article: https://medium.com/#galadhruvil7/flutter-splash-screen-animation-16c50e18b9d8 I think that's very simple but wonderful, the case is I would like to change the flutter icon into another image and text. Here is the code
SplashScreenState() {
_timer = new Timer(const Duration(seconds: 1), () {
setState(() {
assetImage = Row(
children: [
Image.asset('assets/logo.png', height: 500, width: 500),
Text("trial")
],
);
});
});
}
and showing the widget like this:
return Scaffold(
backgroundColor: Colors.grey[850],
body: Center(
child: Container(
child: assetImage,
),
),
);
but by running that code, there is no animation effect. Is there a way to keep the animation like the source that I have given while the Flutter logo is changed into image and text ?
Through AnimatedSwitcher with conditional operators you can change your logo with icon and text.
Example - AnimatedSwitcherExample

switch between pages in the flutter using the Smooth Page Indicator

I want to make a steamer to the next page using the Smooth Page Indicator widget. I added this widget to the page but I don't know how to add the pages I want to go to. I will be grateful for your help
My code :
final controller = PageController(viewportFraction: 0.8, keepPage: true);
#override
Widget build(BuildContext context) {
return Scaffold(
body:
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(height: 35.0),
Container(
child: SmoothPageIndicator(
controller: controller,
count: 2,
effect: JumpingDotEffect(
dotHeight: 16,
dotWidth: 16,
jumpScale: .7,
verticalOffset: 15,
),
),
),
]),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_scanQR();
});
},
child: const Icon(Icons.qr_code),
backgroundColor: Colors.pink,
),
);
}
the name of my page to which I want to go Page2, and this Page1
I think you might be looking for the PageView widget : https://api.flutter.dev/flutter/widgets/PageView-class.html
By passing the same PageController to both, the PageView and the SmoothPageIndicator widgets you should be able to swipe left and right AND see the dots move along. That simple.

How to create an image gallery on flutter?

I have a Set<String> variable containing a list of image files. In a Card object, in addition to some Text elements, I want to create a gallery of images like this:
The number of images stored in Set<String> is variable. If possible, I want to show in a corner (top-right) the current number image and the total count of images (e.g. 1/5).
The images will be withdrawn from a webserver, and I don't know if it's more efficient to save them in cache or not. I don't want to use storage folder to save the images.
If possible, I would to withdraw all the images in a single http request, to save time.
Here the variable:
Set<String> _photosList = {
'http://myhost.com/image01.jpg',
'http://myhost.com/image02.bmp',
'http://myhost.com/image03.png',
'http://myhost.com/image04.gif',
};
There are certain things which I want you to explore before actually jumping into the code. These are:
PageView class, this will help you know about how this scrolling thing works like Gallery View. Also, will tell you how the nextPage and previousPage works with the icon clicks
PageController class how the pages works inside the PageView
Stack class, for aligning your arrow on top of your gallery
Let us now jump to the code how it works. Follow the comments to know about each work
// this will keep track of the current page index
int _pageIndex = 0;
// this is your page controller, which controls the page transition
final PageController _controller = new PageController();
Set<String> _photosList = {
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRqRwpDKN_zJr1C7pPeWcwOa36BtPm4HeLPgA&usqp=CAU',
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSgjZ8pw5WLIGMBibVi_g4CMlSE-EOvrLv7Ag&usqp=CAU',
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQUuMIENOhc1DmruZ6SwLc7JtrR6ZMBRAb3jQ&usqp=CAU',
'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRzasDrBHWV-84vxbmlX7MTuz3QHqtT8jtTuA&usqp=CAU'
};
Now the UI of the Gallery View.
Please note: This code supports, swipe functionality in the view. If you want to disable it just add this line inside your Pageview.builder()
physics:new NeverScrollableScrollPhysics()
Container(
// use MediaQuery always, it will always adjust the dimensions
// according to different screens
height: MediaQuery.of(context).size.height * 0.3,
width: MediaQuery.of(context).size.width * 0.4,
// here is your stack
child: Stack(
children: [
// PageView.builder is just the part of PageView, read through
// Documentation, and you will get to know
PageView.builder(
controller: _controller,
// here you can remove swipe gesture. UNCOMMENT IT
// physics:new NeverScrollableScrollPhysics()
onPageChanged: (index){
// with each change updating the index of our variable too
setState(() => _pageIndex = index);
},
itemCount: _photosList.length,
// building the view of our gallery
itemBuilder: (BuildContext context, int position){
return Align(
alignment: Alignment.topLeft,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(_photosList.elementAt(position))
)
)
)
);
}
),
// this will come over the images, the icon buttons
Positioned(
left: 0.0,
right: 0.0,
top: MediaQuery.of(context).size.height * 0.12,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: (){
// checking if we are not on pos = 0
// then we can always go back else do nothing
if(_pageIndex != 0)
_controller.previousPage(duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
icon: Icon(Icons.arrow_back_ios, color: Colors.white, size: 28.0)
),
IconButton(
onPressed: (){
// checking if we are not on pos = photosList.length - 1
// we calculate 0 to length-1
// then we can always go forward else do nothing
if(_pageIndex < _photosList.length-1)
_controller.nextPage(duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
icon: Icon(Icons.arrow_forward_ios, color: Colors.white, size: 28.0)
),
]
)
)
]
)
)
Result
Pointer: In order to show them numbering on the corner. just make use of two variables which is already there for you in the code
_pageIndex, keeps an update of the page changes
_photosList.length, which gives you the total count of the images
Do something like this, and show it using Container in the same view.
//_pageIndex + 1, cos it starts from 0 not 1, and goes up to 4 not 5
Text('${_pageIndex+1}/$_photosList.length')

Flutter AnimatedSwitcher jumps between children

I am trying to implement some custom design in an expasion panel list. Therefore, I wanted to create some kind of animation that animates smoothly from one view (e.g. header) to another view (e.g. full info of the tile) that has other dimensions (obviously, full info will be higher than just the header). This is quite easy to implement with an AnimatedContainer. However, I would need the height of the header widget and the full info widget in order to animate between these two heigths. As these values differ between tiles (other info -> maybe other height) and tracking height via global keys is not my preferred solution, I decided to use the much simpler AnimatedSwitcher instead. However, the behavior of my AnimatedSwitcher is quite strange. At first, the other tiles in the ListView (in my example the button) move down instantly and subsequently the tile expands. Has anyone an idea of how I could implement some code in order to achieve the same animation that I would get from AnimatedContainer(button/other tiles moving down simultaniously with the tile expanding)? Thanks in advance for any advice. Here is my code:
class MyPage extends State {
List _items;
int pos;
#override
void initState() {
pos = 0;
_items = [
Container(
color: Colors.white,
width: 30,
key: UniqueKey(),
child: Column(
children: <Widget>[Text('1'), Text('2')], //example that should visualise different heights
),
),
Container(
width: 30,
color: Colors.white,
key: UniqueKey(),
child: Column(
children: <Widget>[Text('1'), Text('2'), Text('44534'), Text('534534')],
),
)
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.only(top: 100),
children: <Widget>[
AnimatedSwitcher(
duration: Duration(seconds: 1),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: _items[pos],
),
RaisedButton(
child: Text('change'),
onPressed: pos == 0
? () {
setState(() => pos = 1);
}
: () {
setState(() => pos = 0);
})
],
),
);
}
}
The solution was quite simple. Just found out that there exists an AnimatedSize Widget that finds out the size of its children automatically.
I stumbled on this post and since I had a similar problem I decided to create a tutorial here on how to mix AnimatedSwitcher and AnimatedSize to solve this issue. Animations do not happen at the same time but the advantage is that you have full control on the animation provided to the switcher.
I ended up doing this in the end (please note that I'm using BlocBuilder and that AnimatedSizeWidget is a basic implementation of AnimatedSize:
AnimatedSizeWidget(
duration: const Duration(milliseconds: 250),
child: BlocBuilder<SwapCubit, bool>(
builder: (context, state) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
child: state
? Icon(Icons.face, size: 80, key: Key("80"))
: Icon(Icons.face, size: 160, key: Key("160")),
);
},
),
),
var isWidgetA = true;
final Widget widgetA = Container(
key: const ValueKey(1),
color: Colors.red,
width: 100,
height: 100,
);
final Widget widgetB = Container(
key: const ValueKey(2),
color: Colors.green,
width: 50,
height: 50,
);
...
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: ScaleTransition(
child: child,
scale: animation,
alignment: Alignment.center,
),
);
},
child: isWidgetA
? widgetA
: widgetB,
),

How to animate an image moving across the screen?

I have a fixed size image (of a playing card) and I'd like to write code so users see the image slide from one part of the screen to another (like a card being dealt and moving across the surface). If possible, it would be best to do so in a way that's moderately responsive for different screen sizes.
Most of what I've seen or learned about involves Hero widgets or animation where a widget changes size but stays in the same location. I'm asking about something different.
What you want is the animatedPosition widget, it will move any widget from one point of the screen to another, you can even morph the size of the widget.
https://api.flutter.dev/flutter/widgets/AnimatedPositioned-class.html
AnimatedPositioned(
width: selected ? 200.0 : 50.0,
height: selected ? 50.0 : 200.0,
top: selected ? 50.0 : 150.0,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Container(
color: Colors.blue,
child: const Center(child: Text('Tap me')),
),
),
),
You can either use Flutter's built in animations Animation<double>. In Flutter, an Animation object knows nothing about what is onscreen. An Animation is an abstract class that understands its current value and its state (completed or dismissed). One of the more commonly used animation types is Animation<double>.
For example:
// lib/main.dart (AnimatedLogo)
class AnimatedLogo extends AnimatedWidget {
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: FlutterLogo(),
),
);
}
}
Or you can import and use one of these packages into your project:
https://pub.dev/packages/simple_animations
https://pub.dev/packages/animator