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:
direction
: Defines the direction of the shimmer animation (Default:ShimmerDirection.ltr
).loop
: Determines if the shimmer animation should loop (Default:true
).enabled
: Allows you to control when the shimmer effect should be active (Default:true
).duration
: Defines the duration of the shimmer animation (Default:const Duration(milliseconds: 1500)
).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!