Placing images partially off screen and rotate it - flutter

I'm currently playing around with Flutter and came across an issue where I need your help. I already googled a lot, but missing terms or there is none...
I have a wheel that can be rotated around the center of the wheel (actually of the Container) using a GestureDetector. This is fine so far and works great.
However, I'd like to place the wheel on the left, so that half of it is off screen.
What I have ---------------+--- What I want to achieve
The wheel should still be spinnable around its center, so that the covered colors will be revealed again.
Your help is highly appreciated!
The code I use so far is basically taken from here: How to use GestureDetector to rotate images in Flutter smoothly?
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wheel',
theme: ThemeData(
primarySwatch: Colors.yellow,
),
home: const Wheel(),
);
}
}
class Wheel extends StatefulWidget {
const Wheel({Key? key}) : super(key: key);
#override
_WheelState createState() => _WheelState();
}
class _WheelState extends State<Wheel> {
double finalAngle = 0.0;
double oldAngle = 0.0;
double upsetAngle = 0.0;
#override
Widget build(BuildContext context) {
return _defaultApp(context);
}
_defaultApp(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 340,
height: 500,
color: Colors.grey,
margin: const EdgeInsets.all(10.0),
child: LayoutBuilder(
builder: (context, constraints) {
Offset centerOfGestureDetector = Offset(
constraints.maxWidth / 2, constraints.maxHeight / 2);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
upsetAngle =
oldAngle - touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
oldAngle = finalAngle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
setState(
() {
finalAngle = touchPositionFromCenter.direction +
upsetAngle;
},
);
},
child: Transform.rotate(
angle: finalAngle,
child: Image.asset('assets/images/wheel.png',
scale: 1.0),
),
);
},
),
),
],
)
],
),
),
);
}
}

I have a solution. although it is not the best solution but it is workable:
If you like it or use it kindly upvote :)
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
appBar: new AppBar(
title: new Text('Hello'),
),
body: ListView(
scrollDirection: Axis.horizontal,
reverse: true,
physics: NeverScrollableScrollPhysics(),
children: [
Container(
width: MediaQuery.of(context).size.width * 2,
height: 500,
color: Colors.grey,
margin: const EdgeInsets.all(10.0),
child: LayoutBuilder(
builder: (context, constraints) {
Offset centerOfGestureDetector =
Offset(constraints.maxWidth / 2, constraints.maxHeight / 2);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
upsetAngle = oldAngle - touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
oldAngle = finalAngle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition - centerOfGestureDetector;
setState(
() {
finalAngle =
touchPositionFromCenter.direction + upsetAngle;
},
);
},
child: Transform.rotate(
angle: finalAngle,
child: Image.network(
'https://static.toiimg.com/thumb/msid-31346158,width-748,height-499,resizemode=4,imgsize-114461/.jpg',
scale: 1.0),
),
);
},
),
),
],
),
);
}

Related

How to make custom animated Container from button of the app till half of the app screen

expected behavior
i tried this code but it give me completely difference result from left side and strange animated
double val = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 400,
color: Colors.red,
),
TweenAnimationBuilder(
duration: const Duration(milliseconds: 150),
tween: Tween<double>(begin: 0 , end: val) ,
builder: (BuildContext context, double? value, Widget? child) {
return (
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(0, 3, 200 * value!)
..rotateY((pi/6)*value),
child: DefaultTabController(
length: 5,
child: Scaffold(
body: Center(
child: Container(
color: Colors.yellowAccent,
child: IconButton(
onPressed: () {
setState(() {
setState(() {
val == 0 ? val = 1 : val = 0 ;
});
});
},
icon: Text('tab me'),
),
),
)
)
)
)
);
}
)
],
);
}
also i need only the red Container the one who animated from down to up , but i don't know why the main screen is also animate .. i need it never animated ..
any suggestion most welcome guys .. thanks
Instead of custom animation, you can use AnimatedContainer().
Create a boolean like selected which will tell the animated container when to close and when to open the container. And using setState you can toggle the animation.
Align your AnimatedContainer() with Align() and give alignment: Alignment.bottomCenter. And give height:0 is not selected and when selected give height the half of screen using MediaQuery.of(context)
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () {
setState(() {
selected = !selected;
});
},
child: Text("Tap Me!!"),
),
Spacer(),
GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: double.infinity,
height: selected ? MediaQuery.of(context).size.height / 2 : 0,
color: selected ? Colors.red : Colors.blue,
alignment:
selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 75),
),
),
)
]);
}
}
You can try the same code in dartpad here

Flutter rebuild a TweenAnimationBuilder

I want to make a flipping animation with TweenAnimationBuilder, a container will flip over and change the color. I want to add a button when user click on it, the container will flip over again and change into another colour.
Here is my code:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isBack = true;
Color backColor = Colors.green;
Color topColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
box(),
SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(25, 10),
elevation: 10,
),
onPressed: () {
setState(() {
backColor = Colors.red;
topColor = Colors.blue;
});
},
child:
Text('change to blue', style: TextStyle(fontSize: 16))),
],
),
),
),
);
}
Widget box() {
print('building');
return TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: pi),
duration: Duration(seconds: 1),
builder: (BuildContext context, double value, _) {
print(value);
if (value >= (pi / 2)) {
isBack = false;
} else {
isBack = true;
}
return (Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(value),
child: Container(
width: 100,
height: 100,
child: isBack
? Container(
color: backColor,
)
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateY(
pi),
child: Container(
color: topColor,
),
)
),
));
});
}
}
At the first build, the tween value will start from the beginning:
building
0
0
0
0.13080335172486462
0.19619246121668257
0.2180893620122034
...
3.141592653589793
but when I click on the button to change the color, it will not start again from the begin value, it just stays at 3.14:
building
3.141592653589793
Right now the button will only change the color of the container, but it will not flip again.
I suppose after the setstate function, the tween value will restart again at 0, why won't it do so?
Can anybody explain it please?
All you need is an AnimationController and AnimatedBuilder to control the animation.
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
bool isBack = true;
Color backColor = Colors.blue;
Color topColor = Colors.red;
late AnimationController _animationController;
late Animation _rotationAnimation;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
_rotationAnimation =
Tween<double>(begin: 0, end: pi).animate(_animationController);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
box(),
SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(25, 10),
elevation: 10,
),
onPressed: () {
setState(() {
backColor = Colors.red;
topColor = Colors.blue;
});
// if (_animationController.isDismissed) {
// _animationController.forward();
// } else if (_animationController.isCompleted) {
// _animationController.reverse();
// }
_animationController.forward(from: 0.0);
},
child:
Text('change to blue', style: TextStyle(fontSize: 16))),
],
),
),
),
);
}
Widget box() {
return AnimatedBuilder(
animation: _animationController,
// tween: Tween<double>(begin: 0, end: pi),
// duration: Duration(seconds: 1),
builder: (_, __) {
// print(value);
if (_rotationAnimation.value >= (pi / 2)) {
isBack = false;
} else {
isBack = true;
}
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(_rotationAnimation.value),
child: Container(
width: 100,
height: 100,
child: isBack
? Container(
color: backColor,
)
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..rotateY(pi),
child: Container(
color: topColor,
),
),
),
);
});
}
}

Custom shape of PopupMenuButton in flutter

I wan to change the shape of my PopupMenuButton in flutter, want to add a triangle on top as shown in below picture, I have spent a lot of time on google but no achievement please help me out I am new to flutter so I do not know how to change this default container, now it simply has white rounded container, white arrow/triangle is not being added on top of it. please help out, thanks in advance
popUpMenu= PopupMenuButton<String>(
key: _menuKey,
offset: Offset(50,100),
padding: EdgeInsets.all(0.0),
onSelected: (value) {
if (value == "Tax & Additional Charges") {
endDrawerController.drawerKey.value =
EndDrawerKeys.TaxAndAdditionalChargesEndDrawer;
endDrawerController.scaffoldKey.currentState.openEndDrawer();
print("Entering in tax");
} else if (value == "Hold this Invoice") {
endDrawerController.drawerKey.value =
EndDrawerKeys.HoldInvoiceEndDrawer;
endDrawerController.scaffoldKey.currentState.openEndDrawer();
}
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.h))),
itemBuilder: (context) => [
PopupMenuItem(
value: "Tax & Additional Charges",
child: popUpMenuSingleItem(
icon: AppAssets.DeliveryIcon,
text: "Tax & Additional Charges",
topMargin: 15.h),
),
PopupMenuItem(
value: "Hold this Invoice",
child: popUpMenuSingleItem(
icon: AppAssets.DeliveryIcon,
text: "Hold this Invoice",
topMargin: 25.h),
),
],
);
This is how I want my PopupMenuButton to be appear
You can create a shape for your custom PopupMenuButton.
Sample...
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
PopupMenuButton(
offset: Offset(0, 50),
shape: const TooltipShape(),
itemBuilder: (_) => <PopupMenuEntry>[
PopupMenuItem(
child: ListTile(
leading: const Icon(Icons.calculate_outlined),
title: const Text('Tax & Additional Charges'),
)),
PopupMenuItem(
child: ListTile(
leading: const Icon(Icons.av_timer_outlined),
title: const Text('Hold This Invoice'),
)),
],
),
],
),
);
}
}
/// I'm using [RoundedRectangleBorder] as my reference...
class TooltipShape extends ShapeBorder {
const TooltipShape();
final BorderSide _side = BorderSide.none;
final BorderRadiusGeometry _borderRadius = BorderRadius.zero;
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(_side.width);
#override
Path getInnerPath(
Rect rect, {
TextDirection? textDirection,
}) {
final Path path = Path();
path.addRRect(
_borderRadius.resolve(textDirection).toRRect(rect).deflate(_side.width),
);
return path;
}
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
final Path path = Path();
final RRect rrect = _borderRadius.resolve(textDirection).toRRect(rect);
path.moveTo(0, 10);
path.quadraticBezierTo(0, 0, 10, 0);
path.lineTo(rrect.width - 30, 0);
path.lineTo(rrect.width - 20, -10);
path.lineTo(rrect.width - 10, 0);
path.quadraticBezierTo(rrect.width, 0, rrect.width, 10);
path.lineTo(rrect.width, rrect.height - 10);
path.quadraticBezierTo(
rrect.width, rrect.height, rrect.width - 10, rrect.height);
path.lineTo(10, rrect.height);
path.quadraticBezierTo(0, rrect.height, 0, rrect.height - 10);
return path;
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
#override
ShapeBorder scale(double t) => RoundedRectangleBorder(
side: _side.scale(t),
borderRadius: _borderRadius * t,
);
}
try this:
class PopMenu extends StatefulWidget {
#override
_PopMenuState createState() => _PopMenuState();
}
class _PopMenuState extends State<PopMenu> {
List<Icon> icons = [
Icon(Icons.person),
Icon(Icons.settings),
Icon(Icons.credit_card),
];
GlobalKey _key = LabeledGlobalKey("button_icon");
OverlayEntry _overlayEntry;
Offset _buttonPosition;
bool _isMenuOpen = false;
void _findButton() {
RenderBox renderBox = _key.currentContext.findRenderObject();
_buttonPosition = renderBox.localToGlobal(Offset.zero);
}
void _openMenu() {
_findButton();
_overlayEntry = _overlayEntryBuilder();
Overlay.of(context).insert(_overlayEntry);
_isMenuOpen = !_isMenuOpen;
}
void _closeMenu() {
_overlayEntry.remove();
_isMenuOpen = !_isMenuOpen;
}
OverlayEntry _overlayEntryBuilder() {
return OverlayEntry(
builder: (context) {
return Positioned(
top: _buttonPosition.dy + 50,
left: _buttonPosition.dx - 250,
width: 300,
child: _popMenu(),
);
},
);
}
Widget _popMenu() {
return Column(
children: [
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(right: 20),
child: ClipPath(
clipper: ArrowClipper(),
child: Container(
width: 17,
height: 17,
color: Color(0xFFF67C0B9),
),
),
),
),
Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Color(0xFFF67C0B9),
borderRadius: BorderRadius.circular(4),
),
child: Theme(
data: ThemeData(
iconTheme: IconThemeData(
color: Colors.white,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
icons.length,
(index) {
return GestureDetector(
onTap: () {},
child: Container(
width: 300,
height: 100,
child: icons[index],
),
);
},
),
),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
key: _key,
decoration: BoxDecoration(
color: Color(0xFFF5C6373),
borderRadius: BorderRadius.circular(4),
),
child: IconButton(
icon: Icon(Icons.menu),
color: Colors.white,
onPressed: () {
_isMenuOpen ? _closeMenu() : _openMenu();
},
),
),
),
);
}
}
class ArrowClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Path path = Path();
path.moveTo(0, size.height);
path.lineTo(size.width / 2, size.height / 2);
path.lineTo(size.width, size.height);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}

Rotating image based on drag handle in flutter

My end goal is to achieve somethinng like this:
As you can see there's the drag handle is required to rotate this image.
I have a following code:
import 'package:flutter/material.dart';
double ballRadius = 7.5;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _angle = 0.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
child: Transform.rotate(
angle: _angle,
child: Column(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanUpdate: (DragUpdateDetails details) {
Offset centerOfGestureDetector = Offset(
constraints.maxWidth / 2,
constraints.maxHeight / 2,
);
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
print(touchPositionFromCenter.direction);
setState(() {
_angle = touchPositionFromCenter.direction;
});
},
);
},
),
),
Container(
height: 30,
width: 5,
color: Colors.black,
),
Container(
height: 200,
width: 200,
color: Colors.red,
),
],
),
),
)
],
),
),
),
);
}
}
It is working. But sometimes it's too fast or too slow. Please help me fix this issue.
I made a few modifications to the code, notably
Treating the "real" centerOfGestureDetector as the center of all the items you would like to rotate
Determining and tracking the change in angle with the onPanStart,onPanEnd and onPanUpdate methods
import 'package:flutter/material.dart';
double ballRadius = 7.5;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _angle = 0.0;
double _oldAngle = 0.0;
double _angleDelta = 0.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
child: Transform.rotate(
angle: _angle,
child: Column(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: LayoutBuilder(
builder: (context, constraints) {
// Offset centerOfGestureDetector = Offset(
// constraints.maxWidth / 2, constraints.maxHeight / 2);
/**
* using center of positioned element instead to better fit the
* mental map of the user rotating object.
* (height = container height (30) + container height (30) + container height (200)) / 2
*/
Offset centerOfGestureDetector =
Offset(constraints.maxWidth / 2, 130);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
_angleDelta = _oldAngle -
touchPositionFromCenter.direction;
},
onPanEnd: (details) {
setState(
() {
_oldAngle = _angle;
},
);
},
onPanUpdate: (details) {
final touchPositionFromCenter =
details.localPosition -
centerOfGestureDetector;
setState(
() {
_angle = touchPositionFromCenter.direction +
_angleDelta;
},
);
},
);
},
),
),
Container(
height: 30,
width: 5,
color: Colors.black,
),
Container(
height: 200,
width: 200,
color: Colors.red,
),
],
),
),
)
],
),
),
),
);
}
}

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.