Flutter UI challenge - flutter

How to create this kind of UI in flutter. with expanded pageview??
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:movie_app/components/bottom_tabs.dart';
import 'package:movie_app/components/card_image.dart';
import 'package:movie_app/components/expandable_text.dart';
import 'package:movie_app/components/heading.dart';
import 'package:movie_app/constants.dart';
import 'package:movie_app/screens/card_details/components/card_details_cast.dart';
import 'package:movie_app/screens/card_details/components/card_details_photos.dart';
import 'package:movie_app/screens/card_details/components/card_details_reviews.dart';
import 'package:movie_app/screens/card_details/components/card_details_tabs.dart';
import '../../../components/my_text.dart';
import '../card_details.dart';
class Body extends StatefulWidget {
const Body({Key key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
PageController _tabsPageController;
int _selectedTab = 0;
#override
void initState() {
_tabsPageController = PageController();
super.initState();
}
#override
void dispose() {
_tabsPageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Stack(
clipBehavior: Clip.none,
children: [
Image.network(
"https://static.wikia.nocookie.net/film-vault/images/c/c8/Furypost.jpg/revision/latest?cb=20171202094520",
height: 260,
fit: BoxFit.cover,
),
Positioned(
top: 100,
left: 180,
child: SvgPicture.asset(
"assets/icons/play icon.svg",
color: kwhitecolor,
height: 50,
),
),
Container(
margin: EdgeInsets.only(top: 200, left: 15, right: 15, bottom: 20),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const CardImage(
image:
"https://m.media-amazon.com/images/M/MV5BMjA4MDU0NTUyN15BMl5BanBnXkFtZTgwMzQxMzY4MjE#._V1_.jpg",
width: 120,
),
const SizedBox(width: 15),
// Text("data"),
Column(
// mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
MyText(
"FURY",
fontSize: 22,
isBold: true,
color: Colors.black,
),
SizedBox(width: 2),
MyText(
"(2014)",
fontSize: 14,
isBold: true,
color: Colors.black,
),
],
),
const MyText(
"Action/Drama/War",
fontWeight: FontWeight.w600,
),
const SizedBox(height: 40),
Row(
children: [
const Icon(Icons.remove_red_eye_outlined,
color: klightgreyColor),
const SizedBox(width: 2),
const MyText("1.5M", isBold: true),
const SizedBox(width: 10),
SvgPicture.asset(
"assets/icons/comment.svg",
height: 21,
color: klightgreyColor,
),
const SizedBox(width: 4),
const MyText("1K", isBold: true),
const SizedBox(width: 10),
SvgPicture.asset(
"assets/icons/clock.svg",
height: 18,
color: klightgreyColor,
),
const SizedBox(width: 4),
const MyText("2h 30min", isBold: true),
],
),
],
),
],
),
const SizedBox(height: 30),
const Heading(
"STORYLINE",
fontSize: 18,
padding: 0,
lineColor: Color.fromARGB(255, 255, 68, 68),
// fontWeight: FontWeight.w800,
),
const SizedBox(height: 10),
const ExpandableText(
text:
"1945, the Allies are making their final push in the European theater. A battle-hardened Army sergeant nallier (Brad Pitt), leading a Sherman tank and a five-man crew, undertakes a deadly mission behind enemy lines. Hopelessly outnumbered, outgunned and saddled with an inexperienced soldier (Logan Lerman) in their midst, Wardaddy and his men face overwhelming odds as they move to strike at the heart of Nazi Germany.",
max: 4,
),
SizedBox(height: 10),
CardDetailsBottomTabs(
selectedTab: _selectedTab,
tabPressed: (num) {
_tabsPageController.animateToPage(num,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic);
},
),
// HERE I WANT TO USE EXPANDED WIDGET INSTEAD OF SIZEDBOX, BUT IT GIVES ME ERROR
SizedBox(
height: 300,
child: PageView(
controller: _tabsPageController,
onPageChanged: (num) {
setState(() {
_selectedTab = num;
});
},
children: const [
CardDetailsCast(),
CardDetailsPhoto(),
CardDetailsReview(),
],
),
),
],
),
),
],
),
);
}
}
this is all what I did so far, when I wrap pageview inside expanded widget it gives me error,
So is there any one who could find a way to wrap the pageview with expanded widget???
this is all what I did so far, when I wrap pageview inside expanded widget it gives me error,
So is there any one who could find a way to wrap the pageview with expanded widget???
this is all what I did so far, when I wrap pageview inside expanded widget it gives me error,
So is there any one who could find a way to wrap the pageview with expanded widget???

You can follow CustomScrollView with SliverAppBar. Example from SliverAppBar
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool _pinned = true;
bool _snap = false;
bool _floating = false;
// [SliverAppBar]s are typically used in [CustomScrollView.slivers], which in
// turn can be placed in a [Scaffold.body].
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: _pinned,
snap: _snap,
floating: _floating,
expandedHeight: 160.0,
flexibleSpace: const FlexibleSpaceBar(
title: Text('SliverAppBar'),
background: FlutterLogo(),
),
),
const SliverToBoxAdapter(
child: SizedBox(
height: 20,
child: Center(
child: Text('Scroll to see the SliverAppBar in effect.'),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: index.isOdd ? Colors.white : Colors.black12,
height: 100.0,
child: Center(
child: Text('$index', textScaleFactor: 5),
),
);
},
childCount: 20,
),
),
],
),
bottomNavigationBar: BottomAppBar(
child: Padding(
padding: const EdgeInsets.all(8),
child: OverflowBar(
overflowAlignment: OverflowBarAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('pinned'),
Switch(
onChanged: (bool val) {
setState(() {
_pinned = val;
});
},
value: _pinned,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('snap'),
Switch(
onChanged: (bool val) {
setState(() {
_snap = val;
// Snapping only applies when the app bar is floating.
_floating = _floating || _snap;
});
},
value: _snap,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('floating'),
Switch(
onChanged: (bool val) {
setState(() {
_floating = val;
_snap = _snap && _floating;
});
},
value: _floating,
),
],
),
],
),
),
),
);
}
}

Related

How to count the number of banners/pages for dot indicators? I'm using smooth_page_indicator package available on pub.dev

I have to return the number of pages in the PageView so that the value of 'count' in DotsAnimatedWidget->AnimatedSmoothIndicator automatically updates if the number of banners are increased or decreased. Is there any way to count the number of pages in the PageView or count the number of children? Should I approach a different way?
Link for the package used:
https://pub.dev/packages/smooth_page_indicator
class _BannerWidgetState extends State<BannerWidget> {
int _pages = 0;
final _controller = PageController();
#override
Widget build(BuildContext context) {
return Stack(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
height: 140,
width: MediaQuery.of(context).size.width,
color: Colors.white,
child: PageView(
controller: _controller,
onPageChanged: ((val) {
setState(() {
_pages = val.toInt();
});
}),
children: const [
Center(
child: Text(
'Banner 1',
style: TextStyle(fontSize: 20),
),
),
Center(
child: Text(
'Banner 2',
style: TextStyle(fontSize: 20),
),
),
Center(
child: Text(
'Banner 3',
style: TextStyle(fontSize: 20),
),
),
],
),
),
),
),
DotsIndicatorWidget(pages: _pages),
],
);
}
}
class DotsIndicatorWidget extends StatelessWidget {
const DotsIndicatorWidget({
Key? key,
required int pages,
}) : _pages = pages,
super(key: key);
final int _pages;
#override
Widget build(BuildContext context) {
return Positioned.fill(
bottom: 18,
child: Align(
alignment: Alignment.bottomCenter,
child: Row(
//mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedSmoothIndicator(
activeIndex: _pages,
count: 3,
effect: const WormEffect(
spacing: 4.0,
activeDotColor: Colors.green,
dotColor: Colors.grey,
dotHeight: 8.0,
dotWidth: 8.0,
radius: 3,
type: WormType.normal),
),
],
),
),
);
}
}
Create a variable for children of PageView
List<Widget> _pages = [some Container]
int _currentIndex = 0;
And update it on onPageChanged()
onPageChanged: ((page) {
setState(() {
_currentIndex = page.toInt();
});
}),
Count the number of pages in the PageView used: _pages.length
But I think, used package smooth_page_indicator is best way

How to fetch data (Text) from Website in Flutter / dart?

So, I'm pretty new on Flutter (just about a week) and dart but I hope this Question is not toooooo stupid:
I'm trying to create an App for an existing Website (a forum). I dont want to use the flutter WebView, because I want to create an comepletly new Interface (The Website is not optimized for mobile View, that's why I'm builing this App).
The (german) Forum:
https://www.porsche-914.com/forum
I want to fetch the single forum topics as well as the posts itself including username, title and date and then map the text to a custom widget to display it. The Widget Part is no problem, it's already done but I cant figure out how to fetch that data from the website and how to store it.
I don't know, hope you can help me...
Thanks a lot for the taken time.
How the mapping should work:
fetched[index].title, fetched[index].text
Center(
child: NeumorphismPost(
title: fetched[index].title,
titleColor: Theme.of(context).cardColor,
textColor: Theme.of(context).cardColor,
text: fetched[index].text,
width: 370,
onTap: () {
_popupPage(context,
title: fetched[index].title,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
fetched[index].text,
style: TextStyle(
color: Theme.of(context).cardColor,
fontSize: 18),
),
),
);
},
),
),
This is my custom Widget:
import 'package:flutter/material.dart';
class NeumorphismPost extends StatefulWidget {
final double width;
final double height;
final double borderRadius;
final String title;
final String text;
final Color titleColor;
final Color textColor;
final double titleSize;
final double textSize;
final Function onTap;
NeumorphismPost({
this.width = 185,
this.height = 185,
this.title = "Title",
this.text = "",
this.borderRadius = 20,
this.titleColor = const Color(0xFF424242),
this.textColor = const Color(0xFF424242),
this.titleSize = 22,
this.textSize = 18,
required this.onTap,
});
#override
State<NeumorphismPost> createState() => _NeumorphismPostState();
}
class _NeumorphismPostState extends State<NeumorphismPost> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
direction: Axis.vertical,
children: [
SingleChildScrollView(
child: SizedBox(
height: widget.height,
width: widget.width,
child: GestureDetector(
onTap: () {},
child: Scaffold(
backgroundColor: Colors.transparent,
body: SizedBox(
height: widget.height,
width: widget.width,
child: Center(
child: Container(
height: widget.height,
width: widget.width,
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius:
BorderRadius.circular(widget.borderRadius),
boxShadow: [
BoxShadow(
color: Theme.of(context).hintColor,
offset: const Offset(5, 5),
blurRadius: 15,
spreadRadius: 1,
),
BoxShadow(
color: Theme.of(context).backgroundColor,
offset: Offset(-5, -5),
blurRadius: 15,
spreadRadius: 1,
)
],
),
child: Wrap(
direction: Axis.horizontal,
children: [
SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Wrap(
direction: Axis.horizontal,
children: [
SizedBox(
height: widget.height / 3,
child: Wrap(
children: [
Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets
.symmetric(
horizontal: 15,
vertical: 15),
child: Text(
widget.title,
textAlign:
TextAlign.center,
overflow:
TextOverflow.fade,
style: TextStyle(
fontSize:
widget.titleSize,
fontWeight:
FontWeight.bold,
color:
widget.titleColor,
),
),
),
],
),
],
),
),
],
),
Wrap(
direction: Axis.horizontal,
children: [
GestureDetector(
onTap: () {
widget.onTap();
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 5),
child: SizedBox(
height: widget.height / 1.38,
child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 15,
vertical: 15),
child: Text(
widget.text,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: widget.textSize,
fontWeight:
FontWeight.normal,
color: widget.textColor,
),
),
),
),
),
),
],
),
],
),
),
),
],
),
),
),
),
),
),
),
),
],
),
);
}
}
From my point of view, you want to create a scraper. I checked the target website (https://www.porsche-914.com/forum), it can be scraped without any special technique (they don't have many Ajax calls (you may test by using Postman)). So it is a possible task. My suggestion flow is:
Load the raw HTML using any technique (http, dio, inappwebview ...)
Parse it using BeautifulSoup (https://pub.dev/packages/beautiful_soup_dart) (or any parser; it is just my favorite parser.)
Map it to your existing model.
Here is some example code. Hope it helps:
import 'package:http/http.dart' as http;
import 'package:beautiful_soup_dart/beautiful_soup.dart';
class TestParse {
excuteSample() async {
var url = Uri.parse('https://www.porsche-914.com/forum');
var response = await http.get(url);
BeautifulSoup bs = BeautifulSoup(response.body);
final allHeaderName = bs.findAll('td', attrs: {'class': 'oben'});
allHeaderName.forEach((element) {
print('the header: ${element.text}');
});
}
}
Here is the result:
The final result you need is a long story and long code. Hope this will give you a starting point.
UPDATE: I added the full demo code, using your requested code:
import 'package:beautiful_soup_dart/beautiful_soup.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(const MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final testparse = TestParse();
List<String> yourModelPleaseReplaceThis = [];
#override
void initState() {
super.initState();
excuteRequestAndParse();
}
excuteRequestAndParse() async {
final result =
await testparse.excuteSample(); //[1] i guess you missing this await
setState(() {
yourModelPleaseReplaceThis = result;
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Center(
child: NeumorphismPost(
title: yourModelPleaseReplaceThis[index],
titleColor: Theme.of(context).cardColor,
textColor: Theme.of(context).cardColor,
text: yourModelPleaseReplaceThis[index],
width: 370,
onTap: () {
//THIS CODE BELOW IS YOUR CODE....
},
),
);
},
itemCount: yourModelPleaseReplaceThis.length,
);
}
}
// BELOW IS TESTING CODE....
class TestParse {
Future<List<String>> excuteSample() async {
var url = Uri.parse('https://www.porsche-914.com/forum');
var response = await http.get(url);
BeautifulSoup bs = BeautifulSoup(response.body);
final allHeaderName = bs.findAll('td', attrs: {'class': 'oben'});
allHeaderName.forEach((element) {
print('the header: ${element.text}');
});
return Future.value(allHeaderName.map((e) => e.text).toList());
}
}
class NeumorphismPost extends StatefulWidget {
final double width;
final double height;
final double borderRadius;
final String title;
final String text;
final Color titleColor;
final Color textColor;
final double titleSize;
final double textSize;
final Function onTap;
NeumorphismPost({
this.width = 185,
this.height = 185,
this.title = "Title",
this.text = "",
this.borderRadius = 20,
this.titleColor = const Color(0xFF424242),
this.textColor = const Color(0xFF424242),
this.titleSize = 22,
this.textSize = 18,
required this.onTap,
});
#override
State<NeumorphismPost> createState() => _NeumorphismPostState();
}
class _NeumorphismPostState extends State<NeumorphismPost> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
direction: Axis.vertical,
children: [
SingleChildScrollView(
child: SizedBox(
height: widget.height,
width: widget.width,
child: GestureDetector(
onTap: () {},
child: Scaffold(
backgroundColor: Colors.transparent,
body: SizedBox(
height: widget.height,
width: widget.width,
child: Center(
child: Container(
height: widget.height,
width: widget.width,
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius:
BorderRadius.circular(widget.borderRadius),
boxShadow: [
BoxShadow(
color: Theme.of(context).hintColor,
offset: const Offset(5, 5),
blurRadius: 15,
spreadRadius: 1,
),
BoxShadow(
color: Theme.of(context).backgroundColor,
offset: Offset(-5, -5),
blurRadius: 15,
spreadRadius: 1,
)
],
),
child: Wrap(
direction: Axis.horizontal,
children: [
SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: <Widget>[
Wrap(
direction: Axis.horizontal,
children: [
SizedBox(
height: widget.height / 3,
child: Wrap(
children: [
Column(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets
.symmetric(
horizontal: 15,
vertical: 15),
child: Text(
widget.title,
textAlign:
TextAlign.center,
overflow:
TextOverflow.fade,
style: TextStyle(
fontSize:
widget.titleSize,
fontWeight:
FontWeight.bold,
color:
widget.titleColor,
),
),
),
],
),
],
),
),
],
),
Wrap(
direction: Axis.horizontal,
children: [
GestureDetector(
onTap: () {
widget.onTap();
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 5),
child: SizedBox(
height: widget.height / 1.38,
child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 15,
vertical: 15),
child: Text(
widget.text,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: widget.textSize,
fontWeight:
FontWeight.normal,
color: widget.textColor,
),
),
),
),
),
),
],
),
],
),
),
),
],
),
),
),
),
),
),
),
),
],
),
);
}
}
This is the result:
Please note:
This list I created is for sample, so it is just a list of Strings. You should create a complete Model.

How to make an ExpansionTile trailing Icon spin?

I've created an expansiontile and I've changed the original trailing icon to an svg icon which I customized. Anytime I click the expansion tile... everything works but the new svg trailing icon doesnt spin like the original trailing icon. Please how do I fix this?
code below
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class ExpansionTileCard extends StatelessWidget {
final String toptitle;
final List<Widget> children;
const ExpansionTileCard({Key key, this.toptitle, this.children})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
border:
new Border(bottom: new BorderSide(color: Colors.green))),
child: new Row(
children: [
new Expanded(
flex: 9,
child: new Container(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExpansionTile(
title: Text(
toptitle,
style: TextStyle(
color: Colors.black,
letterSpacing: -1,
fontSize: 19,
fontWeight: FontWeight.bold),
),
children: children,
childrenPadding: EdgeInsets.fromLTRB(0, 0, 0, 20),
iconColor: Colors.green,
trailing: SvgPicture.asset(
'assets/icons/textmessage.svg',
height: 30,
),
),
],
),
),
)
],
),
),
],
),
);
}
}
While we don't provide trailing on ExpansionTile it uses the default behavior of it.
We are passing a widget into trailing and we need to control it. In order to do that, we need to check if ExpansionTile is expanded or not, the onExpansionChanged call back will provide this value. To handle animation we can use many widgets like Transform, RotatedBox, AnimatedRotation... In this ExpansionTileCard widget will be extended from StatefulWidget .
Steps
convert to StatefulWidget
create a bool inside state class to check the Expanded-state.
bool _isExpanded = false;
change value on onExpansionChanged
onExpansionChanged: (value) {
setState(() {
_isExpanded = value;
});
},
assign your widget on trailing by wrapping animation widget or just check the state return two widget
_isExpanded? ExpandedWidget:NormalViewWidget()
trailing: AnimatedRotation( /// you can use different widget for animation
turns: _isExpanded ? .5 : 0,
duration: Duration(seconds: 1),
child: CustomWidget()// your svgImage here
),
Widget State
class _ExpansionTileCardState extends State<ExpansionTileCard> {
bool _isExpanded = false;
#override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.green))),
child: Row(
children: [
Expanded(
flex: 9,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExpansionTile(
onExpansionChanged: (value) {
setState(() {
_isExpanded = value;
});
},
title: Text(
widget.toptitle,
style: TextStyle(
color: Colors.black,
letterSpacing: -1,
fontSize: 19,
fontWeight: FontWeight.bold),
),
children: widget.children,
childrenPadding: EdgeInsets.fromLTRB(0, 0, 0, 20),
iconColor: Colors.green,
trailing: AnimatedRotation(
turns: _isExpanded ? .5 : 0,
duration: Duration(seconds: 1),
child: SvgPicture.asset(
'assets/icons/textmessage.svg',
height: 30,
),
),
],
),
),
)
],
),
),
],
),
);
}
}

Is it possible to update PopupMenuButton items while it is still open?

Hello to everyone reading this,
So i've been trying to get a PopupMenuButton to change the current selected tile while it is still open. I'm using it in a drawer so a user can easily change their status. I've trimmed my code a bit to provide some basic replicable code. It uses a cubit to manage the users 'status'. The status does update, but the selection in the PopupMenuButton does not. Is there an easy way to fix this?
This is how it looks now:
status change
I've already tried:
Wrapping the child of the PopupMenuItem in a StatefulBuilder and making the PopupMenuButton a stateful widget
Wrapping the child of the PopupMenuItem in a BlocConsumer
Wrapping the child of the PopupMenuItem in a container and giving it a color based the cubit status
Giving the PopupMenuItem a ListTile as a child and setting its selected property based on the cubit status
Making the PopupMenuButton a stateful widget and managing the status using setState
This is my (simplified) code:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DrawerStatusTestScaff extends StatelessWidget {
const DrawerStatusTestScaff({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => UserStatusCubit(),
child: Scaffold(
appBar: AppBar(),
drawer: MenuDrawer(),
),
);
}
}
class MenuDrawer extends StatelessWidget {
const MenuDrawer({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final double _headerHeight = kToolbarHeight +
((3 * (kToolbarHeight - 20)) + 40) +
MediaQuery.of(context).padding.top;
void _toSettings() {
print('Settings pressed');
Navigator.pop(context);
}
return Container(
width: 305,
child: Drawer(
child: Column(
children: [
SizedBox(
height: _headerHeight,
child: UserAccountsDrawerHeader(
margin: const EdgeInsets.all(0),
currentAccountPicture: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFFFFFFF),
),
child: Center(
child: const Text('S.P',
style: TextStyle(
fontSize: 30,
color: const Color(0xFF03A9F4),
)),
),
),
accountName: const Text('S.O.M.E Person'),
accountEmail: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
StatusPopUp(
child: Row(
children: [
const Text('s.person#companymmail.com'),
Padding(
padding: const EdgeInsets.fromLTRB(15, 2, 0, 0),
child: Icon(
Icons.circle,
color: context.watch<UserStatusCubit>().state == 0
? const Color(0xFF00E676)
: context.watch<UserStatusCubit>().state == 1
? const Color(0xFFFFEE58)
: context
.watch<UserStatusCubit>()
.state ==
2
? const Color(0xFFE53935)
: const Color(0xFFBDBDBD),
size: 10,
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(right: 15),
child: Tooltip(
message: 'Settings',
child: InkWell(
onTap: _toSettings,
child: const Icon(
Icons.settings,
size: 25,
),
),
),
),
],
),
decoration: BoxDecoration(
color: const Color(0xFF03A9F4),
),
),
),
Expanded(
child: ListView(),
),
],
),
),
);
}
}
class StatusPopUp extends StatelessWidget {
final Widget child;
const StatusPopUp({
Key? key,
required this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final int _dropDownIndex = context.watch<UserStatusCubit>().state;
return PopupMenuButton<int>(
tooltip: 'Change Status',
offset: Offset(0, 51.5 + (_dropDownIndex.toDouble() * 48.5)),
onSelected: (int index) {
context.read<UserStatusCubit>().emit(index);
},
initialValue: _dropDownIndex,
child: child,
itemBuilder: (context) => <PopupMenuEntry<int>>[
PopupMenuItem<int>(
value: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Online'),
Padding(
padding: const EdgeInsets.fromLTRB(15, 2, 0, 0),
child: const Icon(
Icons.circle,
color: const Color(0xFF00E676),
size: 10,
),
),
],
),
),
PopupMenuItem<int>(
value: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Busy'),
Padding(
padding: const EdgeInsets.fromLTRB(15, 2, 0, 0),
child: const Icon(
Icons.circle,
color: const Color(0xFFFFEE58),
size: 10,
),
),
],
),
),
PopupMenuItem<int>(
value: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Away'),
Padding(
padding: const EdgeInsets.fromLTRB(15, 2, 0, 0),
child: const Icon(
Icons.circle,
color: const Color(0xFFE53935),
size: 10,
),
),
],
),
),
PopupMenuItem<int>(
value: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Offline'),
Padding(
padding: const EdgeInsets.fromLTRB(15, 2, 0, 0),
child: const Icon(
Icons.circle,
color: const Color(0xFFBDBDBD),
size: 10,
),
),
],
),
),
],
);
}
}
class UserStatusCubit extends Cubit<int> {
UserStatusCubit() : super(0);
}

Flutter - How to make a custom TabBar

This is the output that I want. I am still new in flutter so can anyone let me know if there is already a widget for this kind of switch or how should I make one ??
Also, I want the data shown below this button to change if I choose the other button but I guess that's obvious.
Thanks in advance.
You can use the TabBar widget to achieve this. I added a full example demonstrating how you can create this using the TabBar widget:
CODE
class StackOver extends StatefulWidget {
#override
_StackOverState createState() => _StackOverState();
}
class _StackOverState extends State<StackOver>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Tab bar',
),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
// give the tab bar a height [can change hheight to preferred height]
Container(
height: 45,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(
25.0,
),
),
child: TabBar(
controller: _tabController,
// give the indicator a decoration (color and border radius)
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
25.0,
),
color: Colors.green,
),
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
tabs: [
// first tab [you can add an icon using the icon property]
Tab(
text: 'Place Bid',
),
// second tab [you can add an icon using the icon property]
Tab(
text: 'Buy Now',
),
],
),
),
// tab bar view here
Expanded(
child: TabBarView(
controller: _tabController,
children: [
// first tab bar view widget
Center(
child: Text(
'Place Bid',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
// second tab bar view widget
Center(
child: Text(
'Buy Now',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
),
);
}
}
OUTPUT
Try out this you have to change some colour and font:-
import 'package:flutter/material.dart';
typedef SwitchOnChange = Function(int);
class CustomSwitch extends StatefulWidget {
SwitchOnChange onChange;
CustomSwitch({this.onChange});
#override
State<StatefulWidget> createState() {
return CustomSwitchState();
}
}
class CustomSwitchState extends State<CustomSwitch>
with TickerProviderStateMixin {
AnimationController controller;
Animation animation;
GlobalKey key = GlobalKey();
#override
void initState() {
Future.delayed(Duration(milliseconds: 100)).then((v) {
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 300));
tabWidth = key.currentContext.size.width / 2;
// var width = (media.size.width - (2 * media.padding.left)) / 2;
animation = Tween<double>(begin: 0, end: tabWidth).animate(controller);
setState(() {});
controller.addListener(() {
setState(() {});
});
});
super.initState();
}
var selectedValue = 0;
double tabWidth = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
selectedValue == 0 ? this.controller.forward() : controller.reverse();
selectedValue = selectedValue == 0 ? 1 : 0;
},
child: Container(
key: key,
height: 44,
decoration: BoxDecoration(
color: Colors.grey, borderRadius: BorderRadius.circular(22)),
child: Stack(
children: <Widget>[
Row(
children: <Widget>[
Transform.translate(
offset: Offset(animation?.value ?? 0, 0),
child: Container(
height: 44,
width: tabWidth,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(color: Colors.grey, blurRadius: 3),
]),
),
),
],
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: tabWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.directions_walk),
SizedBox(width: 5),
Text("Place Bid")
],
),
),
Container(
width: tabWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.directions_walk),
SizedBox(width: 5),
Text("Buy now")
],
),
)
],
),
),
],
),
),
);
}
}
The following is my workaround, which I believe to be the best method.
import 'package:flutter/material.dart';
class SettingsScreen extends StatelessWidget {
const SettingsScreen({
super.key,
});
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Settings'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(AppBar().preferredSize.height),
child: Container(
height: 50,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
10,
),
color: Colors.grey[200],
),
child: TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
10,
),
color: Colors.pink,
),
tabs: const [
Tab(
text: 'Basic',
),
Tab(
text: 'Advanced',
)
],
),
),
),
),
),
body: const TabBarView(
children: [
Center(
child: Text(
'Basic Settings',
style: TextStyle(
fontSize: 30,
),
),
),
Center(
child: Text(
'Advanced Settings',
style: TextStyle(
fontSize: 30,
),
),
),
],
),
),
);
}
}
You can use also PageView widget.
const double borderRadius = 25.0;
class CustomSwitchState extends StatefulWidget {
#override
_CustomSwitchStateState createState() => _CustomSwitchStateState();
}
class _CustomSwitchStateState extends State<CustomSwitchState> with SingleTickerProviderStateMixin {
PageController _pageController;
int activePageIndex = 0;
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: _menuBar(context),
),
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
physics: const ClampingScrollPhysics(),
onPageChanged: (int i) {
FocusScope.of(context).requestFocus(FocusNode());
setState(() {
activePageIndex = i;
});
},
children: <Widget>[
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Center(child: Text("Place Bid"),),
),
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Center(child: Text("Buy Now"),),
),
],
),
),
],
),
),
),
));
}
Widget _menuBar(BuildContext context) {
return Container(
width: 300.0,
height: 50.0,
decoration: const BoxDecoration(
color: Color(0XFFE0E0E0),
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
onTap: _onPlaceBidButtonPress,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
decoration: (activePageIndex == 0) ? const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
) : null,
child: Text(
"Place Bid",
style: (activePageIndex == 0) ? TextStyle(color: Colors.white) : TextStyle(color: Colors.black),
),
),
),
),
Expanded(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
onTap: _onBuyNowButtonPress,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
decoration: (activePageIndex == 1) ? const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
) : null,
child: Text(
"Buy Now",
style: (activePageIndex == 1) ? TextStyle(color: Colors.white, fontWeight: FontWeight.bold) : TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
),
),
),
),
],
),
);
}
void _onPlaceBidButtonPress() {
_pageController.animateToPage(0,
duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
}
void _onBuyNowButtonPress() {
_pageController.animateToPage(1,
duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
}
}
OUTPUT
If you want tab layout like this you can use this
Output:
import 'package:flutter/material.dart';
import 'package:icons_helper/icons_helper.dart';
class DetailScreen extends StatefulWidget {
var body;
String title = "";
DetailScreen(this.body, this.title);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<DetailScreen> with TickerProviderStateMixin {
late int _startingTabCount;
List<Tab> _tabs = <Tab>[];
List<Widget> _generalWidgets = <Widget>[];
late TabController _tabController;
#override
void initState() {
_startingTabCount = widget.body["related_modules"].length;
_tabs = getTabs(_startingTabCount);
_tabController = getTabController();
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
bottom: TabBar(
isScrollable: true,
tabs: _tabs,
controller: _tabController,
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.grey,
Colors.blue,
],
stops: [0.3, 1.0],
),
),
),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(context);
},
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.skip_previous),
color: Colors.white,
onPressed: () {
goToPreviousPage();
},
),
Container(
margin: EdgeInsets.only(right: 15),
child: IconButton(
icon: Icon(Icons.skip_next),
color: Colors.white,
onPressed: () {
goToNextPage();
},
),
)
],
),
body: Column(
children: <Widget>[
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: _tabController,
children: getWidgets(),
),
),
],
),
);
}
TabController getTabController() {
return TabController(length: _tabs.length, vsync: this)
..addListener(_updatePage);
}
Tab getTab(int widgetNumber) {
return Tab(
icon: Column(
children: [
if (widget.body["related_modules"][widgetNumber]["icon"].toString() ==
"fa-comments-o") ...[
Icon(
Icons.comment_outlined,
),
] else if (widget.body["related_modules"][widgetNumber]["icon"]
.toString() ==
"fa-map-marker") ...[
Icon(
Icons.location_on_rounded,
),
] else if (widget.body["related_modules"][widgetNumber]["icon"]
.toString() ==
"fa-address-card") ...[
Icon(
Icons.contact_page_sharp,
),
] else ...[
Icon(
getIconUsingPrefix(
name: widget.body["related_modules"][widgetNumber]["icon"]
.toString()
.substring(3),
),
)
]
],
),
text: widget.body["related_modules"][widgetNumber]["label"].toString(),
);
}
Widget getWidget(int widgetNumber) {
return Center(
child: Text("Widget nr: $widgetNumber"),
);
}
List<Tab> getTabs(int count) {
_tabs.clear();
for (int i = 0; i < count; i++) {
_tabs.add(getTab(i));
}
return _tabs;
}
List<Widget> getWidgets() {
_generalWidgets.clear();
for (int i = 0; i < _tabs.length; i++) {
_generalWidgets.add(getWidget(i));
}
return _generalWidgets;
}
void _updatePage() {
setState(() {});
}
//Tab helpers
bool isFirstPage() {
return _tabController.index == 0;
}
bool isLastPage() {
return _tabController.index == _tabController.length - 1;
}
void goToPreviousPage() {
_tabController.animateTo(_tabController.index - 1);
}
void goToNextPage() {
isLastPage()
? showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("End reached"),
content: Text("This is the last page.")))
: _tabController.animateTo(_tabController.index + 1);
}
}