Design Complex UI in Flutter - Animated Drawer Widget
Published November 07, 2020How 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
-
AnimatedBuilder
- Transform
-
Stack
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
GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
) |
Create Animation Controller to do the Animation by
animationController=AnimationController(vsync: this,duration: Duration(milliseconds: 225));
|
Gesture events
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();
}
}
|
How to add Smooth Animation for the widget?
Add Animation for the Main widget by
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(),
),
)
|
Add Animation for Drawer widget by
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;
});
}),
),
),
|
Complete code
main.dart
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'),
),
],
),
),
),
);
}
}
|
Home.dart
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,
),
);
}
}
|
Settings.dart
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"),
),
),
);
}
}
|
Tags: Drawer Widget, Navigation Drawer, Flutter 3D Animation
Article Contributed By :
|
|
|
|
3103 Views |