I'm trying to make this particular UI and I can't figure out how to create that specific BoxShadow design in the image above. The BoxShadow that I have created is a bit near the center but, the UI that I want to create is more leaned diagonally.
This is my main.dart file.
import 'package:flutter/material.dart';
import 'package:secondlife_mobile/PageViewHolder.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Perspective PageView',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late PageViewHolder holder;
late PageController _controller;
double fraction =
0.57; // By using this fraction, we're telling the PageView to show the 50% of the previous and the next page area along with the main page
#override
void initState() {
super.initState();
holder = PageViewHolder(value: 2.0);
_controller = PageController(initialPage: 2, viewportFraction: fraction);
_controller.addListener(() {
holder.setValue(_controller.page);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text("Perspective PageView"),
),
body: Container(
child: Center(
child: AspectRatio(
aspectRatio: 1,
child: ChangeNotifierProvider<PageViewHolder>.value(
value: holder,
child: PageView.builder(
controller: _controller,
itemCount: 4,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return MyPage(
number: index.toDouble(),
fraction: fraction,
);
}),
),
),
),
),
));
}
}
class MyPage extends StatelessWidget {
final number;
final double? fraction;
const MyPage({super.key, this.number, this.fraction});
#override
Widget build(BuildContext context) {
double? value = Provider.of<PageViewHolder>(context).value;
double diff = (number - value);
// diff is negative = left page
// diff is 0 = current page
// diff is positive = next page
//Matrix for Elements
final Matrix4 pvMatrix = Matrix4.identity()
..setEntry(3, 2, 1 / 0.9) //Increasing Scale by 90%
..setEntry(1, 1, fraction!) //Changing Scale Along Y Axis
..setEntry(3, 0, 0.004 * -diff); //Changing Perspective Along X Axis
return Stack(
fit: StackFit.expand,
alignment: FractionalOffset.center,
children: [
Transform(
transform: pvMatrix,
alignment: FractionalOffset.center,
child: Container(
decoration: const BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 2.0,
spreadRadius: 4.0,
offset: Offset(8.0, 20.0), // shadow direction: bottom right
)
]),
child: Image.asset("assets/images/image_${number.toInt() + 1}.jpg",
fit: BoxFit.fill),
),
),
],
);
}
}
And this is how it looks like on the emulator.
Related
When I scroll through one List Wheel Scroll View, the other list either lags or does not scroll smoothly.
https://pub.dev/packages/linked_scroll_controller allows to sync lists but does not support FixedExtendScrollPhysics.
Output : -
https://pub.dev/packages/linked_scroll_controller works perfectly if we are using ScrollPhysics but throws an error when used with a widget that uses FixedExtendScrollPhysics. I want both the list to move Synchronizing that is if I move green list I want red list to move simultaneously and vice versa
Code :
import 'dart:math';
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: 'List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const List(),
);
}
}
class List extends StatefulWidget {
const List({Key? key}) : super(key: key);
#override
_ListState createState() => _ListState();
}
class _ListState extends State<List> {
final scrollController = FixedExtentScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List"),
backgroundColor: Colors.green,
),
body: Row(
children: [
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
itemExtent: 100,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: (value) {
setState(() {
scrollController.animateToItem(value,
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut);
});
},
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.green,
height: 50,
width: 50,
)
]
]),
),
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
controller: scrollController,
physics: const FixedExtentScrollPhysics(),
itemExtent: 100,
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.red,
height: 50,
width: 50,
)
]
]),
)
],
));
}
}
Really interesting question. The problem was syncing both the scrollviews. I made few changes to your code to achieve the desired result.
The basic idea is to remove listener to the other scroll before forcing pixels. After the scroll, add the same listener. But because it happens instantaneously and actual scroll happens sometimes in future, they don't overlap perfectly.
So I had to introduce CancelableCompleter from the async library to make sure add operation does not happen if another scroll event had happened.
With forcePixels, the scrolling to other wheel is not deferred hence CancelableCompleter is not required.
// ignore_for_file: invalid_use_of_protected_member
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: 'List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const List(),
);
}
}
class List extends StatefulWidget {
const List({Key? key}) : super(key: key);
#override
_ListState createState() => _ListState();
}
class _ListState extends State<List> {
final _firstScrollController = FixedExtentScrollController();
final _secondScrollController = FixedExtentScrollController();
#override
void initState() {
super.initState();
_firstScrollController.addListener(_firstScrollListener);
_secondScrollController.addListener(_secondScrollListener);
}
#override
void dispose() {
_firstScrollController
..removeListener(_firstScrollListener)
..dispose();
_secondScrollController
..removeListener(_secondScrollListener)
..dispose();
super.dispose();
}
void _firstScrollListener() {
_secondScrollController.removeListener(_secondScrollListener);
_secondScrollController.position.forcePixels(_firstScrollController.offset);
_secondScrollController.addListener(_secondScrollListener);
}
void _secondScrollListener() {
_firstScrollController.removeListener(_firstScrollListener);
_firstScrollController.position.forcePixels(_secondScrollController.offset);
_firstScrollController.addListener(_firstScrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List"),
backgroundColor: Colors.green,
),
body: Row(
children: [
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
itemExtent: 100,
controller: _firstScrollController,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: (value) {
print('first wheel : item selected: $value');
},
children: [
for (int i = 0; i < 25; i++) ...[
Container(
color: Colors.green,
height: 50,
width: 50,
)
]
]),
),
SizedBox(
height: 600,
width: 300,
child: ListWheelScrollView(
controller: _secondScrollController,
physics: const FixedExtentScrollPhysics(),
itemExtent: 100,
onSelectedItemChanged: (value) {
print('second wheel : item selected: $value');
},
children: [
for (int i = 0; i < 25; i++) ...[
Container(
color: Colors.red,
height: 50,
width: 50,
)
]
]),
)
],
));
}
}
I am using protective member function forcePixels as Flutter has not provided any way to set pixels without animation without creating subclass of ScrollPosition. If you are fine with this linter warning, it is all good. If not, we will have to extend ListWheelScrollView to use ScrollPosition where we could make changes as per need.
Try separate two controller and add listener like this:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const List(),
);
}
}
class List extends StatefulWidget {
const List({Key? key}) : super(key: key);
#override
_ListState createState() => _ListState();
}
class _ListState extends State<List> {
final scrollController1 = FixedExtentScrollController();
final scrollController2 = FixedExtentScrollController();
#override
void initState() {
super.initState();
scrollController1.addListener(() {
if (scrollController1.position.hasPixels) {
scrollController2.animateTo(
scrollController1.offset,
duration: const Duration(milliseconds: 10), //adjust delay you need
curve: Curves.easeInOut,
);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List"),
backgroundColor: Colors.green,
),
body: Row(
children: [
SizedBox(
height: 600,
width: 150,
child: ListWheelScrollView(
itemExtent: 100,
controller: scrollController1,
physics: const FixedExtentScrollPhysics(),
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.green,
height: 50,
width: 50,
)
]
]),
),
SizedBox(
height: 600,
width: 150,
child: ListWheelScrollView(
controller: scrollController2,
physics: const FixedExtentScrollPhysics(),
itemExtent: 100,
children: [
for (int i = 0; i < 5; i++) ...[
Container(
color: Colors.red,
height: 50,
width: 50,
)
]
]),
)
],
));
}
}
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ScrollController _controllerA;
late ScrollController _controllerB;
/// FixedExtentScrollController
/// late FixedExtentScrollController _controllerA;
/// late FixedExtentScrollController _controllerB;
#override
void initState() {
super.initState();
_controllerA = ScrollController();
_controllerB = ScrollController();
/// _controllerA = FixedExtentScrollController();
/// _controllerB = FixedExtentScrollController();
_controllerA.addListener(() {
if (_controllerA.position.hasPixels) {
_controllerB.jumpTo(_controllerA.offset);
}
});
/// if you neet bind _controllerB to _controllerA
/// _controllerB.addListener(() {
/// if (_controllerB.position.hasPixels) {
/// _controllerA.jumpTo(_controllerB.offset);
/// }
///});
}
#override
void dispose() {
_controllerA.dispose();
_controllerB.dispose();
super.dispose();
}
Widget _listView(Color color, ScrollController controller) {
var width = MediaQuery.of(context).size.width / 2;
return Container(
width: width,
child: ListView.builder(
controller: controller,
shrinkWrap: true,
itemCount: 100,
itemExtent: 50,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
color: color, border: Border.all(color: Colors.white)),
);
}),
);
}
/// ListWheelScrollView
/// Widget _listView(Color color, FixedExtentScrollController controller) {
/// var width = MediaQuery.of(context).size.width / 2;
/// return Container(
/// width: width,
/// child: ListWheelScrollView(
/// physics: FixedExtentScrollPhysics(),
/// controller: controller,
/// itemExtent: 50,
/// children: [
/// ...List.generate(
/// 100,
/// (index) => Container(
/// decoration: BoxDecoration(
/// color: color,
/// border: Border.all(color: Colors.white)),
/// ))
/// ]),
/// );
/// }
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Row(
children: [
_listView(Colors.red, _controllerA),
_listView(Colors.yellow, _controllerB),
],
)),
);
}
}
Hero animation is the best for navigating between screen, but I need same animation between widgets. Like one card moving another place for example: Product Card moves to shoppingcart and something else. Thanks for answers!
Try this one, add_to_cart_animation:
import 'package:add_to_cart_animation/add_to_cart_animation.dart';
import 'package:add_to_cart_animation/add_to_cart_icon.dart';
import 'package:flutter/material.dart';
import 'list_item.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: 'Add To Cart Animation',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Add To Cart Animation'),
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> {
// We can detech the location of the card by this GlobalKey<CartIconKey>
GlobalKey<CartIconKey> gkCart = GlobalKey<CartIconKey>();
late Function(GlobalKey) runAddToCardAnimation;
var _cartQuantityItems = 0;
#override
Widget build(BuildContext context) {
return AddToCartAnimation(
// To send the library the location of the Cart icon
gkCart: gkCart,
rotation: true,
dragToCardCurve: Curves.easeIn,
dragToCardDuration: const Duration(milliseconds: 1000),
previewCurve: Curves.linearToEaseOut,
previewDuration: const Duration(milliseconds: 500),
previewHeight: 30,
previewWidth: 30,
opacity: 0.85,
initiaJump: false,
receiveCreateAddToCardAnimationMethod: (addToCardAnimationMethod) {
// You can run the animation by addToCardAnimationMethod, just pass trough the the global key of the image as parameter
this.runAddToCardAnimation = addToCardAnimationMethod;
},
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
centerTitle: false,
actions: [
// Improvement/Suggestion 4.4 -> Adding 'clear-cart-button'
IconButton(
icon: Icon(Icons.cleaning_services),
onPressed: () {
_cartQuantityItems = 0;
gkCart.currentState!.runClearCartAnimation();
},
),
SizedBox(width: 16),
AddToCartIcon(
key: gkCart,
icon: Icon(Icons.shopping_cart),
colorBadge: Colors.red,
),
SizedBox(
width: 16,
)
],
),
body: ListView(
children: [
AppListItem(onClick: listClick, index: 1),
AppListItem(onClick: listClick, index: 2),
AppListItem(onClick: listClick, index: 3),
AppListItem(onClick: listClick, index: 4),
AppListItem(onClick: listClick, index: 5),
AppListItem(onClick: listClick, index: 6),
AppListItem(onClick: listClick, index: 7),
],
),
),
);
}
// Improvement/Suggestion 4.4 -> Running AddTOCartAnimation BEFORE runCArtAnimation
void listClick(GlobalKey gkImageContainer) async {
await runAddToCardAnimation(gkImageContainer);
await gkCart.currentState!.runCartAnimation((++_cartQuantityItems).toString());
}
}
OR
[not null safety]
this is a sample of add to cart, add_cart_parabola:
import 'dart:ui';
import 'package:add_cart_parabola/add_cart_parabola.dart';
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(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> {
int _counter = 0;
GlobalKey floatKey = GlobalKey();
GlobalKey rootKey = GlobalKey();
Offset floatOffset ;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
RenderBox renderBox = floatKey.currentContext.findRenderObject();
floatOffset = renderBox.localToGlobal(Offset.zero);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
key: rootKey,
width: double.infinity,
height: double.infinity,
color: Colors.grey,
child: ListView(
children: List.generate(40, (index){
return generateItem(index);
}).toList(),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.yellow,
key: floatKey,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Widget generateItem(int index){
Text text = Text("item $index",style: TextStyle(fontSize:
25),);
Offset temp;
return GestureDetector(
onPanDown: (details){
temp = new Offset(details.globalPosition.dx, details.globalPosition
.dy);
},
onTap: (){
Function callback ;
setState(() {
OverlayEntry entry = OverlayEntry(
builder: (ctx){
return ParabolaAnimateWidget(rootKey,temp,floatOffset,
Icon(Icons.cancel,color: Colors.greenAccent,),callback,);
}
);
callback = (status){
if(status == AnimationStatus.completed){
entry?.remove();
}
};
Overlay.of(rootKey.currentContext).insert(entry);
});
},
child: Container(
color: Colors.orange,
child: text,
),
);
}
}
For animating widget in the same screen you can use AnimatedPositioned widget see the below code
import 'dart:math';
import 'package:flutter/material.dart';
class AnimatedPositionedDemo extends StatefulWidget {
const AnimatedPositionedDemo({Key? key}) : super(key: key);
static String routeName = 'animated_positioned';
#override
_AnimatedPositionedDemoState createState() => _AnimatedPositionedDemoState();
}
class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
late double topPosition;
late double leftPosition;
double generateTopPosition(double top) => Random().nextDouble() * top;
double generateLeftPosition(double left) => Random().nextDouble() * left;
#override
void initState() {
super.initState();
topPosition = generateTopPosition(30);
leftPosition = generateLeftPosition(30);
}
void changePosition(double top, double left) {
setState(() {
topPosition = generateTopPosition(top);
leftPosition = generateLeftPosition(left);
});
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final appBar = AppBar(title: const Text('AnimatedPositioned'));
final topPadding = MediaQuery.of(context).padding.top;
// AnimatedPositioned animates changes to a widget's position within a Stack
return Scaffold(
appBar: appBar,
body: SizedBox(
height: size.height,
width: size.width,
child: Stack(
children: [
AnimatedPositioned(
top: topPosition,
left: leftPosition,
duration: const Duration(seconds: 1),
child: InkWell(
onTap: () => changePosition(
size.height -
(appBar.preferredSize.height + topPadding + 50),
size.width - 150),
child: Container(
alignment: Alignment.center,
width: 150,
height: 50,
child: Text(
'Click Me',
style: TextStyle(
color:
Theme.of(context).buttonTheme.colorScheme!.onPrimary,
),
),
color: Theme.of(context).primaryColor,
),
),
),
],
),
),
);
}
}
I hope it works for you
For Animated widgets, flutter team has provided a video on youtube here
And you can read all about them on their website here
I am trying to create animated screens in which for example, when the screen is loaded, each one would contain a column with widgets. What I would like to do is, when the screen is loaded, have each widget in the column float in from off screen (from bottom) in the order in which they appear.
I have been searching but cannot seem to find a solution. How may I achieve this?
I've done something that might be what you want. However, to make it so each element animates after the last one for itself and not all together I had to use the ListView.builder to get the index. You can use the Column widget if you can find out the index of the element.
Here is the code:
The home screen:
import 'package:flutter/material.dart';
import 'package:testing/fade_in_from_bottom.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
final List<Widget> children = <Widget>[
Container(
height: 32.0,
color: Colors.amber,
),
Container(
height: 32.0,
color: Colors.black,
),
Container(
height: 32.0,
color: Colors.purple,
),
Container(
height: 32.0,
color: Colors.green,
),
Container(
height: 32.0,
color: Colors.indigo,
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: children.length,
itemBuilder: (BuildContext context, int index) {
return FadeInFromBottom(
key: UniqueKey(), // this is very important
index: index,
child: children[index],
);
},
),
),
],
),
);
}
}
Here is the FadeInFromBottom widget I made:
import 'package:flutter/material.dart';
class FadeInFromBottom extends StatefulWidget {
#override
_FadeInFromBottomState createState() => _FadeInFromBottomState();
final Key key;
final Duration animationDuration;
final Duration offsetDuration;
final Widget child;
final int index;
FadeInFromBottom({
#required this.key,
#required this.child,
#required this.index,
this.animationDuration = const Duration(milliseconds: 400),
this.offsetDuration = const Duration(milliseconds: 800),
}) : super(key: key); // this line is important
}
// How to add AutomaticKeepAliveClientMixin? Follow steps 1, 2 and 3:
// 1. add AutomaticKeepAliveClientMixin to FadeInFromBottom widget State
class _FadeInFromBottomState extends State<FadeInFromBottom>
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true; // 2. add this line
double progress = 0.0;
Animation<double> animation;
AnimationController controller;
#override
void initState() {
super.initState();
final Duration offsetDuration =
widget.offsetDuration == null ? 0.0 : widget.offsetDuration;
final int index = widget.index == null ? 0 : widget.index;
// we await the future to create the animation delay
Future.delayed(offsetDuration * index).then(
(_) {
controller = AnimationController(
duration: widget.animationDuration, vsync: this);
animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller,
curve: Curves.linear,
),
)..addListener(() {
setState(() => progress = animation.value);
});
controller.forward();
},
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context); // 3. add this line
return Opacity(
opacity: progress,
child: Transform.translate(
offset: Offset(
0.0,
(1.0 - progress) * 999.0,
),
child: widget.child,
),
);
}
}
Don't forget to add the key: UniqueKey() property, without it the animations will be messed up.
I want to make something very simple. There's a Row with 2 widgets. When I press a button, they swap orders. I want this order swap to be animated.
I've loked at AnimatedPositioned but it requires a Stack. What would be the best way of doing such thing?
I thought Animating position across row cells in Flutter answered this but it's another different problem
You can easily animate widgets in a Row with SlideAnimation. Please see the code below or you may directly run the code on DartPad https://dartpad.dev/e5d9d2c9c6da54b3f76361eac449ce42 Just tap on the colored box to swap their positions with an slide animation.
SlideAnimation
Animates the position of a widget relative to its normal position.
The translation is expressed as an Offset scaled to the child's size.
For example, an Offset with a dx of 0.25 will result in a horizontal
translation of one quarter the width of the child.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
List<Animation<Offset>> _offsetAnimation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_offsetAnimation = List.generate(
2,
(index) => Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: Offset(index == 0 ? 1 : -1, 0.0),
).animate(_controller),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
void _animate() {
_controller.status == AnimationStatus.completed
? _controller.reverse()
: _controller.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Flutter Demo Row Animation")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BoxWidget(
callBack: _animate,
text: "1",
color: Colors.red,
position: _offsetAnimation[0],
),
BoxWidget(
callBack: _animate,
text: "2",
color: Colors.blue,
position: _offsetAnimation[1],
)
],
),
RaisedButton(
onPressed: _animate,
child: const Text("Swap"),
)
],
),
),
);
}
}
class BoxWidget extends StatelessWidget {
final Animation<Offset> position;
final Function callBack;
final String text;
final Color color;
const BoxWidget(
{Key key, this.position, this.callBack, this.text, this.color})
: super(key: key);
#override
Widget build(BuildContext context) {
return SlideTransition(
position: position,
child: GestureDetector(
onTap: () => callBack(),
child: Container(
margin: const EdgeInsets.all(10),
height: 50,
width: 50,
color: color,
child: Center(
child: Container(
height: 20,
width: 20,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Center(child: Text(text)),
),
),
),
),
);
}
}
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