Furniture App UI with Flutter Animations

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 and leftItemList are lists of FurnitureItem 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: Provides Ticker 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:

Summary

Leave a Comment