Design Complex UI in Flutter - Animated Drawer Widget
How to implement a complex UI in Flutter. In this example, we are creating an Animated widget with 3d Animation. Widgets used in this Example are GestureDetector widget is used to Handle the Gesture events like drag events. AnimatedBuilder widget is used to Make an animation for the widget. Transform widget is used to make transition animation for the widget Stack widget is used to align the widgets for the main widget and Drawer events. Handle the Drag Events by ) Create Animation Controller to do the Animation by Gesture events How to add Smooth Animation for the widget? Add Animation for the Main widget by Add Animation for Drawer widget by Complete code main.dart Home.dart Settings.dart Tags: Drawer Widget, Navigation Drawer, Flutter 3D Animation
GestureDetector
AnimatedBuilder
Stack
GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
animationController=AnimationController(vsync: this,duration: Duration(milliseconds: 225));
void _onDragStart(DragStartDetails dragStartDetails)
{
bool isDragOpenFromLeft=animationController.isDismissed&&dragStartDetails.globalPosition.dx<minDragStartEdge;
bool isDragCloseFromRight=animationController.isCompleted&&dragStartDetails.globalPosition.dx>maxDragStartEdge;
_canBeDragged=isDragOpenFromLeft||isDragCloseFromRight;
}
void _onDragUpdate(DragUpdateDetails dragStartDetails)
{
if(_canBeDragged)
{
double delta=dragStartDetails.primaryDelta/maxSlide;
animationController.value+=delta;
}
}
void _onDragEnd(DragEndDetails details) {
//I have no idea what it means, copied from Drawer
double _kMinFlingVelocity = 365.0;
if (animationController.isDismissed || animationController.isCompleted) {
return;
}
if (details.velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) {
double visualVelocity = details.velocity.pixelsPerSecond.dx /
MediaQuery.of(context).size.width;
animationController.fling(velocity: visualVelocity);
} else if (animationController.value < 0.5) {
animationController.reverse();
} else {
animationController.forward();
}
}
Transform.translate(
offset: Offset(maxSlide * animationController.value, 0),
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(-math.pi * animationController.value / 2),
alignment: Alignment.centerLeft,
child: getWidget(),
),
)
Transform.translate(
offset: Offset(maxSlide * (animationController.value - 1), 0),
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(math.pi / 2 * (1 - animationController.value)),
alignment: Alignment.centerRight,
child: MyDrawer(onMenuSelect:(pos){
animationController.reverse();
setState(() {
position=pos;
});
}),
),
),
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'custom_drawer.dart';
import 'home.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: 'Flutter Complex UI',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: CustomDrawer(),
);
}
}
CustomDrawer.dart
import 'package:flutter/material.dart';
import 'package:flutter_complex_ui/favourites.dart';
import 'package:flutter_complex_ui/settings.dart';
import 'dart:math' as math;
import 'home.dart';
class CustomDrawer extends StatefulWidget{
static CustomDrawerState of(BuildContext context) =>
context.findAncestorStateOfType<CustomDrawerState>();
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return CustomDrawerState();
}
}
typedef menuCallback = void Function(int pos);
class CustomDrawerState extends State<CustomDrawer> with SingleTickerProviderStateMixin
{
AnimationController animationController;
static const double maxSlide = 225;
static const double minDragStartEdge = 60;
static const double maxDragStartEdge = maxSlide - 16;
bool _canBeDragged;
int position=0;
@override
void initState() {
// TODO: implement initState
super.initState();
animationController=AnimationController(vsync: this,duration: Duration(milliseconds: 225));
}
void toogle()
{
animationController.isDismissed?animationController.forward():animationController.reverse();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (animationController.isCompleted) {
close();
return false;
}
return true;
},
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
behavior: HitTestBehavior.translucent,
onTap: animationController.isCompleted ?close () : null,
child: AnimatedBuilder(
animation: animationController,
child: (position==0)?MyHomePage():Settings(),
builder: (context,_){
double animValue = animationController.value;
final slideAmount = maxSlide * animValue;
final contentScale = 1.0 - (0.3 * animValue);
return Stack(
children: [
Transform.translate(
offset: Offset(maxSlide * (animationController.value - 1), 0),
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(math.pi / 2 * (1 - animationController.value)),
alignment: Alignment.centerRight,
child: MyDrawer(onMenuSelect:(pos){
animationController.reverse();
setState(() {
position=pos;
});
}),
),
),
Transform.translate(
offset: Offset(maxSlide * animationController.value, 0),
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(-math.pi * animationController.value / 2),
alignment: Alignment.centerLeft,
child: getWidget(),
),
),
Positioned(
top: 4.0 + MediaQuery.of(context).padding.top,
left: 4.0 + animationController.value * maxSlide,
child: Material(
color: Colors.transparent,
child: IconButton(
icon: Icon(Icons.menu),
onPressed: toogle,
color: Colors.white,
),
),
),
],
);
},
),
),
);
}
getWidget()
{
if(position==0)return MyHomePage();
else if(position==1)return Favourite();
else return Settings();
}
open()=>animationController.forward();
close()=>animationController.reverse();
void setPosition(pos){
setState(() {
position=pos;
});
}
void _onDragStart(DragStartDetails dragStartDetails)
{
bool isDragOpenFromLeft=animationController.isDismissed&&dragStartDetails.globalPosition.dx<minDragStartEdge;
bool isDragCloseFromRight=animationController.isCompleted&&dragStartDetails.globalPosition.dx>maxDragStartEdge;
_canBeDragged=isDragOpenFromLeft||isDragCloseFromRight;
}
void _onDragUpdate(DragUpdateDetails dragStartDetails)
{
if(_canBeDragged)
{
double delta=dragStartDetails.primaryDelta/maxSlide;
animationController.value+=delta;
}
}
void _onDragEnd(DragEndDetails details) {
//I have no idea what it means, copied from Drawer
double _kMinFlingVelocity = 365.0;
if (animationController.isDismissed || animationController.isCompleted) {
return;
}
if (details.velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) {
double visualVelocity = details.velocity.pixelsPerSecond.dx /
MediaQuery.of(context).size.width;
animationController.fling(velocity: visualVelocity);
} else if (animationController.value < 0.5) {
animationController.reverse();
} else {
animationController.forward();
}
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
}
class MyDrawer extends StatelessWidget {
const MyDrawer({this.onMenuSelect});
final menuCallback onMenuSelect;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.blueAccent,
child: SafeArea(
child: Theme(
data: ThemeData(brightness: Brightness.dark),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Container(
child: AppBar(title: Text("Menu"), ),
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: [
const Color(0xFF3366FF),
const Color(0xFF00CCFF),
],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 0.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
)
,
/* Image.asset(
'assets/flutter_europe_white.png',
width: 200,
),*/
ListTile(
leading: Icon(Icons.new_releases),
onTap: (){
onMenuSelect(0);
},
title: Text('News'),
),
ListTile(
leading: Icon(Icons.star),
onTap: (){
onMenuSelect(1);
},
title: Text('Favourites'),
),
ListTile(
leading: Icon(Icons.map),
onTap: (){
onMenuSelect(2);
},
title: Text('Map'),
),
ListTile(
leading: Icon(Icons.settings),
onTap: (){
onMenuSelect(3);
},
title: Text('Settings'),
),
ListTile(
leading: Icon(Icons.person),
onTap: (){
onMenuSelect(4);
},
title: Text('Profile'),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'custom_drawer.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key }) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List listNews=[];
bool hasPages=true;
ScrollController _scrollController = new ScrollController();
int page=1;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Center(child: Text("News",)), flexibleSpace: Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: [
const Color(0xFF3366FF),
const Color(0xFF00CCFF),
],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 0.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
),),
body: ListView(
children: [
getMars(Colors.grey),
getMars(Colors.red),
getMars(Colors.yellow),
getMars(Colors.white70),
],
)
);
}
getMars(color)
{
return Container(
height: 120.0,
margin: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 24.0,
),
child: new Stack(
children: <Widget>[
planetCard(color),
planetThumbnail,
],
)
);
}
final planetThumbnail = new Container(
margin: new EdgeInsets.symmetric(
vertical: 16.0
),
alignment: FractionalOffset.centerLeft,
child: CircleImage(size: Size.fromWidth(120),child: Image.network("https://p1.pxfuel.com/preview/366/297/294/model-jewelry-lady-young-indian-girl-royalty-free-thumbnail.jpg",
width: 100,
height: 120,
fit: BoxFit.fitWidth,
),),
);
planetCard(color) {
return Container(
height: 124.0,
margin: EdgeInsets.only(left: 46.0),
decoration: BoxDecoration(
color: (color),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(8.0),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black12,
blurRadius: 10.0,
offset: Offset(0.0, 10.0),
),
],
),
);
}
}
class CircleImage extends StatelessWidget{
final Size size;
final Widget child;
CircleImage({Key key,@required this.child,this.size=const Size.fromWidth(120)}):super(key:key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
decoration: new BoxDecoration(
color: Colors.grey,
shape: BoxShape.circle),
height: size.width,
child: ClipOval(
child: child,
),
);
}
}
import 'package:flutter/material.dart';
class Settings extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.greenAccent,
appBar: AppBar(title: Center(child: Text("Settings")), flexibleSpace: Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: [
const Color(0xFF3366FF),
const Color(0xFF00CCFF),
],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 0.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
),),
body: Container(
child: Center(
child: Text("Settings Page"),
),
),
);
}
}
1267 Views