I stumbled upon this design in dribble
and in trying to implement it in flutter I was able to create the curves using clip Path. this is what I have
I am trying to get rid of the space in between the shapes so that they can collapse.
this is my CurvedRectangleClipper:`
import 'package:flutter/material.dart';
class CurvedRectangleClipper extends CustomClipper<Path> {
final double offset = 80;
#override
Path getClip(Size size) {
// TODO: implement getClip
Path path = Path();
path.lineTo(0, size.height - offset);
var firstEndpoint = Offset(offset, size.height);
path.arcToPoint(firstEndpoint, radius: Radius.circular(-offset),clockwise: false);
path.lineTo(size.width, size.height);
path.lineTo(size.width, offset);
path.lineTo(offset, offset);
var secondEndPoint = Offset(0,0);
path.arcToPoint(secondEndPoint, radius: Radius.circular(-offset),clockwise: true);
path.lineTo(0, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
and this is my main.dart file:
import 'package:devotion/CurvedRectangleClipper.dart';
import 'package:flutter/material.dart';
void main() {
runApp(HomeScreen());
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainScreen(),
title: 'Devotion',
theme: appTheme,
);
}
}
var appTheme =
ThemeData(fontFamily: 'Oxygen', primaryColor: Colors.purpleAccent);
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Devotion'),
),
body: ListView(
scrollDirection: Axis.vertical,
children: <Widget>[
CurvedListItem(
title: 'Yoga and Meditation for Beginners',
time: 'TODAY 5:30 PM'),
CurvedListItem(
title: 'Practice French, English And Chinese',
time: 'TUESDAY 5:30 PM',
),
CurvedListItem(
title: 'Adobe XD Live Event in Europe',
time: 'FRIDAY 6:00 PM',
),
],
),
);
}
}
class CurvedListItem extends StatelessWidget {
final String title;
final String time;
final String people;
final IconData icon;
CurvedListItem({this.title, this.time, this.icon, this.people});
#override
Widget build(BuildContext context) {
return ClipPath(
clipper: CurvedRectangleClipper(),
child: Container(
color: Colors.pink,
padding: EdgeInsets.only(
left: 32,
top: 100,
bottom: 50,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
time,
style: TextStyle(color: Colors.white, fontSize: 12),
),
SizedBox(
height: 2,
),
Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold),
),
Row(),
]),
),
);
}
}
`
I guess the top curve is not needed if the items are placed on each other but I left it there to know the actual shape.
Corrections and comments are also welcome. Thanks in advance.
AFAIK, you cannot get rid of the space between the clipped widgets. There is a easy and different approach you can take to solve this.
Here is how I would have done it:
void main() {
runApp(HomeScreen());
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainScreen(),
title: 'Devotion',
theme: appTheme,
);
}
}
ThemeData appTheme = ThemeData(
fontFamily: 'Oxygen',
primaryColor: Colors.purpleAccent,
);
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Devotion'),
),
body: ListView(
scrollDirection: Axis.vertical,
children: <Widget>[
CurvedListItem(
title: 'Yoga and Meditation for Beginners',
time: 'TODAY 5:30 PM',
color: Colors.red,
nextColor: Colors.green,
),
CurvedListItem(
title: 'Practice French, English And Chinese',
time: 'TUESDAY 5:30 PM',
color: Colors.green,
nextColor: Colors.yellow,
),
CurvedListItem(
title: 'Adobe XD Live Event in Europe',
time: 'FRIDAY 6:00 PM',
color: Colors.yellow,
),
],
),
);
}
}
class CurvedListItem extends StatelessWidget {
const CurvedListItem({
this.title,
this.time,
this.icon,
this.people,
this.color,
this.nextColor,
});
final String title;
final String time;
final String people;
final IconData icon;
final Color color;
final Color nextColor;
#override
Widget build(BuildContext context) {
return Container(
color: nextColor,
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(80.0),
),
),
padding: const EdgeInsets.only(
left: 32,
top: 80.0,
bottom: 50,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
time,
style: TextStyle(color: Colors.white, fontSize: 12),
),
const SizedBox(
height: 2,
),
Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold),
),
Row(),
]),
),
);
}
}
Note: I don't claim it to be optimal, but still easy and neat.
Hope that helped!
Use Align and set heightFactor: 0.75 and alignment: Alignment.topCenter,
ListView(
children: [
Align(
heightFactor: 0.75,
alignment: Alignment.topCenter,
child: CurvedListItem(
title: 'Yoga and Meditation for Beginners',
time: 'TODAY 5:30 PM',
),
),
Align(
heightFactor: 0.75,
alignment: Alignment.topCenter,
child: CurvedListItem(
title: 'Practice French, English And Chinese',
time: 'TUESDAY 5:30 PM',
),
),
Align(
heightFactor: 0.75,
alignment: Alignment.topCenter,
child: CurvedListItem(
title: 'Adobe XD Live Event in Europe',
time: 'FRIDAY 6:00 PM',
),
)
],
);
Related
I am developing new application in flutter. This application works well in android simulator, android real device and in iOS simulator. In iOS real device application works well in first launch but when i restart the app it shows only the textfield. Please help me to solve this issue.
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:moonlight/screens/sign_in.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Moonlight',
theme: ThemeData(fontFamily: 'Prata'),
home: SignInScreen(),
);
}
}
sign_in.dart:
import 'package:flutter/material.dart';
import 'package:moonlight/screens/sign_up.dart';
import 'package:moonlight/utils/constants.dart';
import 'package:moonlight/widget/textButton.dart';
import 'package:moonlight/widget/textFiled.dart';
import '../services/dimensions.dart';
class SignInScreen extends StatefulWidget {
const SignInScreen({Key? key}) : super(key: key);
#override
State<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
final TextEditingController emailController = new
TextEditingController();
final TextEditingController passwordController = new
TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize:
Size.fromHeight(Dimensions().getResponsiveSize(170)),
child: Container(
color: appBarColor,
child: AppBar(
backgroundColor: appBarColor,
toolbarHeight: Dimensions().getResponsiveSize(170),
centerTitle: true,
title: Column(
children: [
Container(
//color: Colors.red,
width: Dimensions().getResponsiveSize(200),
height: Dimensions().getResponsiveSize(40),
//width: 200,
//color: Colors.red,
child: Image.asset(
logoUrl,
fit: BoxFit.fitHeight,
//height: Dimensions().getResponsiveSize(50),
//width: Dimensions().getResponsiveSize(200),
//width: 50,
),
),
SizedBox(height:
Dimensions().getResponsiveSize(20),),
Text('Sign In',
style: TextStyle(
fontSize: Dimensions().getResponsiveSize(25),
fontWeight: FontWeight.bold
),
),
SizedBox(height: Dimensions().getResponsiveSize(5),),
Text('Welcome',
style: TextStyle(
fontSize: Dimensions().getResponsiveSize(15),
),
),
],
),
),
)),
bottomNavigationBar: GestureDetector(
onTap: () {
},
child: Container(
color: appBarColor,
height: Dimensions().getResponsiveSize(50),
child: Center(
child: Text(
'Go',
style: TextStyle(color: Colors.white, fontSize:
Dimensions().getResponsiveSize(15),
fontWeight: FontWeight.bold
),
)),
),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal:
Dimensions().getResponsiveSize(20)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextFieldInput(
controller: emailController,
icon: Icons.email,
text: 'Email',
),
SizedBox(
height: Dimensions().getResponsiveSize(10),
),
TextFieldInput(
controller: passwordController,
icon: Icons.lock_rounded,
text: 'Password',
isObscure: true,
),
Row(
children: [
Spacer(),
TextButtonWidget(
onPressed:forgotPassword,
text: 'Forgot Password?',
size: Dimensions().getResponsiveSize(15),
color: appBarColor,
),
],
),
SizedBox(
height: Dimensions().getResponsiveSize(20),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
color: Colors.grey,
width: Dimensions().getResponsiveSize(70),
height: 1.2,
),
SizedBox(
width: Dimensions().getResponsiveSize(10),
),
Text(
'OR',
style: TextStyle(color: Colors.grey,
fontSize: Dimensions().getResponsiveSize(15)
),
),
SizedBox(
width: Dimensions().getResponsiveSize(10),
),
Container(
color: Colors.grey,
width: Dimensions().getResponsiveSize(70),
height: 1.2,
),
],
),
SizedBox(
height: Dimensions().getResponsiveSize(20),
),
TextButtonWidget(
onPressed: signUpScreen,
text: 'Sign Up',
size: Dimensions().getResponsiveSize(15),
color: appBarColor,
),
],
),
),
);
}
forgotPassword(){
}
signUpScreen(){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SignUpScreen()),
);
}
}
dimensions.dart
import 'package:get/get.dart';
class Dimensions{
static double shortestSide = Get.size.shortestSide;
getResponsiveSize(dynamic size) {
dynamic x = (size/392.72727272727275) * 100;
return (shortestSide*x)/100;
}
}
first of all, i do apologise if i am not describing the problem as thorough or as easy to understand as possible, forgive me as i just started learning how to build flutter chat app. i have the code below and i have integrated with firebase. i also enabled phone sign-in method and have input phone number for testing. what i am facing is that my app is able to bypass the registration screen and verification screen even though i have provided the wrong phone number and verification code. i have attached my code below and do appreciate all the help i can get.
// ignore_for_file: deprecated_member_use
import 'package:country_pickers/country.dart';
import 'package:country_pickers/country_pickers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:influential_brands_club/colors.dart';
import 'package:influential_brands_club/presentation/bloc/auth/auth_cubit.dart';
import 'package:influential_brands_club/presentation/bloc/phone_auth/phone_auth_cubit.dart';
import 'package:influential_brands_club/presentation/pages/phone_verification_page.dart';
import 'package:influential_brands_club/presentation/pages/set_initial_profile_page.dart';
import 'package:influential_brands_club/presentation/screens/home_screen.dart';
import 'package:influential_brands_club/presentation/widgets/theme/style.dart';
class RegistrationScreen extends StatefulWidget {
const RegistrationScreen({Key? key}) : super(key: key);
#override
State<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
static Country _selectedFilteredDialogCountry =
CountryPickerUtils.getCountryByPhoneCode("65");
String _countryCode = _selectedFilteredDialogCountry.phoneCode;
String _phoneNumber = "";
final TextEditingController _phoneAuthController = TextEditingController();
#override
void dispose() {
_phoneAuthController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<PhoneAuthCubit, PhoneAuthState>(
listener: (context, phoneAuthState) {
if (phoneAuthState is PhoneAuthSuccess) {
BlocProvider.of<AuthCubit>(context).loggedIn();
}
if (phoneAuthState is PhoneAuthFailure) {
Scaffold.of(context).showSnackBar(SnackBar(
backgroundColor: Colors.red,
content: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text("Invalid Code"),
Icon(Icons.error_outline)
],
),
),
));
}
}, builder: (context, phoneAuthState) {
if (phoneAuthState is PhoneAuthSmsCodeReceived) {
return PhoneVerificationPage(
phoneNumber: _phoneNumber,
);
}
if (phoneAuthState is PhoneAuthProfileInfo) {
return SetInitialProfileWidget(phoneNumber: _phoneNumber);
}
if (phoneAuthState is PhoneAuthLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
if (phoneAuthState is PhoneAuthSuccess) {
return BlocBuilder<AuthCubit, AuthState>(
builder: (context, authState) {
if (authState is Authenticated) {
return HomeScreen(
uid: authState.uid,
);
}
return Container();
});
}
return _bodyWidget();
}),
);
}
Widget _bodyWidget() {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 60),
child: Column(children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const <Widget>[
Text(""),
Text("Verify your phone number",
style: TextStyle(
fontSize: 18,
color: yellowColor,
fontWeight: FontWeight.w500)),
Icon(
Icons.more_vert,
color: Colors.white,
)
],
),
const SizedBox(
height: 30,
),
const Text(
"IB Club will send an SMS message (carrier charges may apply) to verify your phone number. Enter your country code and phone number below:",
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
const SizedBox(
height: 30,
),
ListTile(
tileColor: primaryColor,
onTap: _openFilteredCountryPickerDialog,
title: _buildDialogItem(_selectedFilteredDialogCountry),
),
const SizedBox(
height: 8,
),
Row(
children: <Widget>[
Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.5,
color: yellowColor,
))),
width: 80,
height: 42,
alignment: Alignment.center,
child: Text(_selectedFilteredDialogCountry.phoneCode,
style: const TextStyle(color: Colors.white)),
),
const SizedBox(
width: 12,
),
Expanded(
child: SizedBox(
height: 40,
child: TextField(
controller: _phoneAuthController,
decoration: const InputDecoration(
hintText: "Phone Number",
hintStyle: TextStyle(color: Colors.white54),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: darkPrimaryColor),
)),
cursorColor: Colors.white,
style: const TextStyle(color: Colors.white)),
),
),
],
),
const SizedBox(
width: 8,
),
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: MaterialButton(
color: yellowColor,
onPressed: _submitVerifyPhoneNumber,
child: const Text(
"Next",
style: TextStyle(
fontSize: 18,
color: Colors.black,
),
),
),
),
)
])));
}
void _openFilteredCountryPickerDialog() {
showDialog(
context: context,
builder: (_) => Theme(
data: Theme.of(context).copyWith(
primaryColor: primaryColor,
),
child: CountryPickerDialog(
titlePadding: const EdgeInsets.all(8.0),
searchCursorColor: Colors.black,
searchInputDecoration: const InputDecoration(
hintText: "Search",
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: darkPrimaryColor),
)),
isSearchable: true,
title: const Text("Select your phone code",
style: TextStyle(color: yellowColor)),
onValuePicked: (Country country) {
setState(() {
_selectedFilteredDialogCountry = country;
_countryCode = country.phoneCode;
});
},
itemBuilder: _buildDialogItem,
),
));
}
Widget _buildDialogItem(Country country) {
return Container(
height: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: primaryBlack, width: 1),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
CountryPickerUtils.getDefaultFlagImage(country),
const SizedBox(
height: 8.0,
),
Text(" +${country.phoneCode} "),
const SizedBox(
height: 8.0,
),
SizedBox(width: 155, child: Text(country.name)),
const Spacer(),
const Icon(Icons.arrow_drop_down)
],
),
);
}
void _submitVerifyPhoneNumber() {
if (_phoneAuthController.text.isNotEmpty) {
_phoneNumber = "+$_countryCode${_phoneAuthController.text}";
BlocProvider.of<PhoneAuthCubit>(context).submitVerifyPhoneNumber(
phoneNumber: _phoneNumber,
);
}
}
}
I want to have a custom sliver appBar with a search bar in it. I made a normal app bar that looks like this : But I want that when we scroll down, the app bar looks like that :
Actually, the code of the normal app bar is just a green AppBar of elevation: 0 and just below I add my Header(). Here's the code of my Header :
class Header extends StatefulWidget {
String title;
IconData icon;
Header({#required this.title, #required this.icon});
#override
_HeaderState createState() => _HeaderState();
}
class _HeaderState extends State<Header> {
TextEditingController _editingController;
#override
void initState() {
super.initState();
_editingController = TextEditingController();
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return PreferredSize(
preferredSize: size,
child: Container(
margin: EdgeInsets.only(bottom: kDefaultPadding * 2.5),
height: size.height*0.2,
child: Stack(
children: [
Container(
height: size.height*0.2-27,
width: size.width,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(36),
bottomRight: Radius.circular(36),
)
),
child: Align(
alignment: Alignment.topCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.title, style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.white, fontWeight: FontWeight.bold)),
SizedBox(width: 20,),
Icon(widget.icon, size: 40, color: Colors.white,)
],
)),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: kDefaultPadding),
padding: EdgeInsets.symmetric(horizontal: kDefaultPadding),
height: 54,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [BoxShadow(
offset: Offset(0, 10),
blurRadius: 50,
color: Theme.of(context).primaryColor.withOpacity(0.23),
)]
),
child: Row(
children: [
Expanded(
child: TextField(
controller: _editingController,
textAlignVertical: TextAlignVertical.center,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
hintText: 'Search',
hintStyle: TextStyle(color: Theme.of(context).primaryColor.withOpacity(0.5)),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
),
),
_editingController.text.trim().isEmpty ? IconButton(
icon: Icon(Icons.search, color: Theme.of(context).primaryColor.withOpacity(0.5)),
onPressed: null) :
IconButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
icon: Icon(Icons.clear, color: Theme.of(context).primaryColor.withOpacity(0.5)),
onPressed: () => setState(() {
_editingController.clear();
})),
],
),
),
)
],
),
),
);
}
#override
void dispose() {
_editingController.dispose();
super.dispose();
}
}
Any help to build this is welcome.
I've made a simple example to show the main logic.
Create your own SliverPersistentHeaderDelegate and calculate shrinkFactor.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white70,
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
floating: false,
delegate: SearchHeader(
icon: Icons.terrain,
title: 'Trees',
search: _Search(),
),
),
SliverFillRemaining(
hasScrollBody: true,
child: ListView(
physics: NeverScrollableScrollPhysics(),
children: [
Text('some text'),
Placeholder(
color: Colors.red,
fallbackHeight: 200,
),
Container(
color: Colors.blueGrey,
height: 500,
)
],
),
)
],
),
);
}
}
class _Search extends StatefulWidget {
_Search({Key key}) : super(key: key);
#override
__SearchState createState() => __SearchState();
}
class __SearchState extends State<_Search> {
TextEditingController _editingController;
#override
void initState() {
super.initState();
_editingController = TextEditingController();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 20, right: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: TextField(
controller: _editingController,
// textAlignVertical: TextAlignVertical.center,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
hintText: 'Search',
hintStyle: TextStyle(
color: Theme.of(context).primaryColor.withOpacity(0.5)),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
),
),
_editingController.text.trim().isEmpty
? IconButton(
icon: Icon(Icons.search,
color: Theme.of(context).primaryColor.withOpacity(0.5)),
onPressed: null)
: IconButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
icon: Icon(Icons.clear,
color: Theme.of(context).primaryColor.withOpacity(0.5)),
onPressed: () => setState(
() {
_editingController.clear();
},
),
),
],
),
);
}
}
class SearchHeader extends SliverPersistentHeaderDelegate {
final double minTopBarHeight = 100;
final double maxTopBarHeight = 200;
final String title;
final IconData icon;
final Widget search;
SearchHeader({
#required this.title,
this.icon,
this.search,
});
#override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
var shrinkFactor = min(1, shrinkOffset / (maxExtent - minExtent));
var topBar = Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
alignment: Alignment.center,
height:
max(maxTopBarHeight * (1 - shrinkFactor * 1.45), minTopBarHeight),
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(title,
style: Theme.of(context).textTheme.headline4.copyWith(
color: Colors.white, fontWeight: FontWeight.bold)),
SizedBox(
width: 20,
),
Icon(
icon,
size: 40,
color: Colors.white,
)
],
),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(36),
bottomRight: Radius.circular(36),
)),
),
);
return Container(
height: max(maxExtent - shrinkOffset, minExtent),
child: Stack(
fit: StackFit.loose,
children: [
if (shrinkFactor <= 0.5) topBar,
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: Container(
alignment: Alignment.center,
child: search,
width: 200,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0, 10),
blurRadius: 10,
color: Colors.green.withOpacity(0.23),
)
]),
),
),
),
if (shrinkFactor > 0.5) topBar,
],
),
);
}
#override
double get maxExtent => 230;
#override
double get minExtent => 100;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}
I want to create a widget that will build describe in this photo
I already created the meter bar but I still don't know how to add numbers from start to end of bar and a arrow in bottom where place what points you have and with same color above of it
child: Row(children: <Widget>[
Column(children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
child: Container(
decoration: new BoxDecoration(
border: new Border(right: BorderSide(color: Colors.black))
),
child: Column(
children: <Widget>[
Text('Points'),
Text('38'),
],
),
),
),
],),
// green bar
Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(right:10.0),
child: Container(
width:ratewidth,
decoration: new BoxDecoration(
border: new Border(bottom: BorderSide(color: Colors.green, width: 5.0))
),
),
)
],),
//yellow bar
Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(right:10.0),
child: Container(
width:ratewidth,
decoration: new BoxDecoration(
border: new Border(bottom: BorderSide(color: Colors.yellow, width: 5.0))
),
),
),
],),
...
],)
With a combination of Row, Column and Align it can be done in a few lines.
The hardest part is actually the triangle. Usually, you'll want to use CustomPainter for the triangle, but I was lazy here. So I combined translation, rotation, and a clip.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome(),
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: ScoreMeter(
score: 1,
),
)
],
),
);
}
}
class ScoreMeter extends StatelessWidget {
final int score;
ScoreMeter(
{
this.score,
Key key})
: super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: 100.0,
child: Row(
children: <Widget>[
Expanded(
child: ScoreMeterItem(
score: score, color: Colors.green, minRange: 0, maxRange: 50),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.yellow,
minRange: 51,
maxRange: 100),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.orange,
minRange: 101,
maxRange: 150),
),
Expanded(
child: ScoreMeterItem(
score: score, color: Colors.red, minRange: 151, maxRange: 200),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.purple,
minRange: 201,
maxRange: 250),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.brown,
minRange: 251,
maxRange: 300),
),
],
),
);
}
}
class ScoreMeterItem extends StatelessWidget {
/// Hello World
final int score;
final Color color;
final int minRange;
final int maxRange;
ScoreMeterItem(
{this.score,
this.color = Colors.grey,
#required this.minRange,
#required this.maxRange,
Key key})
: assert(minRange != null),
assert(maxRange != null),
super(key: key);
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(minRange.toString(), style: theme.textTheme.caption),
Text(maxRange.toString(), style: theme.textTheme.caption),
],
),
ScoreMeterBar(color: color),
score >= minRange && score <= maxRange
? SizedBox(
height: 10.0,
child: Align(
alignment: Alignment(
(score - minRange) / (maxRange - minRange) * 2 - 1,
0.0),
child: Arrow(color: color),
),
)
: SizedBox()
],
),
);
}
}
class Arrow extends StatelessWidget {
final Color color;
Arrow({this.color});
#override
Widget build(BuildContext context) {
return SizedBox(
height: 5.0,
width: 10.0,
child: ClipRect(
child: OverflowBox(
maxWidth: 10.0,
maxHeight: 10.0,
child: Align(
alignment: Alignment.topCenter,
child: Transform.translate(
offset: Offset(.0, 5.0),
child: Transform.rotate(
angle: pi / 4,
child: Container(
width: 10.0,
height: 10.0,
color: color,
),
),
),
),
),
),
);
}
}
class ScoreMeterBar extends StatelessWidget {
final Color color;
ScoreMeterBar({this.color = Colors.grey, Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 8.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
),
color: color,
),
);
}
}
I have looked through the Flutter documentation to try and find an event, callback or even a state that I could hook into when the FlexibleSpaceBar is collapsed or expanded.
return new FlexibleSpaceBar(
title: new Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(_name, style: textTheme.headline),
new Text(_caption, style: textTheme.caption)
]),
centerTitle: false,
background: getImage());`
When the FlexibleSpaceBar is snapped in (collapsed), I want to hide the _caption text and only display the _name text. When it is expanded fully, I obviously want to display both _name & _caption.
How do I go about doing that?
Im new to flutter, so I am somewhat lost on this.
Also reported at https://github.com/flutter/flutter/issues/18567
It's not hard to create your own FlexibleSpaceBar.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return CustomScrollView(
physics: ClampingScrollPhysics(),
controller: controller,
slivers: [
SliverAppBar(
expandedHeight: 220.0,
floating: true,
pinned: true,
elevation: 50,
backgroundColor: Colors.pink,
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
flexibleSpace: _MyAppSpace(),
),
SliverList(
delegate: SliverChildListDelegate(
List.generate(
200,
(index) => Card(
child: Padding(
padding: EdgeInsets.all(10),
child: Text('text $index'),
),
),
),
),
)
],
);
}
}
class _MyAppSpace extends StatelessWidget {
const _MyAppSpace({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, c) {
final settings = context
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final deltaExtent = settings.maxExtent - settings.minExtent;
final t =
(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
.clamp(0.0, 1.0) as double;
final fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
const fadeEnd = 1.0;
final opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t);
return Stack(
children: [
Center(
child: Opacity(
opacity: 1 - opacity,
child: getTitle(
'Collapsed Title',
)),
),
Opacity(
opacity: opacity,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
getImage(),
getTitle(
'Expended Title',
)
],
),
),
],
);
},
);
}
Widget getImage() {
return Container(
width: double.infinity,
child: Image.network(
'https://source.unsplash.com/daily?code',
fit: BoxFit.cover,
),
);
}
Widget getTitle(String text) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
);
}
}
You can use AnimatedOpacity class.
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
var top = constraints.biggest.height;
return FlexibleSpaceBar(
title: AnimatedOpacity(
duration: Duration(milliseconds: 300),
//opacity: top > 71 && top < 91 ? 1.0 : 0.0,
child: Text(
top > 71 && top < 91 ? "Collapse" : "Expanded",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
)),
background: Image.network(
"https://images.ctfassets.net/pjshm78m9jt4/383122_header/d79a41045d07d114941f7641c83eea6d/importedImage383122_header",
fit: BoxFit.cover,
));
}),
Can check original answer from this link
https://stackoverflow.com/a/53380630/9719695
It can be done like this :
inside your initState method add the scroll listener like that :
ScrollController _controller;
bool silverCollapsed = false;
String myTitle = "default title";
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(() {
if (_controller.offset > 220 && !_controller.position.outOfRange) {
if(!silverCollapsed){
// do what ever you want when silver is collapsing !
myTitle = "silver collapsed !";
silverCollapsed = true;
setState(() {});
}
}
if (_controller.offset <= 220 && !_controller.position.outOfRange) {
if(silverCollapsed){
// do what ever you want when silver is expanding !
myTitle = "silver expanded !";
silverCollapsed = false;
setState(() {});
}
}
});
}
then wrap your silverAppBar inside CustomScrollView and add the controller to this CustomScrollView like that :
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
controller: _controller,
slivers: <Widget>[
SliverAppBar(
expandedHeight: 300,
title: myTitle,
flexibleSpace: FlexibleSpaceBar(),
),
SliverList(
delegate: SliverChildListDelegate(<Widget>[
// your widgets inside here !
]),
),
],
),
);
}
finally change the condition value _controller.offset > 220 to fit your need !
FlexibleSpaceBar per se won't be enough. You need to wrap it into CustomScrollView and SliverAppBar. These widgets must be controller by a ScrollController, which will fire an event whenever scroll offset changes. Based on it, you can find out if app bar is collapsed or expanded, and change the content accordingly. Here you will find a working example.
Give an height in padding in FlexibleSpaceBar
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.only(
top: 100, // give the value
title: Text(
"Test"
),
Follow up to Vishnu Suresh answer:
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.only(
top: kToolbarHeight, // give the value
title: Text(
"Test"
),
This will use the appbar height for the padding.