How to Implement a Shimmer Effect in Flutter: A Step-by-Step Guide with Examples

Introduction:

Flutter, Google's UI toolkit for building natively compiled applications, has gained immense popularity among developers due to its simplicity and efficiency. One eye-catching UI effect that has become increasingly popular is the shimmer effect. The shimmer effect adds a subtle animation to placeholder elements, giving the impression that the content is loading or awaiting data. In this blog post, we'll walk you through the process of implementing a shimmer effect in Flutter with examples.

Preparation:

Before we start, make sure you have Flutter installed and set up on your machine. If you haven't already, you can follow the official documentation to get started: https://flutter.dev/docs/get-started/install

Step 1: Create a New Flutter Project

Open your terminal or command prompt and create a new Flutter project using the following command:

flutter create shimmer_effect_example 

Step 2: Add Dependencies

In your pubspec.yaml file, add the shimmer package to your dependencies:

dependencies:
  flutter:
    sdk: flutter
  shimmer: ^2.0.0  # Replace with the latest version of the package

Run flutter pub get to install the added dependencies.

Step 3: Import Required Libraries

In your Dart file where you want to use the shimmer effect, import the necessary libraries:

import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';

Step 4: Implement the Shimmer Effect

For this example, let's create a simple screen with a shimmer effect on a placeholder container:

class ShimmerEffectScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Shimmer Effect Example'),
      ),
      body: Center(
        child: Shimmer.fromColors(
          baseColor: Colors.grey[300],
          highlightColor: Colors.grey[100],
          child: Container(
            width: 200.0,
            height: 100.0,
            color: Colors.white, // Use the same color as the highlightColor
          ),
        ),
      ),
    );
  }
}

In the above code, we've used the Shimmer.fromColors widget from the shimmer package. The baseColor represents the color of the content when it is not shimmering, while the highlightColor is the color of the shimmer animation. The child property holds the widget you want to animate with the shimmer effect.

Step 5: Run the App

Now, let's run the app on your simulator or physical device to see the shimmer effect in action. Use the following command in your terminal:

flutter run 

Congratulations! You have successfully implemented the shimmer effect in your Flutter app.

Customizing the Shimmer Effect:

The shimmer package provides additional options to customize the shimmer effect according to your app's design. Here are some properties you can use:

  1. direction: Defines the direction of the shimmer animation (Default: ShimmerDirection.ltr).
  2. loop: Determines if the shimmer animation should loop (Default: true).
  3. enabled: Allows you to control when the shimmer effect should be active (Default: true).
  4. duration: Defines the duration of the shimmer animation (Default: const Duration(milliseconds: 1500)).
  5. child: The widget to be animated with the shimmer effect.

Example of Customizing the Shimmer Effect:

Shimmer.fromColors(
  baseColor: Colors.grey[300],
  highlightColor: Colors.grey[100],
  direction: ShimmerDirection.rtl,
  loop: 3,
  enabled: true,
  duration: Duration(seconds: 2),
  child: YourCustomWidget(),
)

Complete Source Code:

Example of Customizing the Shimmer Effect for different usage

GitHub Repository - Find the complete code for each example in this tutorial on GitHub.

import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';


void main() => runApp(const MyApp());


class MyApp extends StatelessWidget {
  const MyApp({super.key});


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'FlutterforGeeks',
      routes: <String, WidgetBuilder>{
        'loading': (_) => const LoadingListPage(),
        'slide': (_) => SlideToUnlockPage(),
      },
      theme: ThemeData.dark(),
      home: const MyHomePage(),
    );
  }
}


class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});


  @override
  State<MyHomePage> createState() => _MyHomePageState();
}


class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FlutterforGeeks'),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(vertical: 8.0),
        child: Column(
          children: <Widget>[
            ListTile(
              title: const Text('Loading List'),
              onTap: () => Navigator.of(context).pushNamed('loading'),
            ),
            ListTile(
              title: const Text('Slide To Unlock'),
              onTap: () => Navigator.of(context).pushNamed('slide'),
            )
          ],
        ),
      ),
    );
  }
}


class LoadingListPage extends StatefulWidget {
  const LoadingListPage({super.key});


  @override
  State<LoadingListPage> createState() => _LoadingListPageState();
}


class _LoadingListPageState extends State<LoadingListPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Loading List'),
      ),
      body: Shimmer.fromColors(
          baseColor: Colors.black12,
          highlightColor: Colors.black45,
          enabled: true,
          child: const SingleChildScrollView(
            physics: NeverScrollableScrollPhysics(),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.max,
              children: [
                BannerPlaceholder(),
                TitlePlaceholder(width: double.infinity),
                SizedBox(height: 16.0),
                ContentPlaceholder(
                  lineType: ContentLineType.threeLines,
                ),
                SizedBox(height: 16.0),
                TitlePlaceholder(width: 200.0),
                SizedBox(height: 16.0),
                ContentPlaceholder(
                  lineType: ContentLineType.twoLines,
                ),
                SizedBox(height: 16.0),
                TitlePlaceholder(width: 200.0),
                SizedBox(height: 16.0),
                ContentPlaceholder(
                  lineType: ContentLineType.twoLines,
                ),
              ],
            ),
          )),
    );
  }
}


class SlideToUnlockPage extends StatelessWidget {
  final List<String> days = <String>[
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday',
    'Sunday'
  ];
  final List<String> months = <String>[
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ];


  SlideToUnlockPage({super.key});


  @override
  Widget build(BuildContext context) {
    final DateTime time = DateTime.now();
    final int hour = time.hour;
    final int minute = time.minute;
    final int day = time.weekday;
    final int month = time.month;
    final int dayInMonth = time.day;
    return Scaffold(
      // appBar: AppBar(
      //   title: const Text('Slide To Unlock'),
      // ),
      body: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Image.asset(
            'assets/background.jpg',
            fit: BoxFit.cover,
          ),
          Positioned(
            top: 48.0,
            right: 0.0,
            left: 0.0,
            child: Center(
              child: Column(
                children: <Widget>[
                  Text(
                    '${hour < 10 ? '0$hour' : '$hour'}:${minute < 10 ? '0$minute' : '$minute'}',
                    style: const TextStyle(
                      fontSize: 60.0,
                      color: Colors.white,
                    ),
                  ),
                  const Padding(
                    padding: EdgeInsets.symmetric(vertical: 4.0),
                  ),
                  Text(
                    '${days[day - 1]}, ${months[month - 1]} $dayInMonth',
                    style: const TextStyle(fontSize: 24.0, color: Colors.white),
                  )
                ],
              ),
            ),
          ),
          Positioned(
              bottom: 32.0,
              left: 0.0,
              right: 0.0,
              child: Center(
                child: Opacity(
                  opacity: 0.8,
                  child: Shimmer.fromColors(
                    baseColor: Colors.black12,
                    highlightColor: Colors.white,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                        Image.asset(
                          'assets/chevron_right.png',
                          height: 20.0,
                        ),
                        const Padding(
                          padding: EdgeInsets.symmetric(horizontal: 4.0),
                        ),
                        const Text(
                          'Slide to unlock',
                          style: TextStyle(
                            fontSize: 28.0,
                          ),
                        )
                      ],
                    ),
                  ),
                ),
              ))
        ],
      ),
    );
  }
}


class BannerPlaceholder extends StatelessWidget {
  const BannerPlaceholder({Key? key}) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: 200.0,
      margin: const EdgeInsets.all(16.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12.0),
        color: Colors.white,
      ),
    );
  }
}


class TitlePlaceholder extends StatelessWidget {
  final double width;


  const TitlePlaceholder({
    Key? key,
    required this.width,
  }) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: width,
            height: 12.0,
            color: Colors.white,
          ),
          const SizedBox(height: 8.0),
          Container(
            width: width,
            height: 12.0,
            color: Colors.white,
          ),
        ],
      ),
    );
  }
}


enum ContentLineType {
  twoLines,
  threeLines,
}


class ContentPlaceholder extends StatelessWidget {
  final ContentLineType lineType;


  const ContentPlaceholder({
    Key? key,
    required this.lineType,
  }) : super(key: key);


  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: Row(
        mainAxisSize: MainAxisSize.max,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
            width: 96.0,
            height: 72.0,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12.0),
              color: Colors.white,
            ),
          ),
          const SizedBox(width: 12.0),
          Expanded(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  width: double.infinity,
                  height: 10.0,
                  color: Colors.white,
                  margin: const EdgeInsets.only(bottom: 8.0),
                ),
                if (lineType == ContentLineType.threeLines)
                  Container(
                    width: double.infinity,
                    height: 10.0,
                    color: Colors.white,
                    margin: const EdgeInsets.only(bottom: 8.0),
                  ),
                Container(
                  width: 100.0,
                  height: 10.0,
                  color: Colors.white,
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

Video Demo:


Conclusion:

The shimmer effect is an attractive way to indicate that content is loading or awaiting data in your Flutter app. With the shimmer package, it's effortless to implement and customize this animation to match your app's design. By following this step-by-step guide, you can easily add a shimmer effect to any widget in your Flutter application and provide a more engaging user experience.

Happy coding!

Description of the image

Related Posts