Flutter: Long Press on picture to get zoomed preview like Instagram - flutter

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.

Related

Background image is moving when keyboards appears Flutter

i was searching a lot and reading a lot another threads and nothing work for my case, my background image is moving when keyboards appears. I tried my best, and I am getting frustrated already because I am wasting a lot of time, could someone help me with this?
the image goes up every time I open the textinput and the keyboard appears, I think my mistake is to position the elements in the code, or I am missing something but the truth is I can't figure out what my mistake is.
I want to add that this page" is a page of a PageView, I don't know if that will have something about causing the issue.
Cuz i switch between login and register pages with the buttons, they are a pageview.
import 'package:flutter/material.dart';
import 'package:plantsapp/screens/welcome_page.dart';
import 'package:plantsapp/services/authentication_service.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
AuthenticationService authServ = new AuthenticationService();
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(children: [
_crearfondo2(),
_loginForm()
],),
//_loginForm(),
);
}
Widget _crearfondo2() {
return Container(
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/backgroundlogin.jpg'),
fit: BoxFit.cover,
)));
}
/*Widget _crearFondo() {
return Positioned(
child: Image.asset(
'assets/images/backgroundlogin.jpg',
fit: BoxFit.fill,
),
height: MediaQuery.of(context).size.height,
top: 0.0,
right: 0.0,
left: 0.0,
);
}*/
Widget _loginForm() {
return SingleChildScrollView(
child: Container(
color: Colors.white,
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 130),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 60),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Login',
style: TextStyle(fontSize: 20),
),
_emailForm(),
_passwordForm(),
_button(),
_buttonRegister(),
],
),
),
);
}
Widget _emailForm() {
return TextField(
controller: emailController,
decoration: InputDecoration(hintText: ('Email')),
);
}
Widget _passwordForm() {
return TextField(
controller: passwordController,
decoration: InputDecoration(hintText: ('Password')),
);
}
Widget _button() {
return RaisedButton(
child: Text('Login'),
onPressed: () async {
//Provider.of<AuthenticationService>(context, listen: false).
dynamic result = await authServ.signIn(
email: emailController.text.trim(),
password: passwordController.text.trim());
if (result == null) print('error signing in');
},
);
}
_buttonRegister() {
final navegacionModel = Provider.of<NavegacionModel>(context);
int index = 1;
return RaisedButton(
child: Text('Registrarse'),
onPressed: () {
// Navigator.pushNamed(context, 'registerpage');
navegacionModel.paginaActual = index;
navegacionModel.pageController;
},
);
}
}
WelcomePage:
class WelcomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => new NavegacionModel(),
child: Scaffold(
body: _Paginas(),
),
);
}
}
class _Paginas extends StatelessWidget {
#override
Widget build(BuildContext context) {
final navegacionModel = Provider.of<NavegacionModel>(context);
return PageView(
controller: navegacionModel.pageController,
// physics: BouncingScrollPhysics(),
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
LoginPage(),
RegisterPage(),
],
);
}
}
class NavegacionModel with ChangeNotifier {
int _paginaActual = 0;
PageController _pageController = new PageController();
int get paginaActual => this._paginaActual;
set paginaActual(int valor) {
this._paginaActual = valor;
_pageController.animateToPage(valor, duration: Duration(milliseconds: 235), curve: Curves.easeOut);
notifyListeners();
}
PageController get pageController => this._pageController;
}
Fixed!!! i will share how i fix it for someone need this:
welcome_page.dart
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => new NavegacionModel(),
child: Scaffold(
resizeToAvoidBottomInset: false, // i had to add this here, when i add here the image didnt move anymore by the keyboard
body: _Paginas(),
),
);
}
}
login_page.dart
#override
Widget build(BuildContext context) {
return Stack( // i remove this unnecesary scaffold until i need childrens, for now, i just return the stack
children: [
_crearfondo2(),
Padding( //i add this Padding cuz the form didnt scroll with the "resizeToAvoidBottomInset: false" until i add this padding here.
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: _loginForm(),
),
],
);
}
Try Like This
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter background',
home: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/background.jpg"), fit: BoxFit.cover)),
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
title: Text('My App'),
centerTitle: true,
),
),
),
);
}
Use
resizeToAvoidBottomInset: false,
in the welcome page Scaffold
child: Scaffold(
body: _Paginas(),
),

Why isn't the Provider Package changing the background image?

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`

How to make button change color and icon in the ListTile

I have ListTile in the ListView with RaisedButton as trailing, I want to change color and icon on btn clicked, trouble is if I change it on setstate method all listTile buttons change. So how to determine each one?
Widget _getList(BuildContext context,int index,) {
return Card(
elevation: 3,
child: Column(
children: <Widget>[
ListTile(
leading: Image.asset(
"assets/" + _allDevices[index].image,
fit: BoxFit.cover,
),
title: Text(_allDevices[index].name),
subtitle: Text(_allDevices[index].desc),
trailing: SizedBox.fromSize(
size: Size(56, 56), // button width and height
child: ClipOval(
child: RaisedButton(
elevation: 2,
splashColor: Colors.red,
color: Colors.blue,
onPressed: () {
setState(() {
//pro should do something here... switch index or something....
});
},
child: Icon(Icons.lock_open),
),
)),
onTap: () {},
)
],
),
);
}
Find this sample, All needed is bool flag in the model class which maintains the click status. On click set it true, if it's already true then set it as false.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class Devices {
String name = '';
bool isSelected = false;
Devices(this.name, this.isSelected);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
var _allDevices = [
Devices('Text', false),
Devices('Text', false),
Devices('Text', false),
Devices('Text', false)
];
Widget _getList(BuildContext context, int index) {
return Card(
elevation: 3,
child: Column(
children: <Widget>[
ListTile(
leading: Text('Text'),
title: Text(_allDevices[index].name),
subtitle: Text(_allDevices[index].name),
trailing: SizedBox.fromSize(
size: Size(56, 56), // button width and height
child: ClipOval(
child: RaisedButton(
elevation: 2,
color: _allDevices[index].isSelected
? Colors.green
: Colors.blue,
onPressed: () {
setState(() {
if (_allDevices[index].isSelected) {
_allDevices[index].isSelected = false;
} else{
_allDevices[index].isSelected = true;
}
});
},
child: Icon(Icons.lock_open),
),
)),
onTap: () {},
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 4,
itemBuilder: (context, index) {
return _getList(context, index);
})
]));
}
}

Text and Image together in a Flutter Card

I want to add an image and a text together in a card but I'm not able to do it. I think a row would be used here but not able to implement it. I'm new to flutter and app development so having some troubles. Any help is really appreciated. Also the cards are in GridView(not in ListView).
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'lists.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Just Another App',
home: Home(),
routes: {
"/webview": (_) => WebviewScaffold(
withJavascript: true,
withLocalStorage: true,
url: 'https://www.google.com/',
appBar: AppBar(
title: Text('Browser'),
),
),
},
theme: ThemeData(primaryColor: Colors.black),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final webView = FlutterWebviewPlugin();
#override
void initState() {
super.initState();
webView.close();
}
#override
Widget build(BuildContext context) {
var gridView = GridView.builder(
itemCount: 15,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: Card(
elevation: 10,
child: Padding(
padding: const EdgeInsets.all(35),
child: Container(
// child: SizedBox(child: Text('yashjha'),),
// child: Image.asset('lib/images/${images[index]}'),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/images/${images[index]}'),
fit: BoxFit.fitWidth,
alignment: Alignment.topCenter,
),
),
),
),
),
onTap: () {
setState(() {
Navigator.of(context).pushNamed('/webview');
});
},
);
});
return Scaffold(
appBar: AppBar(
title: Text('Just Another App'),
),
body: Container(
child: gridView,
padding: EdgeInsets.all(12),
),
);
}
[GridViewApp Screenshot][1]}
This is a variable which I'm using in the "body" of Scaffold.
If you want to place an Image and a text side by side, add a Row widget:
child: Row(
children: <Widget>[
DecorationImage(...),
Text(...),
],
)
You can also set mainAxisAlignment and crossAxisAlignment to make sure that your widgets are placed correctly
just wrap your Container into a Row Widget and add a text to it.
Row(
children: <Widget>[
Text("add text here"),
Container(
// child: SizedBox(child: Text('yashjha'),),
// child: Image.asset('lib/images/${images[index]}'),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/images/${images[index]}'),
fit: BoxFit.fitWidth,
alignment: Alignment.topCenter,
),
),
),
],
),

Flutter How to check if Sliver AppBar is expanded or collapsed?

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,
),
),
],
);
}
),
),
);
}
}