how to animate logo transition from appbar to drawer? - flutter

I want to have an animated staggered transition from the blue logo to the black logo when I open or slide the drawer.
Blue logo in SilverAppBar:
Black flutter logo:
I wonder if there is a way to make an animation to move the blue flutter logo to the drawer menu when clicking on hamburger menu or sliding the drawer and also while its moving i want it to change color, how can i do this?
Code:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
bool _pinned = true;
bool _snap = false;
bool _floating = false;
// [SliverAppBar]s are typically used in [CustomScrollView.slivers], which in
// turn can be placed in a [Scaffold.body].
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: IconButton(
icon: const Icon(Icons.menu,
size: 40), // change this size and style
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
),
actions: const [
Padding(
padding: EdgeInsets.fromLTRB(5, 20, 80, 5),
)
],
pinned: _pinned,
snap: _snap,
floating: _floating,
expandedHeight: 160.0,
flexibleSpace: const FlexibleSpaceBar(
title: FlutterLogo(
size: 100,
),
),
),
],
),
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Center(child: Image.asset("assets/cib-flutter.png")),
)
],
),
));
}
}
Edit
I think i should not use Drawer() entirely and should make a custom animated transition from the SilverAppBar view to the right?

use hero widget I think
Like this :
in AppBar :
Hero(
child: Image.asset('your_img_path'),
tag: 'logo',
)
in Drawer :
Hero(
child: Image.asset('your_img_path'),
tag: 'logo',
)
check that they are the same tag to make the animation

Related

How to constraint item position in flutter?

I try to make a list of widget. It look like this:
I know of no such thing as Constraint Layout in flutter. But I need something to position my arrow icon in a fixed position on the right. To put it simple, this is my widget code:
Row(
children:[
SizedBox(),
Column(),//this is all the item on the left
Spacer(),
Expanded(// this is the heart and arrow button
child: Column()
)
]
)
I notice that if my column on the left get too wide, my arrow and heart icon is shifted out of line.
How to put my icon in fixed position to the right?
here try this, You have to wrap middle column with expanded so it will take the maximum space available
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (_, index) => Container(
padding: EdgeInsets.all(15),
margin: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),border: Border.all(width: 1.5)),
child: Row(
children: [
Container(
width: 25,
height: 40,
color: Colors.black,
),
Expanded(
child: Column(children: [
//put your children here
]),
),
//this will be always on right
Column(
children: [
Icon(Icons.heart_broken),
Icon(Icons.chevron_right),
],
)
],
),
),
);
}
}

Flutter: Content behind AppBar when using FlexibleSpacebar

I'm trying to implement a collapsable AppBar, using a FlexibleSpaberBar and Tabs, the code "works" fine with the content of one of the tabs is a ListView, but, when the content is more static (inside a Column) and is smaller than the actual screen size, then when I scroll up in order to hide the FlexibleSpacebar, the content if the tab gets behind the app bar.
As you can see, the content of Tab1Widget is smaller than the screen size (although depending on the device actual screen size, it could be bigger than the screen size) and when I scroll up in order to hide the FlexibleSpaceBar, the content of Tab1Widget gets behind the AppBar
Attaching a working example to reproduce the problem.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
elevation: 0.0,
title: const Text("App Bar Demo"),
expandedHeight: 200.0,
floating: true,
pinned: true,
forceElevated: innerBoxIsScrolled,
flexibleSpace: const FlexibleSpaceBar(
background: Placeholder(),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(TabBar(
unselectedLabelColor: Colors.grey[700],
indicatorSize: TabBarIndicatorSize.label,
tabs: const [
Tab(
child: Align(
alignment: Alignment.center,
child: Text("Tab 1"),
),
),
Tab(
child: Align(
alignment: Alignment.center,
child: Text("Tab 2"),
),
),
])))
];
},
body: const TabBarView(
children: [
Tab1Widget(),
Tab2Widet(),
],
)),
),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
child: _tabBar,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
class Tab1Widget extends StatelessWidget {
const Tab1Widget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Builder(builder: (BuildContext context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverSafeArea(
sliver: SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverToBoxAdapter(
child: Container(
color: Colors.green,
child: Column(
children: const [
Text(
"Content 1",
style: TextStyle(color: Colors.white),
),
Text(
"Content 2",
style: TextStyle(color: Colors.white),
),
Text(
"Content 3",
style: TextStyle(color: Colors.white),
),
],
),
),
),
),
)
],
);
}),
);
}
}
class Tab2Widet extends StatelessWidget {
const Tab2Widet({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
Also, created a Dartpad here

How to add a fixed bar between a sliver bar and the scrolling content

I have a view in my flutter mobile app in which there is:
A top bar that is a sliver bar (title + tabs) [red box on image]
A scrollable list of data [green box on image]
When i scroll, the tabs disappear leaving only the page title.
Goal:
I want to add a header [blue box on image] that does not disappear when i scroll and stays between the sliver bar and the scrollable items.
I seem to only be able to put the header inside the title of the sliver bar above the tabs, or in the scrollable list in which case it disappears as i scroll
You can easily implement this on your own. The widget you will need is called SliverPersistentHeader with pinned parameter equal to true and custom implementation of SliverPersistentHeaderDelegate.
Take a look at this example:
https://dartpad.dartlang.org/6e0f6b4a212e572c7b74e5f301abc71b
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
floating: true,
delegate: SliverAppBarDelegate(
minHeight: 60,
maxHeight: 60,
child: Container(
color: Colors.red,
child: Center(
child: Text(
'This will hide'),
),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: SliverAppBarDelegate(
minHeight: 60.0,
maxHeight: 60.0,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: Text('This will remain visible',
style: TextStyle(fontSize: 20)),
),
Divider(),
],
),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return ListTile(
title: Text('List Tile $index'),
);
}, childCount: 100),
),
],
),
);
}
}
class SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
SliverAppBarDelegate({
#required this.minHeight,
#required this.maxHeight,
#required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
#override
double get minExtent => minHeight;
#override
double get maxExtent => math.max(maxHeight, minHeight);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
#override
bool shouldRebuild(SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
Ended up using a sticky header: https://pub.dev/packages/flutter_sticky_header
And keep the same header without changing it through the scroll

Align widget center with AppBar bottom edge

I need to align a widget's center with the appbar bottom edge.
So it will be located vertically half on the appbar and half on the page body.
Right now I've added the widget into the AppBar bottom: but it wont align with it's horizontal center line.
Currently It looks like this:
While i want that the center of the SelectEnvironment button along with the horizontal white line will 'sit' exactly on the bottom edge of the appBar
The code for the appBar is like this:
class CustomAppBar extends AppBar {
final Widget appBarActionButton;
CustomAppBar({Widget title = AppUtils.EMPTY_TEXT_VIEW, this.appBarActionButton}): super(
title: title,
backgroundColor: Colors.blueGrey,
elevation: 0,
bottom: PreferredSize(
child: Stack( //The stack holds the horizontal line and the button aligned cente
alignment: Alignment.center,
children: <Widget>[
Container( //This is the horizontal line
color: Colors.GeneralDividerGray,
height: 1.0,
),
Align(
child: Container(
child: appBarActionButton, //This is the button widget
),
)
],
),
preferredSize: Size.fromHeight(4.0)),
);
}
If there a better way to achieve this by taking it outside of the appbar it's ok with me as long it will give the same effect.
I think you should use Stack and Column like this
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
typedef void OnWidgetSizeChange(Size size);
class MeasureSize extends StatefulWidget {
final Widget child;
final OnWidgetSizeChange onChange;
const MeasureSize({
Key key,
#required this.onChange,
#required this.child,
}) : super(key: key);
#override
_MeasureSizeState createState() => _MeasureSizeState();
}
class _MeasureSizeState extends State<MeasureSize> {
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
return Container(
key: widgetKey,
child: widget.child,
);
}
var widgetKey = GlobalKey();
var oldSize;
void postFrameCallback(_) {
var context = widgetKey.currentContext;
if (context == null) return;
var newSize = context.size;
if (oldSize == newSize) return;
oldSize = newSize;
widget.onChange(newSize);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Size s;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Column(
children: [
MeasureSize(
onChange: (size) {
setState(() {
s = size;
});
},
child: AppBar(
title: Text('title'),
),
),
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - (s?.height ?? 0.0),
child: Center(child: Text('body')))
],
),
Positioned(
top: (s?.height ?? 0.0) - 16.0,
child: Container(
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 32,
color: Colors.red[400],
padding: EdgeInsets.all(6),
child: Center(child: Text('Select Environment'))),
],
),
),
)
],
));
}
}
The best way is to use Slivers via a widget like below:
ScrollController scrollController = new ScrollController();
return Stack(
children: [
NestedScrollView(
controller: scrollController,
headerSliverBuilder: (context, value){
return [
// list of widgets in here
];
},
body: Container(
// here, your normal body goes
),
),
Positioned(
top: 50.0,
left: 100.0,
child: Container(
// your centered widget here
),
)
]
);
}
Instead of using a normal appBar, you have to use a SliverAppBar

Flare Flutter Animations

Animations created with Flare Flutter (from 2dimensions.com) cannot switch between different animations of the same Flare Actor. If a black version is first, the white version will not display; if the white version is first, the black will display.
I am not sure if I am doing something wrong or if it is a bug. It can switch between colors, just not animations.
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
const List<String> animations = ['White', 'Black'];
const List<Color> colors = [Colors.blue, Colors.black];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Animation Tester',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Animation Tester'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int index = 0;
void switchAnimation() {
setState(() {
index = index < (animations.length - 1) ? index + 1 : 0;
});
}
#override
Widget build(BuildContext context) {
print(index);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView(
children: <Widget>[
GestureDetector(
onTap: switchAnimation,
child: Icon(
Icons.add,
size: 100.0,
)),
Container(
width: 200.0,
height: 200.0,
child: FlareActor(
'assets/color_wheel_loading.flr',
color: colors[index],
)),
Container(
width: 200.0,
height: 200.0,
child: FlareActor(
'assets/color_wheel_loading.flr',
animation: animations[index],
)),
Center(child: Text('$index'))
],
)),
);
}
}
I have tested your code with my own file, it works perfect. May be your animation names are not right, can you check.
Or you can test this file "https://www.2dimensions.com/a/whitewolfnegizzz/files/flare/pj" and using the code below.
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
const List<String> animations = ['Build and Fade Out', 'Build'];
const List<Color> colors = [Colors.blue, Colors.black];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Animation Tester',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Animation Tester'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int index = 0;
void switchAnimation() {
setState(() {
index = index < (animations.length - 1) ? index + 1 : 0;
});
}
#override
Widget build(BuildContext context) {
print(index);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView(
children: <Widget>[
GestureDetector(
onTap: switchAnimation,
child: Icon(
Icons.add,
size: 100.0,
)),
Container(
width: 200.0,
height: 200.0,
child: FlareActor(
'assets/color_wheel_loading.flr',
color: colors[index],
)),
Container(
width: 200.0,
height: 200.0,
child: FlareActor(
'assets/Pj.flr',
animation: animations[index],
)),
Center(child: Text('$index'))
],
)),
);
}
}
From what i observed, Flare animation names are case sensitive. If the animation names in the flare project are in lowercase, the animation property of your flare actor should also be in lowercase.
Add flr in assets.
initialize it in Pubsepec.yaml file
then add dependency of flare animation in pubsepec.yaml file
after this,
just use this code in your main file
Container(
height: MediaQuery.of(context).size.height *0.8,
child: FlareActor(
'assets/oncemore.flr',
animation: 'Celebrate Duplicate', // Check this, when you are downloading flr file from Flare 2D dimension website
fit: BoxFit.contain,
),
),
After this , Your Flare animation will work perfectly.