Related
I have an application like this:
My aim is that when I press the eye icon next to the text "Hello", I want a box to open just below the text and write the German version of "Hello". So it will say "Hallo".
My purpose is to show the meaning of the word.
When I press the eye, I want to show the German of the word. How can I make a white box under the word Hello, that is, the box in which the German language will be written?
Codes:
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
class selamlasmaLearn extends StatelessWidget {
List <wordAndMeaning> wordsList = [wordAndMeaning("Hello", "Hallo"), wordAndMeaning("Go", "Gehen")];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) {
final double height = MediaQuery.of(context).size.height;
return CarouselSlider(
options: CarouselOptions(
height: height,
viewportFraction: 1.0,
enlargeCenterPage: false,
),
items: wordsList.map((wordAndMeaning word) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amber),
child: Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
word.word,
style: TextStyle(fontSize: 45, color: Colors.white),
),
SizedBox(width: 10,),
Icon(Icons.remove_red_eye_sharp, color: Colors.white, size: 25,), // <<<<<<<<<
],
),
),
);
},
);
}).toList(),
);
}
),
);
}
}
class wordAndMeaning {
String word;
String meaning;
wordAndMeaning(this.word, this.meaning);
}
I keep the word and its German in a list called wordsList.
Thanks for the help in advance.
You can convert the widget to StatefulWidget or use a ValueNotifier to control the preserve/notify the state visibility.
You can use Visibility widget or just if to show and hide German text.
class selamlasmaLearn extends StatefulWidget {
#override
State<selamlasmaLearn> createState() => _selamlasmaLearnState();
}
class _selamlasmaLearnState extends State<selamlasmaLearn> {
bool _showGerman = false;
List<wordAndMeaning> wordsList = [
wordAndMeaning("Hello", "Hallo"),
wordAndMeaning("Go", "Gehen")
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(builder: (context) {
final double height = MediaQuery.of(context).size.height;
return CarouselSlider(
options: CarouselOptions(
height: height,
viewportFraction: 1.0,
enlargeCenterPage: false,
),
items: wordsList.map((wordAndMeaning word) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amber),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(word.word,
style:
TextStyle(fontSize: 45, color: Colors.white)),
if (_showGerman) Text(word.meaning), //modify the way you want
],
),
const SizedBox(
width: 10,
),
IconButton(
icon: Icon(Icons.remove_red_eye_sharp),
color: Colors.white,
iconSize: 25,
onPressed: () {
setState(() {
_showGerman = !_showGerman;
});
},
),
],
),
);
},
);
}).toList(),
);
}),
);
}
}
Use the Tooltip widget
I'm emphasizing on the popup part in your question title. When using a Tooltip you ensure that your widgets do not shift position or jump when the Tooltip widget appear, as the example below illustrates.
Example code:
import 'package:flutter/material.dart';
class TooltipExample extends StatelessWidget {
const TooltipExample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Tooltip(
// Set the tooltip to trigger on a single tap, tapping outside the
// widget will make the tooltip disappear.
triggerMode: TooltipTriggerMode.tap,
// The message shown when the tooltip appears.
message: "Tooltip showing!",
// Consider adjusting this to your needs.
showDuration: const Duration(days: 1),
// The widget that must be clicked to show the tooltip.
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text("Hello"),
SizedBox(
width: 8,
),
Icon(Icons.visibility),
],
),
),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text("Cover me!"),
)
],
),
);
}
}
// Some code to run the above example, note the theme part that turns the
// tooltip white.
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
// Style the overall design of tooltips in the app in one place,
// or provide in each tooltip individually.
theme: ThemeData(
tooltipTheme: const TooltipThemeData(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(4),
),
),
textStyle: TextStyle(
backgroundColor: Colors.white,
color: Colors.black,
),
),
),
home: const Scaffold(
backgroundColor: Colors.amber,
body: TooltipExample(),
),
);
}
}
void main() => runApp(const App());
Here is how it looks:
Note that the Tooltip widget overlays whatever is below it. (instead of pushing it further down - like toggling the visibility of a normal widget in a row or column would have done)
i have setup banner ads in flutter and those are overlapping the bottom navigation bar
I want to display ads below that bottom navigation bar,
is there any way that i can add a margin below the bottom navigation bar ?
i have implemented ads in home.dart (mainpage)
import 'package:provider/provider.dart';
import '../../ui/widgets/bottom_nav_bar.dart';
import '../../core/utils/theme.dart';
import 'search_page.dart';
import 'category.dart';
import 'main_page.dart';
import 'settings.dart';
import 'package:flutter/material.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'for_you.dart';
const String AD_MOB_APP_ID = 'ca-app-pub-3940256099942544~3347511713';
const String AD_MOB_TEST_DEVICE = 'DEC3010B2445165B43EB949F5D97D0F8 - run ad then check device logs for value';
const String AD_MOB_AD_ID = 'ca-app-pub-3940256099942544/6300978111';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
BannerAd _bannerAd;
static final MobileAdTargetingInfo targetingInfo = new MobileAdTargetingInfo(
testDevices: <String>[AD_MOB_TEST_DEVICE],
);
BannerAd createBannerAd() {
return new BannerAd(
adUnitId: AD_MOB_AD_ID,
size: AdSize.banner,
targetingInfo: targetingInfo,
);
}
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
int _selectedIndex = 0;
final PageController _pageController = PageController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
#override
Widget build(BuildContext context) {
final stateData = Provider.of<ThemeNotifier>(context);
final ThemeData state = stateData.getTheme();
FirebaseAdMob.instance.initialize(appId: AD_MOB_APP_ID);
_bannerAd = createBannerAd()..load()..show();
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: false,
backgroundColor: state.primaryColor,
elevation: 0,
title: Text(
'RevWalls',
style: state.textTheme.headline,
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: state.textTheme.body1.color,
),
onPressed: () => showSearch(
context: context, delegate: WallpaperSearch(themeData: state)),
)
],
),
body: Container(
color: state.primaryColor,
child: PageView(
controller: _pageController,
physics: BouncingScrollPhysics(),
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
children: <Widget>[
MainBody(),
Category(),
ForYou(),
SettingsPage(),
],
),
),
bottomNavigationBar: BottomNavyBar(
selectedIndex: _selectedIndex,
unselectedColor: state.textTheme.body1.color,
onItemSelected: (index) {
_pageController.jumpToPage(index);
},
selectedColor: state.accentColor,
backgroundColor: state.primaryColor,
showElevation: false,
items: [
BottomNavyBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavyBarItem(
icon: Icon(Icons.category),
title: Text('Subreddits'),
),
BottomNavyBarItem(
icon: Icon(Icons.phone_android),
title: Text('Exact Fit'),
),
BottomNavyBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
],
),
);
}
Widget oldBody(ThemeData state) {
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: state.primaryColor,
elevation: 4,
title: Text(
'reWalls',
style: state.textTheme.headline,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search, color: state.accentColor),
onPressed: () {
showSearch(
context: context,
delegate: WallpaperSearch(themeData: state));
},
)
],
floating: true,
pinned: _selectedIndex == 0 ? false : true,
snap: false,
centerTitle: false,
),
];
},
body: Container(
color: state.primaryColor,
child: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
children: <Widget>[
MainBody(),
Category(),
ForYou(),
SettingsPage(),
],
),
),
);
}
}
and here is the bottom navigation bar -
library bottom_navy_bar;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class BottomNavyBar extends StatelessWidget {
final int selectedIndex;
final double iconSize;
final Color backgroundColor, selectedColor, unselectedColor;
final bool showElevation;
final Duration animationDuration;
final List<BottomNavyBarItem> items;
final ValueChanged<int> onItemSelected;
BottomNavyBar(
{Key key,
this.selectedIndex = 0,
this.showElevation = true,
this.iconSize = 20,
this.backgroundColor,
this.selectedColor,
this.unselectedColor,
this.animationDuration = const Duration(milliseconds: 250),
#required this.items,
#required this.onItemSelected}) {
assert(items != null);
assert(items.length >= 2 && items.length <= 5);
assert(onItemSelected != null);
}
Widget _buildItem(BottomNavyBarItem item, bool isSelected) {
return AnimatedContainer(
width: isSelected ? 120 : 50,
height: double.maxFinite,
duration: animationDuration,
decoration: BoxDecoration(
color: isSelected ? selectedColor.withOpacity(0.2) : backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
),
alignment: Alignment.center,
child: ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconTheme(
data: IconThemeData(
size: iconSize,
color: isSelected ? selectedColor : unselectedColor),
child: item.icon,
),
),
isSelected
? DefaultTextStyle.merge(
style: TextStyle(
fontSize: 16,
color: selectedColor,
fontWeight: FontWeight.bold),
child: item.title,
)
: SizedBox.shrink()
],
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: backgroundColor,
child: Container(
width: double.infinity,
height: 55,
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: items.map((item) {
var index = items.indexOf(item);
return GestureDetector(
onTap: () {
onItemSelected(index);
},
child: _buildItem(item, selectedIndex == index),
);
}).toList(),
),
),
);
}
}
class BottomNavyBarItem {
final Icon icon;
final Text title;
BottomNavyBarItem({
#required this.icon,
#required this.title,
}) {
assert(icon != null);
assert(title != null);
}
}
Please help
adding builder like this will solve the problem
var paddingBottom = 60.0;
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
//
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'World General info',
//theme: ThemeData(primarySwatch: Colors.cyan,),
theme: ThemeData(
primaryColor: Colors.cyan,
accentColor: Colors.green,
textTheme: TextTheme(bodyText2: TextStyle(color: Colors.purple)),
),
home: MyHomePage(title: 'World General Info'),
builder: (BuildContext context, Widget child) {
return new Padding(
child: child,
padding: EdgeInsets.only(bottom: paddingBottom),
);
}
);
}
}
You can use the MediaQuery.of(context) for that.
Wrap the whole Code inside a Container of height: MediaQuery.of(context).size.height - 60 . (the height of ad)
Column(
children: [
Container(
height: MediaQuery.of(context).size.height - 60,
child: HomePage(),
),
BannerAd(),
]
);
Found answer myself
We can use this to set margin in a container with other things like height, width
This code will add margin to bottom of bottom nav bar, as per my need i want to show ads below navbar so this solves my problem
margin: const EdgeInsets.only(bottom: 50)
Add this to your scaffold
bottomNavigationBar: Container(
height: 50.0,
color: Colors.white,
),
Think Simplest bro ... wrap your column ( mainAxisSize : MainAxisSize.min )
Scaffold(
appBar: _appBar,
body: _body,
bottomNavigationBar: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Container(
color: Colors.amber,
height: 70,
child: Center(child: Text('Banner'))),
BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add')
],
)
]))
I'm a new flutter user. I'm trying to make a shopping list app. Basically the first page contains 3 clickable images. These images are the logos of the three stores we currently shop at for our supplies. When you click on the image, it takes you to another page that has a grid view of all of the items (pictures of the items) that we purchase from that store. My end goal is that when you click on the picture of the item, a dialog box pops up asking how many of this item you would like to put on the list, then put it on the list. I've gotten as far as creating a grid view of the items, and making the first image clickable, but I'm not sure how to make the input dialog box. Any help would be greatly appreciated.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Shopping List');
}
}
class FirstRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Pick a store to start your list"),
),
body: Column(
children: [
Center(
child: Container(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 150, maxWidth: 150),
child: Ink.image(
image: AssetImage('images/aldi.jpg'),
fit: BoxFit.fitWidth,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondRoute()),
);
},
),
),
),
),
),
Expanded(
child: Container(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 150, maxWidth: 150),
child: Ink.image(
image: AssetImage('images/rd.png'),
fit: BoxFit.fitWidth,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ThirdRoute()),
);
},
),
),
),
),
),
Expanded(
child: Container(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 150, maxWidth: 150),
child: Ink.image(
image: AssetImage('images/sams.jpg'),
fit: BoxFit.fitWidth,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FourthRoute()),
);
},
),
),
),
),
),
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Aldi's"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
children: <Widget>[
FlatButton(onPressed: (),
child: Image.asset('images/2milk.jpg'),
),
Image.asset('images/skimMilk.jpg'),
Image.asset('images/almondMilk.jpg'),
Image.asset('images/coconutMilk.jpg'),
Image.asset('images/soyMilk.jpg'),
Image.asset('images/halfAndHalf.jpg'),
Image.asset('images/heavyCream.jpg'),
Image.asset('images/whipCream.jpg'),
],
),
),
),
);
}
}
class ThirdRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Restraurant Depot"),
),
body: Center(),
);
}
}
class FourthRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sam's Club"),
),
body: Center(),
);
}
}
To make a dialog box, there are many widgets you can use.
I think the best use case here is an AlertDialog with your widgets items like TextField on top.
Inside the onTap add this, you can leave the long decorations if you don't want a frame-less border, i just added them to make my TextField look neater. You can also leave the controller, if you do not plan using a TextEditingController.
onTap: () {
var alert = AlertDialog(
title: Text("How many items do you need?"),
content: TextField(
style: TextStyle(
decoration: TextDecoration.none),
maxLines: 1,
maxLengthEnforced: false,
autofocus: false,
enabled: true,
onSubmitted: (String text) {
int number_input = int.parse(text);
// Do something with your number like pass it to the next material page route
},
controller: _controller,
decoration: new InputDecoration(
errorStyle: TextStyle(color: Colors.redAccent),
border: new UnderlineInputBorder(
borderSide: BorderSide(
color: Color.fromRGBO(40, 40, 40, 1.0),),
borderRadius: BorderRadius.circular(10.0),),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Color.fromRGBO(40, 40, 40, 1.0),),
borderRadius: BorderRadius.circular(10.0),),
disabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Color.fromRGBO(40, 40, 40, 1.0),),
borderRadius: BorderRadius.circular(10.0),),
prefixIcon: new Icon(
Icons.playlist_add,
size: 18.0,),),),);
showDialog(
context: context,
builder: (context) {
return alert;
},);
},
Add You Trigger OnTap() on your image and then use this code to pop your Custom dialog by create your widget in Container
showDialog(
context: context,
builder: (_) => Container(
child: //add you custom widget here
));
How can I create an overlapping card effect in a SliverAppBar as shown in the figure:
I tried following this article of Medium but the problem is it uses Stack with
Stack{
...
overflow: Overflow.visible,
...
}
to implement this and in Stack, overflowing part of widgets don't take inputs thus making my TabBar half Dead.
What can I do to avoid this?
Full Code
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MenuList(),
);
}
}
class MenuList extends StatefulWidget {
#override
_MenuListState createState() => _MenuListState();
}
class _MenuListState extends State<MenuList> {
static const double _appBarBottomBtnPosition =
24.0; //change this value to position your button vertically
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text(
'Testing',
style: TextStyle(color: Colors.red),
),
),
SliverAppBar(
pinned: true,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
titlePadding: EdgeInsets.only(bottom: 25),
title: Text('Title'),
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(0.0),
child: Transform.translate(
offset: const Offset(0, _appBarBottomBtnPosition),
child: RaisedButton(
shape: StadiumBorder(),
child: Text("Click Here"),
onPressed: () {},
),
),
),
),
SliverPadding(
padding: EdgeInsets.only(top: _appBarBottomBtnPosition),
),
SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(context, index){
Color color = Colors.red.withOpacity(1- (index%10)/10);
return Container(
color: color,
alignment: Alignment.center,
child: Text("Color: $color"),
);
}
),
),
],
),
);
}
}
I'm working a little bit with Flutter and I understand everything except Animations (i never liked working with animations).
I've tried to implement a Backdrop in my Flutter app using this Flutter Demo. Implementing the Backdrop is easy.
I stuck on implementing the navigation of the Backdrop which let it slide down and up by the hamburger button.
I have read the Animations in Flutter Tutorial. I understood the basics of animations (controller, animation etc.). But in this Backdrop example, it is a little bit different.
Can someone explain to me this case step by step? Thanks.
I managed to reach a solution combining the information of the following links. Please note that I’m not an expert in Flutter (neither animations). That being said, suggestions and corrections are really appreciated.
Backdrop implementation:
https://github.com/material-components/material-components-flutter-codelabs/blob/104-complete/mdc_100_series/lib/backdrop.dart
Animation:
https://medium.com/#vigneshprakash15/flutter-image-rotate-animation-6b6eaed7fb33
After creating your backdrop.dart file, go to the _BackdropTitle class and edit the part where you define the IconButton for the menu and close icons. You have to perform a rotation in the Opacity item that contains the Icon:
icon: Stack(children: <Widget>[
new Opacity(
opacity: new CurvedAnimation(
parent: new ReverseAnimation(animation),
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.close),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * -6.3,
child: _widget,
);
},
),
),
new Opacity(
opacity: new CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.menu),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
),
],
I had to use a negative value in the angle of the close transformation in order to rotate it in the same direction of the menu animation.
And here is the full code:
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'menu.dart';
const double _kFlingVelocity = 2.0;
class Backdrop extends StatefulWidget {
final MenuCategory currentCategory;
final Widget frontLayer;
final Widget backLayer;
final Widget frontTitle;
final Widget backTitle;
const Backdrop({
#required this.currentCategory,
#required this.frontLayer,
#required this.backLayer,
#required this.frontTitle,
#required this.backTitle,
}) : assert(currentCategory != null),
assert(frontLayer != null),
assert(backLayer != null),
assert(frontTitle != null),
assert(backTitle != null);
#override
_BackdropState createState() => _BackdropState();
}
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin {
final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
value: 1.0,
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void didUpdateWidget(Backdrop old) {
super.didUpdateWidget(old);
if (widget.currentCategory != old.currentCategory) {
_toggleBackdropLayerVisibility();
} else if (!_frontLayerVisible) {
_controller.fling(velocity: _kFlingVelocity);
}
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
const double layerTitleHeight = 48.0;
final Size layerSize = constraints.biggest;
final double layerTop = layerSize.height - layerTitleHeight;
Animation<RelativeRect> layerAnimation = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, layerTop, 0.0, layerTop - layerSize.height),
end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(_controller.view);
return Stack(
key: _backdropKey,
children: <Widget>[
widget.backLayer,
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
onTap: _toggleBackdropLayerVisibility,
child: widget.frontLayer,
),
),
],
);
}
#override
Widget build(BuildContext context) {
var appBar = AppBar(
brightness: Brightness.light,
elevation: 0.0,
titleSpacing: 0.0,
title: _BackdropTitle(
animationController: _controller,
onPress: _toggleBackdropLayerVisibility,
frontTitle: widget.frontTitle,
backTitle: widget.backTitle,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
// TODO
},
)
],
);
return Scaffold(
appBar: appBar,
body: LayoutBuilder(builder: _buildStack)
);
}
bool get _frontLayerVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
void _toggleBackdropLayerVisibility() {
_controller.fling(
velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
}
}
class _FrontLayer extends StatelessWidget {
const _FrontLayer({
Key key,
this.onTap,
this.child,
}) : super(key: key);
final VoidCallback onTap;
final Widget child;
#override
Widget build(BuildContext context) {
return Material(
elevation: 16.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0)
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.0,
alignment: AlignmentDirectional.centerStart,
),
),
Expanded(
child: child,
),
],
),
);
}
}
class _BackdropTitle extends AnimatedWidget {
final AnimationController animationController;
final Function onPress;
final Widget frontTitle;
final Widget backTitle;
_BackdropTitle({
Key key,
this.onPress,
#required this.frontTitle,
#required this.backTitle,
#required this.animationController,
}) : assert(frontTitle != null),
assert(backTitle != null),
assert(animationController != null),
super(key: key, listenable: animationController.view);
#override
Widget build(BuildContext context) {
final Animation<double> animation = this.listenable;
return DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.title,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: Row(children: <Widget>[
// branded icon
SizedBox(
width: 72.0,
child: IconButton(
padding: EdgeInsets.only(right: 8.0),
onPressed: this.onPress,
icon: Stack(children: <Widget>[
new Opacity(
opacity: new CurvedAnimation(
parent: new ReverseAnimation(animation),
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.close),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * -6.3,
child: _widget,
);
},
),
),
new Opacity(
opacity: new CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
).value,
child: new AnimatedBuilder(
animation: animationController,
child: new Container(
child: new Icon(Icons.menu),
),
builder: (BuildContext context, Widget _widget) {
return new Transform.rotate(
angle: animationController.value * 6.3,
child: _widget,
);
},
),
),
]),
),
),
// Here, we do a custom cross fade between backTitle and frontTitle.
// This makes a smooth animation between the two texts.
Stack(
children: <Widget>[
Opacity(
opacity: CurvedAnimation(
parent: ReverseAnimation(animation),
curve: Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset.zero,
end: Offset(0.5, 0.0),
).evaluate(animation),
child: backTitle,
),
),
Opacity(
opacity: CurvedAnimation(
parent: animation,
curve: Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween<Offset>(
begin: Offset(-0.25, 0.0),
end: Offset.zero,
).evaluate(animation),
child: frontTitle,
),
),
],
)
]),
);
}
}
You can find a sample demo for backdrop in flutter gallery..which you will get while installing Flutter.