I've added the provider package to my application where I have two screen. When the user clicks on a small image on the app it changes the main background image on the other screen. I've called the Provider and classes on both screens but it just isn't returning the 'myValue' inside Positioned.fill.
Homepage screen with the background image that needs to change:
import 'package:flutter/material.dart';
import 'package:flutter_app_background/small_images.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Title',
home: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
title: Text('Background Image', style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold),
),
iconTheme: IconThemeData(color: Colors.white),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings, color: Colors.black,),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SmallImages()),
);
},
),
],
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Stack(
children: <Widget>
[
Positioned.fill(
child: GestureDetector(
child: Consumer<MyModel>(
builder: (context, myModel, child) {
return myModel.bgImage;
// return myValue;
},
),
),
),
],
),
);
}
}
class MyModel extends ChangeNotifier {
Image bgImage = Image.asset('images/background_image.jpeg', fit: BoxFit.fill);
}
Small Images screen where the user taps on small image to change the background in Homepage.
import 'package:flutter/material.dart';
import 'package:flutter_app_background/main.dart';
import 'package:provider/provider.dart';
class SmallImages extends StatefulWidget {
static int tappedGestureDetector = 1;
#override
_SmallImagesState createState() => _SmallImagesState();
}
class _SmallImagesState extends State<SmallImages> {
List<bool> isSelected;
void initState() {
isSelected = [true, false, false, false, false, false, false, false, false];
super.initState();
}
#override
Widget build(BuildContext context) {
final myModel = Provider.of<MyModel>(context,listen:true); //default for listen is `true`
return Scaffold(
appBar: AppBar(
title: Text('Small Image', style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),
iconTheme: IconThemeData(color: Colors.white),
actions: <Widget>[
IconButton(
icon: Icon(Icons.arrow_left, color: Colors.black,),
onPressed: () {
Navigator.pop(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
},
),
],
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Material(
child: GestureDetector(
child: MaterialApp(
builder: (context, snapshot) {
return GridView.count(
crossAxisCount: 1,
childAspectRatio: 1.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 0.0,
crossAxisSpacing: 0.0,
children: [
GridView(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,
childAspectRatio: MediaQuery
.of(context)
.size
.width /
(MediaQuery
.of(context)
.size
.height / 2),
),
children: [
GestureDetector(
onTap: () {
setState(() {
SmallImages.tappedGestureDetector = 1;
});
myModel.bgImage = Image.asset('images/iceland_background.jpg');
},
child: Container(
height: 100,
width: 107,
decoration: BoxDecoration(border: SmallImages
.tappedGestureDetector == 1
? Border.all(
color: Color(0xff2244C7), width: 1.0)
: Border
.all(color: Colors.transparent,),),
child: Image.asset(
'images/nightsky_image.png',
),
),
),
Consumer<MyModel>(
builder: (context, myModel, child) {
return GestureDetector(
onTap: () {
setState(() {
SmallImages.tappedGestureDetector = 2;
}); // <-- replaced 'tapped' and 'other'
},
child: Container(
height: 100,
width: 107,
decoration: BoxDecoration(border: SmallImages
.tappedGestureDetector == 2
? Border.all(
color: Color(0xff2244C7), width: 1.0)
: Border
.all(color: Colors.transparent,),),
child: Image.asset(
'images/own_image.png',
),
),
);
},
),
Consumer<MyModel>(
builder: (context, myModel, child) {
return GestureDetector(
onTap: () {
setState(() {
SmallImages.tappedGestureDetector = 3;
}); // <-- replaced 'tapped' and 'other'
},
child: Container(
height: 100,
width: 107,
decoration: BoxDecoration(border: SmallImages
.tappedGestureDetector == 3
? Border.all(
color: Color(0xff2244C7), width: 1.0)
: Border
.all(color: Colors.transparent,),),
child: Image.asset(
'images/iceland_image.png',
),
),
);
},
),
].toList(),
),
],
);
}),
),
),
);
}
}
You should wrap material app with provider:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Title',
home: HomePage(),
);
)
}
}
if you want it to rebuild some widget on change of the MyModel, you should extend MyModel with ChangeNotifer like this:
class MyModel extends ChangeNotifier{
final bgImage = //someimage
and instead of Provider around the MaterialApp you should use ChangeNotifierProvider like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Title',
home: HomePage(),
);
)
}
}
after that you should include provider inside the widget like this:
#override
Widget build(BuildContext context) {
final mymodel = Provider.of<MyModel>(context); // injecting the provider
return Container ( .....
and you can use background image inside the container like this,
color:mymodel.bgImage
as soon as you will change the mymodel.bgImage widget will be rebuilt automatically.
if you are planning to rebuild specific widget inside the widget tree, you can remove injection such as this in this case final mymodel = Provider.of<MyModel>(context); and just wrap that specific widget with Consumer<MyModel> like this:
Container(
child:Consumer<MyModel>(
builder: (context, myModel, child) {
return Text("${myModel.text}"); // supposing that `text` is inside the `MyModel`
},
)
if you don't need child and context to use under the Container you can do it like this:
child:Consumer<MyModel>(
builder: (_, myModel, _) {
example of changing the myModel.bgImage will be something like this:
FlatButton(
onPressed: (val){
myModel.image = otherImage // changeing the value of my model while pressing on the button
}
)
changing the value of my model while pressing on the button will rebuild any widget which injects MyModel Provider and Provider listen property is set to true,(listen property is set to true by default)
example of listen property:
final mymodel = Provider.of<MyModel>(context,listen:false) //default for listen is `true`
Related
Like with awesome bottom sheet of Telegram i would like to have that in our application, i tired to implementing that, but unfortunately i don't have more experience about that and i can implement below code like with that which it doesn't have more feature.
here i attached some Gif images such as what features i want to have them on our application:
opening animation:
switch between tabs animation:
expand and collapsing animation:
preventing closing bottom sheet during dragging down:
Full code:
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 StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLong = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: _onPressed,
child: Text('open'),
)
],
),
),
);
}
_onPressed() {
setImages();
Navigator.of(context).push(TransparentRoute(builder: (context) => NewWidget(images)));
}
List<String> images =[];
void setImages() {
images = List.generate(
isLong ? 5 : 25,
(_) => 'http://placeimg.com/100/100/any',
);
}
}
class NewWidget extends StatefulWidget {
const NewWidget(this.images, {Key? key}) : super(key: key);
final List<String> images;
#override
_NewWidgetState createState() => _NewWidgetState();
}
class _NewWidgetState extends State<NewWidget> {
bool isBig = false;
bool isBouncing = true;
final double topOffset = 200;
final double miniHandleHeight = 20;
double safeAreaPadding = 0;
double? startBigAnimationOffset;
double? startStickyOffset;
double backgroundHeight = 0;
double get savedAppBarHeight => safeAreaPadding + kToolbarHeight;
final ScrollController controller = ScrollController();
#override
void initState() {
WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
super.initState();
}
void _afterLayout(_) {
var media = MediaQuery.of(context);
safeAreaPadding = media.padding.top;
startBigAnimationOffset = topOffset - savedAppBarHeight;
startStickyOffset = (startBigAnimationOffset! + 10);
backgroundHeight = media.size.height - miniHandleHeight - topOffset;
controller.addListener(scrollListener);
}
void scrollListener() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else if (offset < startBigAnimationOffset! && isBig || offset < startStickyOffset!) {
setState(() {
isBig = false;
});
} else if (offset > startBigAnimationOffset! && !isBig || offset > startStickyOffset!) {
setState(() {
isBig = true;
});
}
if (offset < topOffset && !isBouncing) {
setState(() => isBouncing = true);
} else if (offset > topOffset && isBouncing) {
setState(() => isBouncing = false);
}
}
void goOut() {
controller.dispose();
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: /*isStack ? Colors.white : */Colors.transparent,
body: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
physics: isBouncing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
controller: controller,
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
height: topOffset,
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
duration: Duration(milliseconds: 200),
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 10),
child: Container(
height: 5,
width: 60,
),
),
),
builder: (_, number, child) {
return Container(
height: savedAppBarHeight * (number as double) + miniHandleHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular((1 - number) * 50),
),
color: Colors.white,
),
child: Opacity(opacity: 1 - (number), child: child),
);
}),
),
Container(
padding: EdgeInsets.all(5),
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - savedAppBarHeight,
),
decoration: BoxDecoration(
color: Colors.white,
),
child: getGrid(),
)
],
)
],
),
),
);
}
Widget getGrid() {
return GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 3,
children: widget.images.map((url) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
child: Image(
image: NetworkImage(url),
),
);
}).toList(),
);
}
}
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
required this.builder,
RouteSettings? settings,
}) : 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 Container(
color: Colors.black.withOpacity(0.5),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
)),
child: result,
),
);
}
}
Here is the outline, with which you can try to implement it. If you feel difficulty, feel free to ask for more suggestions. IMHO, this is not very challenging to implement (but of course need some time to adjust the fine details into what you want).
Firstly, you mentioned animations many times. Have a look at the official guide here: https://flutter.dev/docs/development/ui/animation. For example, if we want to implement the "opening animation" and the "switch between tabs animation" (and other similar things), we want a Container (or other widgets) to have a size changing with time as the animation goes. There are many ways to implement this, as is suggested in the link provided above. For example, we may use AnimatedContainer directly - it even has a video explaining this here. You can also use explicit animations or other things.
Secondly, as for the animating icons, you may use Lottie https://lottiefiles.com/ to create whatever complex animation as you like. Its flutter plugin is here: https://pub.dev/packages/lottie.
Thirdly, as for "preventing closing bottom sheet during dragging down", I will suggest the following: ListView(children: [Container(height:300,color:Colors.black), ...your_real_children...]). Then when dragging down it will not be closed, but only show a header of at most 300 height.
Lastly, you are not restricted to use showModalBottomSheet or things like that. Indeed, when using flutter, you are completely free. So you can just do Navigator.push and draw whatever widget you like without the constraints that modal bottom sheet gives you. Personally, I suggest that you first start with showModalBottomSheet, then when it does not satisfy your needs, you just directly copy its source code and do whatever modifications as you needed. In short, use something in Flutter, and when you need more control, copy the source code and modify it.
You can be helped from this link https://pub.dev/packages/modal_bottom_sheet
import 'package:example/modals/circular_modal.dart';
import 'package:example/web_frame.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'modals/floating_modal.dart';
import 'modals/modal_complex_all.dart';
import 'modals/modal_fit.dart';
import 'modals/modal_inside_modal.dart';
import 'modals/modal_will_scope.dart';
import 'modals/modal_with_navigator.dart';
import 'modals/modal_with_nested_scroll.dart';
import 'modals/modal_with_scroll.dart';
import 'modals/modal_with_page_view.dart';
import 'examples/cupertino_share.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
title: 'BottomSheet Modals',
builder: (context, Widget? child) => WebFrame(
child: child!,
),
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialWithModalsPageRoute(
builder: (_) => MyHomePage(title: 'Flutter Demo Home Page'),
settings: settings);
}
return MaterialPageRoute(
builder: (context) => Scaffold(
body: CupertinoScaffold(
body: Builder(
builder: (context) => CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
middle: Text('Normal Navigation Presentation'),
trailing: GestureDetector(
child: Icon(Icons.arrow_upward),
onTap: () => CupertinoScaffold
.showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => Stack(
children: <Widget>[
ModalWithScroll(),
Positioned(
height: 40,
left: 40,
right: 40,
bottom: 20,
child: MaterialButton(
onPressed: () => Navigator.of(
context)
.popUntil((route) =>
route.settings.name == '/'),
child: Text('Pop back home'),
),
)
],
),
)),
),
child: Center(child: Container()),
),
),
),
),
settings: settings);
},
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
middle: Text('iOS13 Modal Presentation'),
trailing: GestureDetector(
child: Icon(Icons.arrow_forward),
onTap: () => Navigator.of(context).pushNamed('ss'),
),
),
child: SizedBox.expand(
child: SingleChildScrollView(
primary: true,
child: SafeArea(
bottom: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text('Cupertino Photo Share Example'),
onTap: () => Navigator.of(context).push(
MaterialWithModalsPageRoute(
builder: (context) => CupertinoSharePage()))),
section('STYLES'),
ListTile(
title: Text('Material fit'),
onTap: () => showMaterialModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Bar Modal'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Avatar Modal'),
onTap: () => showAvatarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Float Modal'),
onTap: () => showFloatingModalBottomSheet(
context: context,
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Cupertino Modal fit'),
onTap: () => showCupertinoModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalFit(),
)),
section('COMPLEX CASES'),
ListTile(
title: Text('Cupertino Small Modal forced to expand'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Reverse list'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) =>
ModalInsideModal(reverse: true),
)),
ListTile(
title: Text('Cupertino Modal inside modal'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Cupertino Modal with inside navigation'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalWithNavigator(),
)),
ListTile(
title:
Text('Cupertino Navigator + Scroll + WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ComplexModal(),
)),
ListTile(
title: Text('Modal with WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalWillScope(),
)),
ListTile(
title: Text('Modal with Nested Scroll'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context) => NestedScrollModal(),
)),
ListTile(
title: Text('Modal with PageView'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
builder: (context) => ModalWithPageView(),
)),
SizedBox(
height: 60,
)
],
),
),
),
),
),
),
);
}
Widget section(String title) {
return Padding(
padding: EdgeInsets.fromLTRB(16, 20, 16, 8),
child: Text(
title,
style: Theme.of(context).textTheme.caption,
));
}
}
another helpful link [https://pub.dev/packages/draggable_bottom_sheet][2]
I am trying to create a shopping cart using provider and display the number of items currently in the cart on my homepage. When I create my cart icon with a text widget overlaid, the value being shown does not reflect the number of items in the cart.
Here is my code:
class OurShoppingBasketIcon extends StatelessWidget {
const OurShoppingBasketIcon({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ShoppingBasketScreen()),
);
},
child: Stack(
children: <Widget>[
new Icon(
Icons.shopping_cart_outlined,
color: Colors.white,
),
new Positioned(
right: 0,
child: new Container(
padding: EdgeInsets.all(1),
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
context.read<ShoppingBasket>().items.length.toString(),
style: new TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
)
],
),
),
);
}
}
This is where the icon is implemented:
class OurHomePage extends StatefulWidget {
#override
_OurHomePageState createState() => _OurHomePageState();
}
class _OurHomePageState extends State<OurHomePage> {
#override
Widget build(BuildContext context) {
return Consumer<OurUser>(
builder: (_, user, __) {
return ChangeNotifierProvider<SignInViewModel>(
create: (_) => SignInViewModel(context.read),
builder: (_, child) {
return Scaffold(
appBar: AppBar(
title: Text("My app"),
actions: [
OurShoppingBasketIcon(),
IconButton(
icon: Icon(
Icons.logout,
color: Colors.white,
),
onPressed: () {
context.read<FirebaseAuthService>().signOut();
},
),
],
),
);
},
);
},
);
}
}
There are 2 items in the cart as of writing this:
But the icon on the homepage does not change:
Here is my main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
Provider(
create: (_) => FirebaseAuthService(),
),
StreamProvider<OurUser>(
create: (context) =>
context.read<FirebaseAuthService>().onAuthStateChanged),
ChangeNotifierProvider.value(
value: ShoppingBasket(),
),
],
child: MaterialApp(theme: OurTheme().buildTheme(), home: OurHomePage()),
),
);
}
perhaps if you watch for the value it will be updated dynamically:
context.watch<ShoppingBasket>().items.length.toString(), //<-- watch instead of read
The OurHomePage needs to be wrapped in the Provider<ShoppingBasket>.
return Provider<ShoppingBasket>(
create: (context) => ShoppingBasket(),
child: Consumer<OurUser>(
builder: (_, user, __) {
return ChangeNotifierProvider<SignInViewModel>(
create: (_) => SignInViewModel(context.read),
builder: (_, child) {
return Scaffold(
appBar: AppBar(
title: Text("My app"),
actions: [
OurShoppingBasketIcon(),
IconButton(
icon: Icon(
Icons.logout,
color: Colors.white,
),
onPressed: () {
context.read<FirebaseAuthService>().signOut();
},
),
],
),
),
);
},
);
I forgot to NotifyListeners() in my Change Notifier class:
class ShoppingBasket extends ChangeNotifier {
Map<String, SingleBasketItem> _items = {};
Map<String, SingleBasketItem> get items {
return {..._items};
}
void addItem(String id) {
_items.putIfAbsent(
id,
() => SingleBasketItem(id),
);
notifyListeners(); //HERE
}
so I am currently having a grid of pictures and I want to implement a feature from instagram: If you longPress on one of the pictures, you get a a larger version of that picture appearing in the middle of the screen. If you stop pressing, the image dissapears.
I don't really need the code for that, but I just can't think of which widgets I should use.
Is there maybe a package for something like this? If not then how can I do it with Flutter standard widgets? Maybe using a dialog that appears on the longPress ?
Here's the improved vewrsion that resembles the same exact UX as of Instagram with blurred background.
We can achieve this using a combination of Stateful Widget, Stack and BackdropFliter, here is the sample code -
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Counter Demo',
theme: ThemeData.light(),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueGrey,
centerTitle: true,
title: Text("Demo App"),
),
body: Stacked(),
),
);
}
}
class Stacked extends StatefulWidget {
#override
_StackedState createState() => _StackedState();
}
class _StackedState extends State<Stacked> {
final List<String> images = [
"1.jpg",
"2.jpg",
"3.jpg",
"4.jpg",
"5.jpg",
"6.jpg",
"7.jpg",
"8.jpg",
"9.jpg",
"10.jpg",
];
bool _showPreview = false;
String _image = "assets/images/1.jpg";
#override
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
children: [
GridView.builder(
itemCount: images.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onLongPress: () {
setState(() {
_showPreview = true;
_image = "assets/images/${images[index]}";
});
},
onLongPressEnd: (details) {
setState(() {
_showPreview = false;
});
},
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
clipBehavior: Clip.hardEdge,
child: Image.asset("assets/images/${images[index]}"),
),
),
);
},
),
if (_showPreview) ...[
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 5.0,
sigmaY: 5.0,
),
child: Container(
color: Colors.white.withOpacity(0.6),
),
),
Container(
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.asset(
_image,
height: 300,
width: 300,
),
),
),
),
],
],
));
}
}
This is just a baseline example and there are endless possibilities you can modify this to achieve behavior you want.
Another simple way is we can build this by using StatefulWidget and IndexedStack -
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Counter Demo',
theme: ThemeData.light(),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueGrey,
centerTitle: true,
title: Text("Demo App"),
),
body: Body(),
),
);
}
}
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
final List<String> images = [
"1.jpg",
"2.jpg",
"3.jpg",
"4.jpg",
"5.jpg",
"6.jpg",
"7.jpg",
"8.jpg",
"9.jpg",
"10.jpg",
];
int _index = 0;
String _image = "assets/images/1.jpg";
#override
Widget build(BuildContext context) {
return SafeArea(
child: IndexedStack(
index: _index,
children: [
GridView.builder(
itemCount: images.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onLongPress: () {
setState(() {
_index = 1;
_image = "assets/images/${images[index]}";
});
},
onLongPressEnd: (details) {
setState(() {
_index = 0;
});
},
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
clipBehavior: Clip.hardEdge,
child: Image.asset("assets/images/${images[index]}"),
),
),
);
},
),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
),
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 400,
maxWidth: 400,
),
child: Image.asset(
_image,
),
),
),
)
],
),
);
}
}
You can check output for above code here.
You could use Peek and Pop https://pub.dev/packages/peek_and_pop
Is an implementation for Flutter based on the iOS functionality of the same name.
I'm trying to build two screens where:
The first screen has a Pincode textfield when the user enters the current date in (dd-mm) format for eg: if today's date is 24-07, the user enters 2407, then it should navigate to another page i.e., second screen
First Screen: Passcode.dart
import 'package:flutter/material.dart';
import 'package:flutter_course/HomePage.dart';
//import 'package:pin_entry_text_field/pin_entry_text_field.dart';
import 'package:pin_code_text_field/pin_code_text_field.dart';
import 'package:intl/intl.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: homePage(),
));
}
}
class homePage extends StatefulWidget {
#override
_homePageState createState() => _homePageState();
}
class _homePageState extends State<homePage> {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Stack(children: <Widget>[
Center(
child: Image.asset(
"assets/passcode.jpeg",
width: size.width,
height: size.height,
fit: BoxFit.fill,
),
),
Column(
children: <Widget>[
// SizedBox(height:200,),
// SizedBox(width: 300),
Padding(
padding: EdgeInsets.only(left: 460, top: 150),
child: Text("ENTER PASSCODE",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 38.0,
color: Colors.black)),
),
SizedBox(height: 30),
Padding(
padding: EdgeInsets.only(left: 460),
child: PinCodeTextField(
autofocus: false,
pinTextStyle: TextStyle(
color: Colors.black,
fontSize: 40,
fontWeight: FontWeight.bold),
hideCharacter: true,
maskCharacter: "*",
// highlight: true,
// highlightColor: Colors.blue,
defaultBorderColor: Colors.black,
hasTextBorderColor: Colors.white,
hasError: true,
errorBorderColor: Colors.red,
//onTextChanged: (String)=>func(context),
onDone: (String) => func(context),
),
),
],
)
]);
}
}
void func(context) {
var now = new DateTime.now();
var formatter = new DateFormat('MMMMd');
var formatted = formatter.format(now);
debugPrint(formatted);
if (String == formatted) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => HomePage()));
}
}
Second Screen :HomePage.dart
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(length: 2,child:Scaffold(
drawer: Drawer(
child: Column(
children: <Widget>[
AppBar(
automaticallyImplyLeading: false,
title: Text('Choose'),
backgroundColor:Color(0xffedac51),
),
ListTile(
title: Text('Devices'),
onTap: () {
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (BuildContext context) =>
// ProductsPage()));
},
),
ListTile(
title: Text('Allotted Devices'),
onTap: () {
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (BuildContext context) =>
// ProductsPage()));
},
),
ListTile(
title: Text('Assign Devices'),
onTap: () {
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (BuildContext context) =>
// ProductsPage()));
},
)
],
),
),
appBar: AppBar(
title: Text('Home'),
backgroundColor:Color(0xffedac51) ,
),
body: HomeBody()
)
);}
}
class HomeBody extends StatelessWidget{
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Stack(
children: <Widget>[
Center(
child: new Image.asset(
'assets/passcode.jpeg',
width: size.width,
height: size.height,
fit: BoxFit.fill,
),
),
Padding(
padding: EdgeInsets.only(top: 200, left: 500),
child: Column(
children: <Widget>[
Text("Let\'s Get Started!",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 38.0,
color: Colors.white)),
],
)),
],
);
}
}
When the passcode is entered as the current date in ddmm format, then it should navigate to the next screen
Not quite sure what you're trying to achieve, but something simple like this should do the trick:
DateTime today = DateTime.now();
String day, month, passCode;
if(today.day < 10){
day = "0" + today.day.toString();
} else {
day = today.day.toString();
}
if(today.month < 10){
month = "0" + today.month.toString();
} else {
month = today.month.toString();
}
passCode = day + month;
I am using a SliverAppBar in Flutter, with a background widget.
The thing is When it's expanded, the title and icons (leading and actions) should be white in order to be seen correctly, and when it's collapsed, they should be changed to black.
Any ideas on how I can get a bool out of it? Or other ways of resolving this problem.
Thank you.
class SliverExample extends StatefulWidget {
final Widget backgroundWidget;
final Widget bodyWidgets;
SliverExample({
this.backgroundWidget,
this.body,
});
#override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
// I need something like this
// To determine if SliverAppBar is expanded or not.
bool isAppBarExpanded = false;
#override
Widget build(BuildContext context) {
// To change the item's color accordingly
// To be used in multiple places in code
Color itemColor = isAppBarExpanded ? Colors.white : Colors.black;
// In my case PrimaryColor is white,
// and the background widget is dark
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
leading: BackButton(
color: itemColor, // Here
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: itemColor, // Here
),
onPressed: () {},
),
],
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'title',
style: TextStyle(
fontSize: 18.0,
color: itemColor, // Here
),
),
// Not affecting the question.
background: widget.backgroundWidget,
),
),
// Not affecting the question.
SliverToBoxAdapter(child: widget.body),
],
),
);
}
}
You can use LayoutBuilder to get sliver AppBar biggest height. When biggest.height = MediaQuery.of(context).padding.top + kToolbarHeight, it actually means sliver appbar is collapsed.
Here is a full example, in this example MediaQuery.of(context).padding.top + kToolbarHeight = 80.0:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var top = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 200.0,
floating: false,
pinned: true,
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// print('constraints=' + constraints.toString());
top = constraints.biggest.height;
return FlexibleSpaceBar(
centerTitle: true,
title: AnimatedOpacity(
duration: Duration(milliseconds: 300),
//opacity: top == MediaQuery.of(context).padding.top + kToolbarHeight ? 1.0 : 0.0,
opacity: 1.0,
child: Text(
top.toString(),
style: TextStyle(fontSize: 12.0),
)),
background: Image.network(
"https://images.unsplash.com/photo-1542601098-3adb3baeb1ec?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=5bb9a9747954cdd6eabe54e3688a407e&auto=format&fit=crop&w=500&q=60",
fit: BoxFit.cover,
));
})),
];
},body: ListView.builder(
itemCount: 100,
itemBuilder: (context,index){
return Text("List Item: " + index.toString());
},
),
));
}
}
Final result:
You need to use ScrollController to achieve the desired effect
try this code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SliverExample(
bodyWidgets: Text(
'Hello Body gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg'),
backgroundWidget: Text('Hello Background'),
),
);
}
}
class SliverExample extends StatefulWidget {
final Widget backgroundWidget;
final Widget bodyWidgets;
SliverExample({
this.backgroundWidget,
this.bodyWidgets,
});
#override
_SliverExampleState createState() => _SliverExampleState();
}
class _SliverExampleState extends State<SliverExample> {
ScrollController _scrollController;
Color _theme ;
#override
void initState() {
super.initState();
_theme = Colors.black;
_scrollController = ScrollController()
..addListener(
() => _isAppBarExpanded ?
_theme != Colors.white ?
setState(
() {
_theme = Colors.white;
print(
'setState is called');
},
):{}
: _theme != Colors.black ?
setState((){
print(
'setState is called');
_theme = Colors.black;
}):{},
);
}
bool get _isAppBarExpanded {
return _scrollController.hasClients &&
_scrollController.offset > (200 - kToolbarHeight);
}
#override
Widget build(BuildContext context) {
// To change the item's color accordingly
// To be used in multiple places in code
//Color itemColor = isAppBarExpanded ? Colors.white : Colors.black;
// In my case PrimaryColor is white,
// and the background widget is dark
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
leading: BackButton(
color: _theme, // Here
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.shopping_cart,
color: _theme, // Here
),
onPressed: () {},
),
],
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'title',
style: TextStyle(
fontSize: 18.0,
color: _theme, // Here
),
),
// Not affecting the question.
background: widget.backgroundWidget,
),
),
// Not affecting the question.
SliverToBoxAdapter(child: widget.bodyWidgets),
],
),
);
}
}
if you are not familiar with ? : notation you can use the following
_scrollController = ScrollController()
..addListener(
(){
if(_isAppBarExpanded){
if(_theme != Colors.white){
setState(() {
_theme = Colors.white;
print('setState is called with white');
});
}
}else{
if(_theme != Colors.black){
setState(() {
_theme = Colors.black;
print('setState is called with black');
});
}
}
}
Use NestedScrollView that have innerBoxIsScrolled boolean flag that will be a decent solution for your problem something like this below
NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
print("INNEER SCROLLED VI=======>$innerBoxIsScrolled");
return <Widget>[
SliverAppBar(),
]},
body:Center(child:Text("Test IT")),
);
You can use SliverLayoutBuilder to get the current SliverConstraints and read its value, to easily detect how much scrolling has occurred. This is very similar to LayoutBuilder except it's operating in the sliver-world.
If constraints.scrollOffset > 0, that means the user has scrolled at least a little bit. Using this method, if you want to do some animation/transition when scrolling, it's easy too - just get the current scrollOffset and generate your animation frame based on that.
For example, this SliverAppBar changes color when user scrolls:
SliverLayoutBuilder(
builder: (BuildContext context, constraints) {
final scrolled = constraints.scrollOffset > 0;
return SliverAppBar(
title: Text('Sliver App Bar'),
backgroundColor: scrolled ? Colors.blue : Colors.red,
pinned: true,
);
},
)
This works for me
check this line
title: Text(title,style: TextStyle(color: innerBoxIsScrolled? Colors.black:Colors.white),),
change your title to
title: innerBoxIsScrolled? Text("hello world") : Text("Good Morning",)
class _ProductsState extends State<Products> {
String image;
String title;
_ProductsState(this.image,this.title);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
leading: InkWell(
onTap: (){
},
child: Icon(
Icons.arrow_back_ios,
color: Colors.black,
),
),
backgroundColor: Colors.white,
pinned: true,
//floating: true,
stretch: true,
expandedHeight: 300.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(title,style: TextStyle(color: innerBoxIsScrolled? Colors.black: Colors.white),),
background: CachedNetworkImage(imageUrl:image,fit: BoxFit.fitWidth,alignment: Alignment.topCenter,
placeholder: (context, url) => const CircularProgressIndicator(color: Colors.black,),
errorWidget: (context, url, error) => const Icon(Icons.error),),
),
),
),
];
},
body: Builder(
builder:(BuildContext context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverToBoxAdapter(
child: Container(
height: 90,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
color: Colors.green,
),
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
),
),
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.red,
),
),
],
);
}
),
),
);
}
}