Project
Hi, I'm trying to make some custom transition in flutter between two simple screen. My goal is to use Navigator.push(context, MyRoute(..)) to call another the second screen on top of the first one. My problem is that I want the second screen to be only half size of the height of the device. The rest of the screen should only display the old page, maybe with some kind of blur.
I'm searching for a BottomSheet style effect but without using the actual widget.
Problem
No matter what I try, when Navigator.push is called, the new screen will always be resized to fill the entire screen and I'm unable to get a smaller scaffold on top with some transparency to hide the old page.
Thanks
I think you can do something like the following. First create a transparent page route. Here is a class that extends the PageRoute class to create transparent page route so you can see what is behind it. It overrides the "opaque" value and sets it to false.
import 'package:flutter/widgets.dart';
/// Creates a route that leaves the background behind it transparent
///
///
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
#required this.builder,
RouteSettings settings,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 350);
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final result = builder(context);
return FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(animation),
child: Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: result,
),
);
}
}
Use that as your page route.
In the Scaffold that you want to navigate to you can do the following. This will make it so it only takes up half of the screen and shows the previous page behind it:
Navigator.of(context).push(
TransparentRoute(
builder: (context) => Scaffold(
appBar: null,
backgroundColor: Colors.transparent,
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height / 2,
color: Colors.red,
),
),
)
),
);
If I understand your problem correctly I think this should do the trick!
Related
I don't know if I used correct terms in the title. I meant share by being displayed in diffrent pages with the same state, so that even if I push a new page, the “shared” widget will stay the same.
I'm trying to share the same widget across several pages, like the navigation bar of facebook.com.
As I know, Navigator widget allows to build up a seperate route. I've attempted to use the widget here, and it works quite well.
...
Scaffold(
body: Stack(
children: [
Navigator(
key: navigatorKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) => MainPage());
},
// observers: <RouteObserver<ModalRoute<void>>>[ routeObserver ],
),
Positioned(
bottom: 0,
child: BottomBarWithRecord(),
)
],
));
...
To summarize the situation, there used to be only one root Navigator (I guess it's provided in MaterialApp, but anyway), and I added another Navigator in the route under a Stack (which always display BottomBarWithRecord).
This code works perfect as I expected, that BottomBarWithRecord stays the same even if I open a new page in that new Navigator. I can also open a new page without BottomBarWithRecord by pushing the page in the root Navigator: Navigator.of(context, rootNavigator: true).push(smthsmth)
However, I couldn't find a way to change BottomBarWithRecord() as the route changes, like the appbar of facebook.com.
What I've tried
Subscribe to route using navigator key
As I know, to define a navigator key, I have to write final navigatorKey = GlobalObjectKey<NavigatorState>(context);. This doesn't seem to have addListener thing, so I couldn't find a solution here
Subscribe to route using navigator observer
It was quite complicated. Normally, a super complicated solutions works quite well, but it didn't. By putting with RouteAware after class ClassName, I could use some functions like void didPush() {} didPop() didPushNext to subscribe to the route. However, it was not actually "subscribing" to the route change; it was just checking if user opened this page / opened a new page from this page / ... , which would be complicated to deal with in my situation.
React.js?
When I learned a bit of js with React, I remember that this was done quite easily; I just had to put something like
...
const [appBarIndex, setAppBarIndex] = useState(0);
//0 --> highlight Home icon, 1 --> highlight Chats icon, 2 --> highlight nothing
...
window.addEventListener("locationChange", () => {
//location is the thing like "/post/postID/..."
if (window.location == "/chats") {
setAppBarIndex(1);
} else if (window.location == "/") {
setAppBarIndex(0);
} else {
setAppBarIndex(2);
}
})
Obviously I cannot use React in flutter, so I was finding for a similar easy way to do it on flutter.
How can I make the shared BottomBarWithRecord widget change as the route changes?
Oh man it's already 2AM ahhhh
Thx for reading this till here, and I gotta go sleep rn
If I've mad e any typo, just ignore them
You can define a root widget from which you'll control what screen should be displayed and position the screen and the BottomBar accordingly. So instead of having a Navigator() and BottomBar() inside your Stack, you'll have YourScreen() and BottomBar().
Scaffold(
body: SafeArea(
child: Stack(
children: [
Align(
alignment: Alignment.topCenter,
child: _buildScreen(screenIndex),
),
Align(
alignment: Alignment.bottomCenter,
child: BottomBar(
screenIndex,
onChange: (newIndex) {
setState(() {
screenIndex = newIndex;
});
},
),
),
],
),
),
)
BotttomBar will use the screenIndex passed to it to do what you had in mind and highlight the selected item.
_buildScreen will display the corresponding screen based on screenIndex and you pass the onChange to your BottomBar so that it can update the screen if another item was selected. You won't be using Navigator.of(context).push() in this case unless you want to route to a screen without the BottomBar. Otherwise the onChange passed to BottomBar will be responsible for updating the index and building the new screen.
This is how you could go about it if you wanted to implement it yourself. This package can do what you want as well. Here is a simple example:
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
State<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
final PersistentTabController _controller = PersistentTabController(initialIndex: 0);
#override
Widget build(BuildContext context) {
return PersistentTabView(
context,
controller: _controller,
screens: _buildScreens(),
items: _navBarsItems(),
);
}
List<Widget> _buildScreens() {
return [
const FirstScreen(),
const SecondScreen(),
];
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
PersistentBottomNavBarItem(
icon: const Icon(Icons.home),
title: ('First Screen'),
),
PersistentBottomNavBarItem(
icon: const Icon(Icons.edit),
title: ('Second Screen'),
),
];
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Center(
child: Text('First Screen'),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Center(
child: Text('Second Screen'),
);
}
}
Im trying to make a custom animation for a new page route, where my new page fades in and scales in, and that part is working out nicely. However, my old page will slide out leaving behind a blank white screen. I'd like my new screen to show over the old screen with the old screen remaining just as it was
Heres what I have so far:
Navigator.of(context).push(MyImagePageRoute(MyImageViewer(image: imgsrc)));
class MyImagePageRoute<T> extends CupertinoPageRoute<T> {
MyImagePageRoute(this.child)
: super(builder: (BuildContext context) => new MyImageViewer(image: ''));
#override
// TODO: implement barrierColor
Color get barrierColor => Colors.transparent;
#override
String? get barrierLabel => null;
final Widget child;
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return
ScaleTransition(
scale: animation,
child: FadeTransition(
opacity: animation,
child: child,
),
);
}
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 500);
}
How to implement flutter code so that as soon as my application is launched, it will show circularprogressindicator and then load another class through Navigation.push
As I know navigation.push requires a user action like ontap or onpressed
Please assist me with this
The requirement you need is of Splash Screen, which stays for a while, and then another page comes up. There are certain things you can do, that is
Use Future.delayed constructor, which can delay a process by the Duration time you provide, and then implement your OP, in this case, you Navigator.push()
Future.delayed(Duration(seconds: your_input_seconds), (){
// here you method will be implemented after the seconds you have provided
Navigator.push();
});
The above should be called in the initState(), so that when your page comes up, the above process happens and you are good do go
You can use your CircularProgressIndicator normally in the FirsScreen
Assumptions:
Our page will be called FirstPage and SecondPage respectively.
We will be going from FirstPage -> SecondPage directly after N seconds
Also, if you are working on a page like this, you don't want to go back to that page, so rather than using Navigator.push(), use this pushAndRemoveUntil
Let us jump to the code now
FirstPage.dart
// FIRST PAGE
class FirstPage extends StatefulWidget {
FirstPage({Key key, this.title}) : super(key: key);
final String title;
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
//here is the magic begins
#override
void initState(){
super.initState();
//setting the seconds to 2, you can set as per your
// convenience
Future.delayed(Duration(seconds: 2), (){
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(
builder: (context) => SecondPage()
), (_) => false);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: double.infinity,
width: double.infinity,
child: Center(
child: CircularProgressIndicator()
)
)
);
}
}
SecondPage.dart
// SECOND PAGE
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Page"),
),
body: Container(
height: double.infinity,
width: double.infinity,
child: Center(
child: Text('Welcome to Second Page')
)
)
);
}
}
Result
Look at how the page works, with out having any buttons, stays for 2 seconds and then go to second page. But also, no back button, since going back is not the right choice. You must remove all the items from the stack if you are making a page like this
EDITS AS PER THE ERROR
Since I can see that you're currently getting an error because, the Widget is not ready, to even call the Future.delayed(). To do that what you need to do is, make changes in your FirstPage.dart, initState() method. Rest can left as is
#override()
void initState(){
super.initState();
// Ensures that your widget is first built and then
// perform operation
WidgetsBinding.instance.addPostFrameCallback((_){
//setting the seconds to 2, you can set as per your
// convenience
Future.delayed(Duration(seconds: 2), (){
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(
builder: (context) => SecondPage()
), (_) => false);
});
});
}
OR
If WidgetsBinding.instance.addPostFrameCallback((_){}, this doesn't comes handy, use this in place of the mentioned function
// This needs to be imported for this particular only
// i.e., ScheduleBider not WidgetBinding
import 'package:flutter/scheduler.dart';
#override
void initState(){
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_){
//setting the seconds to 2, you can set as per your
// convenience
Future.delayed(Duration(seconds: 2), (){
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(
builder: (context) => SecondPage()
), (_) => false);
});
});
}
I have a navigator and a map nested in a Stack, the children of the navigator have a dynamic size.
Only the navigator forces himself to take all the space he can and cover the map behind him. It is therefore impossible to move the map.
It's possible to make the navigator adapt to the size of the child?
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
StreamProvider(
create: (context) => LocationService().locationStream,
child: MapView()
),
Positioned(
width: MediaQuery.of(context).size.width * 1,
bottom: 0,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 1,
maxWidth: MediaQuery.of(context).size.width * 1
),
child: navigator(),
)
),
],
)
);
}
navigator() {
return Navigator(
initialRoute: 'anyview',
onGenerateRoute: (settings) {
Widget view;
switch (settings.name) {
case 'anyview':
view = AnyViewDraggableScrollableSheet();
break;
case 'otheranyview':
view = OtherAnyViewDraggableScrollableSheet();
break;
}
var builder = (BuildContext _) {
return view;
};
return MaterialPageRoute(builder: builder, settings: settings);
},
);
}
}
Here's a demo of the problem, the navigator takes all the available space and therefore blocks it by tapping on the red block.
https://dartpad.dev/cd1c4d8f1067db0cc65ea2f62c2cbaad
Navigator manages a stack of routes from which you can either push routes to pages into or pop routes from. If you are trying to have a certain container in the same page above your Map , I don't think that the Navigator is the right widget to use unless you actually want to have a page above it.
I propose that you use a sliding up panel that can be placed at the bottom of the page above your map and the user can just slide it up to use or view any information or buttons that you wish to play on it.
If you do not wish to create one yourself , I have had success by using this package in the past in a same case scenario.
Sliding up panel package
Also , I believe that the easier way to use Navigator is simply
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => MyPage()));
if you wish to route to a different Page from the one you are currently at and Navigator.pop(context);
if you wish to go back to a previous page. There are many other more specific methods for pushing and popping that you can look up in the detailed Flutter documentation.
I'm trying to create a master-detail type container starting with a column of ListTiles on the left side of the screen. When a user taps on an item, a preset URL will then be displayed on the rest of the screen. Tapping a different item displays a different preset URL.
I've looked at the Flutter WebView Plugin and and webview_flutter packages, but either I don't understand them well enough (quite possible!) or they can't yet do everything I want them to to do.
Beside what I just mentioned, if possible I'd also like the web pages to open zoomed to fit the space they're in, but still be pinchable to other sizes.
p.s. I'm new to Flutter and am also confused about widget construction and memory management. If I try using something like a WebView widget, I don't know whether I just code a WebView widget every time I want to open a page, or if I somehow create a single WebView widget, add a controller, and code .loadFromUrl() methods.
You can create a Row with two children. First children will be ListView that will be consisted of ListTiles. Second children will be the WebView. When a user taps on the list tile, load the url with the controller. There is no need to rebuild the WebView every time in your case
Example by using webview_flutter:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
List pages = ["https://google.com", "https://apple.com"];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: <Widget>[
Container(
width: 300,
child: ListView.builder(
itemCount: pages.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(pages[index]),
onTap: () {
if (_controller != null) {
_controller.loadUrl(pages[index]);
}
},
);
},
),
),
Expanded(
child: WebView(
onWebViewCreated: (WebViewController c) {
_controller = c;
},
initialUrl: 'https://stackoverflow.com',
),
),
],
));
}
}
Just wrap the webview inside a SizedBox
SizedBox(
height: 300,
child: WebView()
)