Flutter: Increase hitbox of GestureDetector - flutter

I am fairly new to flutter and currently trying to create a NavBar.
It looks like this:
If I click on the icon, the bar moves to the selected one and the content changes.
However, I have to hit the icon perfectly. I would like to have a "box" around it, so I can tap just near it. Basically divide the space into 3.
I tried the following:
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
The items should take 1/3 of the width. It isn't working that way tho. Any idea on how to increase the "tappable" space?
EDIT
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.\
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var screens = [Text("Button1"), Text("Button2"), Text("Button3")];
int currentScreen = 0;
void changeIndex(int index) => setState(() {
currentScreen = index;
});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.orange,
child: Stack(
children: [
SafeArea(child: screens[currentScreen]),
Container(
alignment: Alignment.bottomCenter, child: NavBar(changeIndex))
],
),
),
);
}
}
class MenuItem {
final String name;
final Color color;
final double offset;
MenuItem(this.name, this.color, this.offset);
}
class NavBar extends StatefulWidget {
#override
State<StatefulWidget> createState() => NavBarState(navBarUpdate);
late Function(int) navBarUpdate;
NavBar(this.navBarUpdate);
}
class NavBarState extends State<NavBar> {
var items = [
MenuItem("Test", Colors.red, -0.76),
MenuItem("Test2", Colors.green, 0),
MenuItem("Test3", Colors.yellow, 0.76)
];
late MenuItem active;
late Function(MenuItem) navBarUpdate;
#override
void initState() {
super.initState();
active = items[0];
}
NavBarState(Function(int) navBarUpdate) {
this.navBarUpdate = (item) {
navBarUpdate(items.indexOf(item));
};
}
#override
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
}

You can use behavior: HitTestBehavior.translucent, or opaque on createNavBarItem
child: GestureDetector(
behavior: HitTestBehavior.translucent,
You can swap your GestureDetector on top level widget from Icon.
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return GestureDetector(
child: Container(
color: Colors.transparent,
width: width / items.length,
height: 55,
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
);
}

Related

How do I schedule widget deletion as a future event?

I am looking for a way to do widget deletion in the future.
It's easiest to describe the problem through an example (and a MWE).
The user is presented with several AnimatedPositioneds containers, representing a card game.
The PositionedContainer part means that each card can be used for Gin Rummy, Bridge, or, in fact, any abstract numbers card game.
When the user clicks one card, the card slides up (using the Animated part of AnimatedContainer)
and then we'd like the card to be removed from the stack of widgets, i.e. to "disappear" (and not just hide through opacity)
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Cards'),
),
body: Center(
child: Container(
alignment: Alignment.center,
child: CardGameWidget(),
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
),
),
),
);
}
}
class CardGameWidget extends StatefulWidget {
#override
CardGameWidgetState createState() => CardGameWidgetState();
}
class CardGameWidgetState extends State<CardGameWidget> {
List<Card> cards = [];
CardGameWidgetState() {
for (var i = 0; i < 5; ++i) {
this.cards.add(Card(
offset: Offset(i * 100.0, 200),
number: Random().nextInt(1 << 16))
);
}
}
Function onTap(int index) => (newOffset) {
setState(() {
cards[index].offset += Offset(0,-100);
});
};
#override
Widget build(BuildContext context) {
List<CardWidget> cardWidgets = [];
for (int i = 0; i < this.cards.length; ++i) {
cardWidgets.add(CardWidget(
onTap: onTap(i),
offset: this.cards[i].offset,
number: this.cards[i].number,
));
}
return Stack(children: cardWidgets);
}
}
class Card {
Card({this.offset, this.number});
Offset offset;
int number;
}
class CardWidget extends StatelessWidget {
CardWidget({
Key key,
this.onTap,
this.offset,
this.number,
});
final Function onTap;
final Offset offset;
final int number;
_handleTap(details) {
onTap(details.globalPosition);
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: this.offset.dx,
top: this.offset.dy,
width: 100,
height: 100,
duration: Duration(seconds: 1),
child: GestureDetector(
onTapUp: _handleTap,
child: Container(
color: Colors.cyan,
padding: EdgeInsets.all(10),
margin: EdgeInsets.all(10),
child: FittedBox(
clipBehavior: Clip.antiAlias,
alignment: Alignment.centerLeft,
fit: BoxFit.contain,
child: Text(this.number.toString()),
))),
);
}
}
How do I schedule widget deletion as a future event, after the completion of an animation?
You can look into AnimiatedList:
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, brightness: Brightness.dark),
home: SimpleAnimatedList(),
);
}
}
class SimpleAnimatedList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SliceAnimatedList(),
);
}
}
class SliceAnimatedList extends StatefulWidget {
#override
_SliceAnimatedListState createState() => _SliceAnimatedListState();
}
class _SliceAnimatedListState extends State<SliceAnimatedList> {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
List<int> _items = [];
int counter = 0;
Widget slideIt(BuildContext context, int index, animation) {
int item = _items[index];
TextStyle textStyle = Theme.of(context).textTheme.headline4;
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset(0, 0),
).animate(animation),
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Container(
height: double.infinity,
child: AnimatedList(
key: listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return slideIt(context, index, animation);
},
),
),
),
Container(
decoration: BoxDecoration(color: Colors.greenAccent),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
listKey.currentState.insertItem(0,
duration: const Duration(milliseconds: 500));
_items = []
..add(counter++)
..addAll(_items);
});
},
child: Text(
"Add item to first",
style: TextStyle(color: Colors.black, fontSize: 20),
),
),
FlatButton(
onPressed: () {
if (_items.length <= 1) return;
listKey.currentState.removeItem(
0, (_, animation) => slideIt(context, 0, animation),
duration: const Duration(milliseconds: 500));
setState(() {
_items.removeAt(0);
});
},
child: Text(
"Remove first item",
style: TextStyle(color: Colors.black, fontSize: 20),
),
)
],
),
),
],
);
}
}

Access variable built in different file/class

I have file.a which contains an array:
final _likes = <String>[];
Based on answers selected by the user, items are added to this array.
I want to then be able to, at the press of a button, display the items in the array to the user.
The issue is that the button I want the users to press is defined in file.b (as the Icon is on the AppBar).
How can I give file.b the ability to see the variable _likes and access the data in it, when it lives in file.a?
file.a:
import 'package:flutter/material.dart';
import './main.dart';
class Images extends StatefulWidget {
#override
_ImagesState createState() => _ImagesState();
}
class _ImagesState extends State<Images> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
#override
// ignore: must_call_super
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
);
_animation = Tween(
begin: 0.0,
end: 1.0,
).animate(_controller);
}
#override
dispose() {
_controller.dispose();
super.dispose();
}
int index = 0;
final likes = <String>[];
#override
Widget build(BuildContext context) {
_controller.forward();
return GestureDetector(
onHorizontalDragEnd: (DragEndDetails dragEndDetails) {
if (dragEndDetails.primaryVelocity == 0) return;
if (dragEndDetails.primaryVelocity.compareTo(0) == -1)
setState(() {
_controller.reset();
dateIdeas.removeAt(index);
});
else
setState(() {
_controller.reset();
likes.add(dateIdeas[index]['Description']);
dateIdeas.removeAt(index);
});
},
child: new Column(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: FadeTransition(
opacity: _animation,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.pink[200],
width: 7,
),
borderRadius: BorderRadius.circular(9),
),
child: new Container(
child: new Image.asset(dateIdeas[index]['Image']),
),
),
),
),
Container(
alignment: Alignment.topCenter,
child: Text(dateIdeas[index]['Description'],
style: TextStyle(
fontSize: 30,
color: Colors.black,
fontFamily: 'IndieFlower',
)),
),
],
),
],
)
],
),
);
}
}
file.b:
import 'package:flutter/material.dart';
import './surprises.dart';
import './images.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SO Surprises',
theme: ThemeData(
primaryColor: Colors.pink[200],
),
home: MyHomePage(title: ''),
);
}
}
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 ideaCount = 1;
int _swipeCount = 0;
void _swipe() {
setState(() {
_swipeCount++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
Container(
height: 150,
width: 150,
child: IconButton(
icon: Image.asset('assets/images/SO.png'),
padding:
const EdgeInsets.only(right: 40.0, top: 10, bottom: 10.0),
),
),
GestureDetector(
onTap: () => print(Images.likes),
child: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Icon(
Icons.star,
color: Colors.white,// add custom icons also
),
),
),
],
),
body: _swipeCount == 0
? Stack(
children: <Widget>[
GestureDetector(
onHorizontalDragEnd: (DragEndDetails dragEndDetails) {
if(dragEndDetails.primaryVelocity == 0) return;
_swipe();
},
child: Container(
color: Colors.transparent,
alignment: Alignment.center,
child: Text("Swipe to get started! $_swipeCount"),
),
),
],
)
: Template(),
);
}
}
By prefixing your likes list with an underscore (_), it means you are making that method only accessible inside the class it belongs too.
To be able to use the method in other parts of your program, remove the underscore(_).
After removing the _, you are making the likes list accessible from other parts of your code.

Size to up animation for widgets

In Flutter you suppose I have a simple Container and I would like to change the size of that to up, for example in this simple screenshot I want to change top container in section 1 to up to have a top container in section 2
and top container in section 1 should behave only 100.0 after size to up
container B in section 1 and section 2 are in the same axis without change position when container A will be resized to up
for example, this is what I want to have
I'm not sure with which one animation I can have this feature
this code work, but this is not what I want to have.
i want to have draggable bottom sheet with changing border radius when bottom sheet arrived to top of screen like with pastes sample video screen and fade0n/out widget inside appbar which that inside top of bottom sheet, which that visible when bottom sheet arrived top or hide when bottom sheet don't have maximum size
import 'package:flutter/material.dart';
void main()=>runApp(SizeUp());
class SizeUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'test',
home: SizeUpAnim(),
);
}
}
class SizeUpAnim extends StatefulWidget {
#override
State<StatefulWidget> createState() =>_SizeUpAnim();
}
class _SizeUpAnim extends State with SingleTickerProviderStateMixin {
AnimationController _controller;
// ignore: constant_identifier_names
static const _PANEL_HEADER_HEIGHT = 32.0;
bool get _isPanelVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 100), value: 1.0, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 8.0,
title: const Text("Step4"),
leading: IconButton(
onPressed: () {
_controller.fling(velocity: _isPanelVisible ? -1.0 : 1.0);
},
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller.view,
),
),
),
body: Column(
children: <Widget>[
Expanded(
child: LayoutBuilder(
builder: _buildStack,
),
),
Text('aaa'),
],
),
);
}
Animation<RelativeRect> _getPanelAnimation(BoxConstraints constraints) {
final double height = constraints.biggest.height;
final double top = height - _PANEL_HEADER_HEIGHT;
const double bottom = -_PANEL_HEADER_HEIGHT;
return RelativeRectTween(
begin: RelativeRect.fromLTRB(0.0, top, 0.0, bottom),
end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate( CurvedAnimation(parent: _controller, curve: Curves.linear));
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Animation<RelativeRect> animation = _getPanelAnimation(constraints);
final ThemeData theme = Theme.of(context);
return Container(
color: theme.primaryColor,
child: Stack(
children: <Widget>[
const Center(
child: Text("base"),
),
PositionedTransition(
rect: animation,
child: Material(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0)),
elevation: 12.0,
child: Container(
height: _PANEL_HEADER_HEIGHT,
child: const Center(child: Text("panel")),
),
),
),
],
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
}
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLong = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('hey'),
RaisedButton(
onPressed: () {
setImages();
setState(() {
isLong = !isLong;
});
},
child: Text(isLong ? 'long' : 'short'),
),
RaisedButton(
onPressed: _onPressed,
child: Text('open'),
)
],
),
),
);
}
_onPressed() {
Navigator.of(context)
.push(TransparentRoute(builder: (context) => NewWidget(images)));
}
List<String> images = List.generate(
5,
(_) => 'http://placeimg.com/100/100/any',
);
void setImages() {
images = List.generate(
isLong ? 5 : 25,
(_) => 'http://placeimg.com/100/100/any',
);
}
}
class NewWidget extends StatefulWidget {
const NewWidget(this.images, {Key key}) : super(key: key);
final List<String> images;
#override
_NewWidgetState createState() => _NewWidgetState();
}
class _NewWidgetState extends State<NewWidget> {
bool isBig = false;
bool isStack = false;
bool isBounsing = true;
final double topOffset = 200;
final double miniHandleHeigh = 30;
double safeAreaPadding = 0;
double startBigAnimationOffset;
double startStickyOffset;
double backgroundHeight = 0;
double get savedAppBarHeigh => safeAreaPadding + kToolbarHeight;
final ScrollController controller = ScrollController();
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
super.initState();
}
void _afterLayout(_) {
var media = MediaQuery.of(context);
safeAreaPadding = media.padding.top;
startBigAnimationOffset = topOffset - savedAppBarHeigh;
startStickyOffset = startBigAnimationOffset + 20;
backgroundHeight = media.size.height - miniHandleHeigh - topOffset;
var canScroll = !_isImageSizeBiggerThenBottomSheetSize(
media.size.width,
media.size.height,
);
controller.addListener(
canScroll ? withoutScrolling : withScrolling,
);
}
void withoutScrolling() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else {
controller.animateTo(
0,
duration: Duration(milliseconds: 100),
curve: Curves.easeIn,
);
}
}
void withScrolling() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else if (offset < startBigAnimationOffset && isBig) {
setState(() {
isBig = false;
});
} else if (offset > startBigAnimationOffset && !isBig) {
setState(() {
isBig = true;
});
} else if (offset > startStickyOffset && !isStack) {
setState(() {
isStack = true;
});
} else if (offset < startStickyOffset && isStack) {
setState(() {
isStack = false;
});
}
if (offset < topOffset && !isBounsing) {
setState(() => isBounsing = true);
} else if (offset > topOffset && isBounsing) {
setState(() => isBounsing = false);
}
}
void goOut() {
controller.dispose();
Navigator.of(context).pop();
}
_isImageSizeBiggerThenBottomSheetSize(
double screenWidth,
double screenHeight,
) {
// padding: 10, 3 in row;
print(screenHeight);
var itemHeight = (screenWidth - 20) / 3;
print(itemHeight);
var gridMaxHeight = screenHeight - topOffset - miniHandleHeigh;
print(gridMaxHeight);
return (widget.images.length / 3).floor() * itemHeight > gridMaxHeight;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: isStack ? Colors.white : Colors.transparent,
body: Stack(
children: [
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
constraints: BoxConstraints(minHeight: backgroundHeight),
decoration: BoxDecoration(
color: Colors.white,
),
),
),
ListView(
physics:
isBounsing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
controller: controller,
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
height: topOffset,
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
duration: Duration(milliseconds: 500),
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 15),
child: Container(
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.circular(2),
),
height: 5,
width: 60,
),
),
),
builder: (_, number, child) {
return Container(
height: savedAppBarHeigh * number + miniHandleHeigh,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular((1 - number) * 20)),
color: Colors.white,
),
child: Opacity(opacity: 1 - number, child: child),
);
}),
),
Container(
padding: EdgeInsets.all(10),
constraints: BoxConstraints(
minHeight:
MediaQuery.of(context).size.height - savedAppBarHeigh),
decoration: BoxDecoration(
color: Colors.white,
),
child: getGrid(),
)
],
),
if (isStack)
_AppBar(
title: 'Gallery',
)
],
),
);
}
Widget getGrid() {
return GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 3,
children: widget.images.map((url) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
child: Image(
image: NetworkImage(url),
),
);
}).toList(),
);
}
}
class _AppBar extends StatelessWidget {
const _AppBar({Key key, this.title}) : super(key: key);
final Color backgroundColor = Colors.white;
final Color color = Colors.grey;
final String title;
#override
Widget build(BuildContext context) {
return Material(
elevation: 5,
child: Container(
height: kToolbarHeight + MediaQuery.of(context).padding.top,
color: backgroundColor,
child: OverflowBox(
alignment: Alignment.topCenter,
maxHeight: 200,
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: kToolbarHeight),
child: AppBar(
centerTitle: false,
backgroundColor: backgroundColor,
iconTheme: IconThemeData(
color: color, //change your color here
),
primary: false,
title: Text(
title,
style: TextStyle(color: color),
),
elevation: 0,
),
),
),
),
),
);
;
}
}
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
#required this.builder,
RouteSettings settings,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 350);
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final result = builder(context);
return Container(
color: Colors.black.withOpacity(0.5),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
)),
child: result,
),
);
}
}
I think you may try this library, sliding_sheet
when you detect the expand status by controller, then you animate/enlarge the container A.

How to snap scroll effect in flutter?

With the normal scroll effect, you are free to scroll how much ever you want,
but I want to have a scrollable list but only scroll full widget or 1/4 of the widget.
something like this:-
How to get a scrolling effect?
You can use PageView.
Here is the sample code. It has the paging animation. It also has attached listener to the PageController, which is useful to get current state.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _controller = PageController(viewportFraction: 0.6);
var _color = "";
#override
void initState() {
super.initState();
_controller.addListener(() {
if (_controller.page < 0.5) {
setState(() {
_color = "yellow";
});
}
if (_controller.page >= 0.5 && _controller.page < 1.5) {
setState(() {
_color = "red";
});
}
if (_controller.page >= 1.5 && _controller.page < 2) {
setState(() {
_color = "blue";
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
height: 200,
),
Text(
_color,
style: TextStyle(fontSize: 40),
),
SizedBox(
height: 100,
child: PageView(
controller: _controller,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
child: Container(
color: Colors.amber,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 200,
child: Container(
color: Colors.red,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 200,
child: Container(
color: Colors.lightBlue,
),
),
),
],
),
),
],
));
}
}

flutter - create an elastic effect on animation

I have a container in a Stack widget of height 100.0. It is positioned in the center using a Positioned widget as follows
Container(
width:100.0,
height:100.0,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned(
top: 40.0,
child: Container(
width: 20.0,
height: 20.0,
color: Colors.red,
),
)
],
)
)
I want to animate the red container in such a way that when clicked it goes to bottom of the parent container and when clicked angain bounces back to top then back to center.
I tried using Curves.elasticOut but that is not enough bounce for me.
How do I acheive this effect
Try this code and let me know if I understand your animation correctly
Edited:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'dart:math';
const BOX_COLOR = Colors.cyan;
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Spring Box",
theme: ThemeData(
primaryColor: Colors.red,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Padding(
child: PhysicsBox(),
padding: EdgeInsets.only(
left: 20.0,
right: 20.0,
top: 20.0,
bottom: 20.0,
),
),
));
}
}
class PhysicsBox extends StatefulWidget {
#override
BoxState createState() => BoxState();
}
class BoxState extends State<PhysicsBox> with TickerProviderStateMixin {
AnimationController controller;
AnimationController controller2;
Animation<double> animation;
SpringSimulation simulation;
double _position;
#override
void initState() {
super.initState();
simulation = SpringSimulation(
SpringDescription(
mass: 1.0,
stiffness: 100.0,
damping: 5.0,
),
400.0,
208.0,
-4000.0,
);
controller2 = AnimationController(vsync: this,duration: Duration(milliseconds: 70));
animation = Tween(begin: 200.0, end: 400.0).animate(controller2)
..addListener((){
if(controller2.status == AnimationStatus.completed){controller.reset();}
setState(() {
_position = animation.value;
});
});
controller = AnimationController(vsync: this,duration: Duration(milliseconds: 700))..forward()
..addListener(() {
if(controller.status == AnimationStatus.completed){controller2.reset();}
setState(() {
_position = simulation.x(controller.value);
});
print('${simulation.x(controller.value)}');
});
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
width:500.0,
height:500.0,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned(
top: _position,
child: GestureDetector(
onTap: (){
if (controller.status == AnimationStatus.completed) {
controller2.forward();//controller.reset();
}else{
controller.forward();}
},
child: Container(
width: 100.0,
height: 100.0,
color: Colors.red,
),
),
)
],
)
);
}
}
Use TweenMax for Flutter: https://pub.dartlang.org/packages/tweenmax
Wrap your container by a GestureDetector to use Tap Gesture, then replace your red container with TweenContainer:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:tweenmax/tweenmax.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Spring Box",
theme: ThemeData(
primaryColor: Colors.red,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
bool isClicked = false;
#override
Widget build(BuildContext context) {
TweenContainer redContainer = TweenContainer(
data: TweenData(
top: 40,
width: 20.0,
height: 20.0,
color: Colors.red,
),
);
return Scaffold(
body: GestureDetector(
child: Container(
width: 100.0,
height: 100.0,
margin: EdgeInsets.only(top: 100, left: 100),
color: Colors.yellow,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
redContainer
],
)
),
onTap: (){
// click first time, animate "redContainer" to bottom:
if(!isClicked){
TweenMax.to(
redContainer,
duration: 0.3,
ease: Curves.ease,
data: TweenData(
top: 80
)
);
} else { // click second time, animate "redContainer" to top:
TweenMax.to(
redContainer,
duration: 0.2,
ease: Curves.easeIn,
data: TweenData(
top: 0
),
onComplete: (redContainer){
// animate it back to center position:
TweenMax.to(
redContainer,
duration: 0.8,
ease: ElasticOutCurve(0.3),
data: TweenData(
top: 40
)
);
}
);
}
isClicked = !isClicked;
},
)
);
}
}
See this video: https://drive.google.com/file/d/1i3BgxWiVna6kQMRKVgUEQDTW2QTlSXWo/view?usp=sharing