I want to create a website using flutter web but I'm unable to navigate to sections in the same page. Here's an example of what I want to achieve using flutter.
P.S. Navigator is not working:
I want to create a website using flutter web but I'm unable to navigate to sections in the same page. Here's an example of what I want to achieve using flutter.
P.S. Navigator is not working:
I created an example with PageView
class MyHomePage extends StatelessWidget {
var list = ["Home","Services", "Work", "About"];
var colors = [Colors.orange, Colors.blue, Colors.red, Colors.green];
PageController controller = PageController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 50,
height: 50,
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10)
),
),
Spacer(),
Row(
children: List.generate(3, (index){
return GestureDetector(
onTap: (){
_scrollToIndex(index);
},
child: Container(
margin: EdgeInsets.all(8),
child: Text(
list[index+1]
),
),
);
}),
)
],
),
Expanded(
child : PageView(
scrollDirection: Axis.vertical,
pageSnapping: false,
controller: controller,
children: List.generate(list.length, (index){
return Container(
width: MediaQuery.of(context).size.width,
height: double.maxFinite,
color: colors[index],
child: Center(
child: Text(
list[index],
style: TextStyle(
color: Colors.white,
fontSize: 50
),
),
),
);
})
),
),
],
)
),
);
}
void _scrollToIndex(int index) {
controller.animateToPage(index + 1, duration: Duration(seconds: 2), curve: Curves.fastLinearToSlowEaseIn);
}
}
The output:
ScrollController is the thing you are looking for.
Add a new one to your ScrolView and you can set where you want it to scroll to.
Josteve mentioned a way of doing it. But I'd like to show the other way which provides more features as one would expect in the gif example you have put.
You can see the demo here: https://mohith7548.github.io/portfolio/
My project has 3 sections called About, Blog & Projects. It also has another top section called Home. So the order of screens is Home, About, Blog & Projects. Each section takes full-screen height & width. So the starting offset for these pages are [0 * screenHeight, 1 * screenHeight, 2 * screenHeight, 3 * screenHeight]
respectively. screenHeight
can be accessed by MediaQuery.of(context).size.height
inside build
method.
class Portfolio extends StatefulWidget {
@override
_PortfolioState createState() => _PortfolioState();
}
class _PortfolioState extends State<Portfolio> {
ScrollController _scrollController;
String _curNavItem;
static double offsetHome = 0;
static double offsetAbout = SizeConfig.screenHeight;
static double offsetBlog = 2 * SizeConfig.screenHeight;
static double offsetProjects = 3 * SizeConfig.screenHeight;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
void scrollTo(String title) {
double offset = 0;
switch (title) {
case Constants.HOME:
offset = offsetHome;
break;
case Constants.ABOUT:
offset = offsetAbout;
break;
case Constants.BLOG:
offset = offsetBlog;
break;
case Constants.PROJECTS:
offset = offsetProjects;
break;
}
setState(() {
_curNavItem = title;
});
// animate to the pag
_scrollController.animateTo(
offset,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutQuart,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: PageScrollPhysics(), // use NeverScrollableScrollPhysics() to block user scrolling
controller: _scrollController,
slivers: <Widget>[
// This is just SliverAppBar wrapped in InterheritedWidget called NavState
// You can use just SliverAppBar
NavState(
curNavItem: _curNavItem,
scrollTo: scrollTo,
child: AppBanner(key: _appBannerKey), // SliverAppBar in another file
),
SliverList(
delegate: SliverChildListDelegate([
About(),
Blog(),
Projects(),
]),
)
],
),
);
}
}
You can do this in different ways:
Suppose you want to have a fixed SidebarView and HeaderView in all pages and also you have a ContentPage that will be changed. So you can have a RootPage including these 3 sections and change ContentPage by the riverpod, so that only ContentPage will be changed.
class RootPage extends StatelessWidget {
const RootPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: SidebarView(),
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Responsive.isDesktop(context))
const Expanded(
flex: 1,
child: SidebarView(),
),
Expanded(
flex: 5,
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HeaderView(),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Consumer(
builder: (context, ref, _) {
var watch = ref.watch(pageVisibleStateProvider);
return contentPageSelection(watch.state);
},
),
),
),
],
),
),
),
],
),
);
}
}
simply change content page:
Widget contentPageSelection(String pageName){
switch(pageName){
case "page1":
return Page1();
case "page2":
return Page2();
case "page3":
return Page3();
default:
return DefaultPage();
}
}
where:
final pageVisibleStateProvider = StateProvider<String>((_) => "defaultPage");
and:
class SidebarView extends StatelessWidget {
const SidebarView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Text("sidebar content"),
);
}
}
class HeaderView extends StatelessWidget {
const HeaderView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Text("HeaderView content"),
);
}
}
Now you can change content page. for example you want to show Page2:
ElevatedButton(
onPressed: (){
ref.read(pageVisibleStateProvider.notifier).state = "page2";
},
child: Text("go to page 2"),
)
where page2 and other content pages only includes content not sidebar or header:
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text("page2 content");
}
}