In the competitive landscape of mobile app development, creating a visually appealing and user-friendly interface is essential for capturing users’ attention and retaining them. Flutter, Google’s open-source UI toolkit, has revolutionized the way developers build beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. One of flutter animations is powerful standout library, which can significantly enhance user experience.
In this article, we’ll delve into the design and implementation of a furniture app UI using flutter animations, focusing on how animations can transform a static interface into a dynamic, engaging experience. We’ll explore key concepts and best practices for integrating animations seamlessly, improving usability, and creating a memorable user journey.
Key Points:
- Learn how to implement flutter animations
- Learn how to implement Animation Controller
- Learn how to add sequence in multiple animations
- Learn how to use Animation Builder efficiently with list of animations
Splash Screen Code:
- The
FurnitureSplash
widget is designed as an introductory screen with a full-screen background image and a text message overlayed at the top. - At the bottom of the screen, there is a button labeled “Get Started” that navigates to the
FurnitureHome
page when pressed. - The splash screen uses positioning and a stack layout to layer and arrange its content.
import 'package:flutter/material.dart';
import 'package:flutter_animation/furniture/furniture_home.dart';
class FurnitureSplash extends StatelessWidget {
const FurnitureSplash({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned.fill(
child: Image.asset('assets/images/sofa.png', fit: BoxFit.cover,),
),
const Positioned(
top: 50,
left: 30,
right: 50,
child: Text(
'We design Furniture for your comfort',
style: TextStyle(
color: Colors.white,
fontSize: 49,
fontWeight: FontWeight.bold),
),
),
Positioned(
left: 30,
right: 30,
bottom: 30,
child: SizedBox(
height: 70,
child: ElevatedButton(
onPressed: (){
Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => const FurnitureHome(),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))
),
child: const Text('Get Started', style: TextStyle(color: Colors.white, fontSize: 18),),
),
),
),
],
),
);
}
}
Furniture Home Screen Code:
Certainly! This code defines a FurnitureHome
widget in Flutter with various animations and UI elements. Here’s a detailed breakdown and summary of each part:
rightItemList
andleftItemList
are lists ofFurnitureItem
widgets. Each item represents a piece of furniture with attributes like title, image, price, color, and a flag indicating if it’s on the right side.
import 'package:flutter/material.dart';
import 'package:flutter_animation/furniture/widgets/furniture_item.dart';
final rightItemList = [
const FurnitureItem(
title: 'Leather Swivel Chair',
image: 'assets/images/chair.png',
price: '299',
color: Color(0xffDEDEDE),
isRight: true,
),
const FurnitureItem(
title: 'Stool Table',
image: 'assets/images/soft_chair.png',
price: '120',
color: Color(0xff9B9ff9),
isRight: true,
),
const FurnitureItem(
title: 'Leather Swivel Chair',
image: 'assets/images/chair.png',
price: '299',
color: Color(0xffDEDEDE),
isRight: true,
)
];
final leftItemList = [
const FurnitureItem(
title: 'Stool Table',
image: 'assets/images/stool.png',
price: '150',
color: Color(0xffCDE9D4),
isRight: false,
),
const FurnitureItem(
title: 'Stool Table',
image: 'assets/images/lamp.png',
price: '200',
color: Color(0xffDEDEDE),
isRight: false,
),
const FurnitureItem(
title: 'Leather Swivel Chair',
image: 'assets/images/chair.png',
price: '299',
color: Color(0xffCDE9D4),
isRight: false,
)
];
Furniture Home Class and Animation Setup:
TickerProviderStateMixin
: ProvidesTicker
objects for animations.- Animation Controllers and Animations: Define controllers and animations for various UI components such as search field, image, text, cart icon, price label, and list items.
class FurnitureHome extends StatefulWidget {
const FurnitureHome({super.key});
@override
State<FurnitureHome> createState() => _FurnitureHomeState();
}
class _FurnitureHomeState extends State<FurnitureHome> with TickerProviderStateMixin {
// Animation Controllers and Animations
late AnimationController _searchController;
late Animation<double> _searchAnimation;
late AnimationController _imageController;
late Animation<double> _imageAnimation;
late AnimationController _elementController;
late Animation<double> _textAnimation;
late Animation<double> _cartAnimation;
late Animation<double> _priceAnimation;
late Animation<double> _favoriteAnimation;
late AnimationController _listController;
late Animation<double> _leftListAnimation;
late Animation<double> _rightListAnimation;
bool _showSearch = false;
Initializing Animations:
initState
: Initializes animation controllers and animations._searchController
: Controls the search field animation._imageController
: Manages the image drop animation._elementController
: Controls animations for text, cart icon, price, and favorite icon._listController
: Manages the animations for the left and right lists._runAnimationSequence
: Method to sequence the animations for a smooth start.
@override
void initState() {
super.initState();
// Search field animation
_searchController = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
);
_searchAnimation = Tween<double>(begin: 0, end: 1).animate(_searchController);
// Image drop animation
_imageController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_imageAnimation = Tween<double>(begin: -1.0, end: 0.0).animate(
CurvedAnimation(parent: _imageController, curve: Curves.easeOut)
);
// Other elements animations
_elementController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_textAnimation = Tween<double>(begin: -2.0, end: 0.0).animate(
CurvedAnimation(parent: _elementController, curve: Curves.easeOut)
);
_cartAnimation = Tween<double>(begin: -2.0, end: 0.0).animate(
CurvedAnimation(parent: _elementController, curve: Curves.easeOut)
);
_priceAnimation = Tween<double>(begin: 2.0, end: 0.0).animate(
CurvedAnimation(parent: _elementController, curve: Curves.easeOut)
);
_favoriteAnimation = Tween<double>(begin: 2.0, end: 0.0).animate(
CurvedAnimation(parent: _elementController, curve: Curves.easeOut)
);
_listController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_leftListAnimation = Tween<double>(begin: 4.0, end: 0.0).animate(
CurvedAnimation(parent: _listController, curve: Curves.easeOut)
);
_rightListAnimation = Tween<double>(begin: 4.0, end: 0.0).animate(
CurvedAnimation(parent: _listController, curve: Curves.easeOut)
);
// Sequence the animations
_runAnimationSequence();
}
Sequence Animations:
void _runAnimationSequence() async {
await Future.delayed(const Duration(milliseconds: 300));
await _imageController.forward();
_elementController.forward();
await Future.delayed(const Duration(milliseconds: 500));
_searchController.forward();
setState(() {
_showSearch = true;
});
await Future.delayed(const Duration(milliseconds: 300));
_listController.forward();
}
Dispose Animation Controllers:
- Here we have disposed all the animation controllers. Otherwise it will cause memory leak.
@override
void dispose() {
_searchController.dispose();
_imageController.dispose();
_elementController.dispose();
_listController.dispose();
super.dispose();
}
Build Method Code:
- In this code, I have used
Column
for vertical stacking of widgets. AnimatedBuilder
manages multiple animations, including for images, text, and icons.- Chair image moves vertically with
_imageAnimation
. - Title and subheading text slide in with
_textAnimation
. - Price button moves horizontally using
_priceAnimation
. - Cart and favorite icons animate into view using their respective animations.
- Search bar includes an animated opacity effect for the search icon.
- Filter icon animates horizontally using
_searchAnimation
. - Two lists (left and right) animate vertically using
_leftListAnimation
and_rightListAnimation
.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedBuilder(
animation: Listenable.merge([_imageAnimation, _textAnimation, _cartAnimation, _priceAnimation, _favoriteAnimation]),
builder: (context, child) {
return Stack(
children: [
Transform.translate(
offset: Offset(0, _imageAnimation.value * 350),
child: Image.asset(
'assets/images/chair_bg.png',
fit: BoxFit.fill,
height: 350,
width: double.infinity,
),
),
Positioned(
top: 46,
left: 16,
child: Transform.translate(
offset: Offset(0, _textAnimation.value * 100),
child: RichText(
text: const TextSpan(
children: [
TextSpan(
text: 'Find the best',
style: TextStyle(color: Colors.black, fontSize: 22)
),
TextSpan(
text: '\nFurniture! 🛋️',
style: TextStyle(color: Colors.black ,fontSize: 24, fontWeight: FontWeight.bold),
)
]
),
),
),
),
Positioned(
top: 106,
left: 16,
child: Transform.translate(
offset: Offset(0, _textAnimation.value * 100),
child: const Text(
'Dinning Chair',
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
),
Positioned(
bottom: 46,
left: 46,
child: Transform.translate(
offset: Offset(_priceAnimation.value * -100, 0),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 16),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(30),
),
child: const Text(
'\$230',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
),
Positioned(
top: 46,
right: 40,
child: Transform.translate(
offset: Offset(0, _cartAnimation.value * 100),
child: Image.asset('assets/images/cart.png', width: 50, height: 50,)
),
),
Positioned(
bottom: 46,
right: 40,
child: Transform.translate(
offset: Offset(_favoriteAnimation.value * 100, 0),
child: Image.asset('assets/images/favourite.png', width: 60, height: 60,)
),
),
],
);
},
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextField(
decoration: InputDecoration(
hintText: 'Search Items',
hintStyle: TextStyle(color: Colors.white.withOpacity(0.4)),
fillColor: Colors.black,
filled: true,
contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
prefixIcon: AnimatedOpacity(
opacity: _showSearch ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Stack(
alignment: Alignment.center,
children: [
Image.asset('assets/images/search.png', width: 25, height: 25),
],
),
),
suffixIcon: AnimatedBuilder(
animation: _searchAnimation,
builder: (context, child) {
return Padding(
padding: EdgeInsets.only(right: 8.0 * _searchAnimation.value),
child: Transform.translate(
offset: Offset(-(MediaQuery.of(context).size.width - 80) * (1 - _searchAnimation.value), 0),
child: Image.asset('assets/images/filter.png', width: 60, height: 60),
),
);
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: AnimatedBuilder(
animation: Listenable.merge([_leftListAnimation, _rightListAnimation]),
builder: (context, child) {
return Stack(
children: [
Positioned(
top: 0,
right: 0,
left: MediaQuery.of(context).size.width / 2,
bottom: 0,
child: Transform.translate(
offset: Offset(0, _rightListAnimation.value * 100),
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: rightItemList.length,
itemBuilder: (context, index) {
return rightItemList[index];
},
),
),
),
Positioned(
top: 0,
left: 0,
right: MediaQuery.of(context).size.width / 2,
bottom: 0,
child: Transform.translate(
offset: Offset(0, _leftListAnimation.value * 100),
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: leftItemList.length,
itemBuilder: (context, index) {
return leftItemList[index];
},
),
),
),
],
);
},
),
),
),
],
),
);
}
FurnitureItem Code:
Here is the Furniture Item widget code. This code is handling image width and height for left and right list.
import 'package:flutter/material.dart';
class FurnitureItem extends StatelessWidget {
const FurnitureItem({
super.key,
required this.title,
required this.image,
required this.price,
required this.color,
required this.isRight
});
final String title;
final String price;
final String image;
final Color color;
final bool isRight;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Container(
height: 230,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: color
),
child: Stack(
children: [
Center(
child: Column(
children: [
const SizedBox(height: 28,),
Image.asset(image, height: isRight ? 130 : 100, width: isRight ? 100 : 80, fit: BoxFit.fill),
const SizedBox(height: 8,),
Text(title, style: const TextStyle(fontSize: 16, color: Colors.black),),
const SizedBox(height: 4,),
Text('\$$price', style: const TextStyle(fontSize: 22, color: Colors.black, fontWeight: FontWeight.bold),),
],
),
),
Positioned(
top: 12,
right: 12,
child: Image.asset('assets/images/favourite.png', width: 40, height: 40,),
),
],
),
),
);
}
}
Thank you for reading 👋
I hope you enjoyed this article. If you have any queries or suggestions please let me know in the comments down below.
I’m Shehzad Raheem 📱 Flutter Developer and I help firms to fulfill their Mobile Application Development, Android Development, and Flutter Development needs. If you want to discuss any project, drop me a message
Follow us: