import 'package:flutter/material.dart'; class Skeleton extends StatelessWidget { final Widget child; const Skeleton({ required this.child, super.key, }); @override Widget build(BuildContext context) { var shimmerGradient = LinearGradient( colors: [ Colors.transparent, Theme.of(context).colorScheme.background.withAlpha(10), Theme.of(context).colorScheme.background.withAlpha(10), Colors.transparent, ], stops: const [ 0.1, 0.3, 0.5, 0.7, ], begin: const Alignment(-1.0, -0.3), end: const Alignment(1.0, 0.9), tileMode: TileMode.clamp, ); return Shimmer( linearGradient: shimmerGradient, child: ShimmerLoading( isLoading: true, child: child, ), ); } } class Shimmer extends StatefulWidget { static ShimmerState? of(BuildContext context) { return context.findAncestorStateOfType(); } const Shimmer({ super.key, required this.linearGradient, this.child, }); final LinearGradient linearGradient; final Widget? child; @override ShimmerState createState() => ShimmerState(); } class ShimmerState extends State with SingleTickerProviderStateMixin { late AnimationController _shimmerController; @override void initState() { super.initState(); _shimmerController = AnimationController.unbounded(vsync: this) ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)); } @override void dispose() { _shimmerController.dispose(); super.dispose(); } LinearGradient get gradient => LinearGradient( colors: widget.linearGradient.colors, stops: widget.linearGradient.stops, begin: widget.linearGradient.begin, end: widget.linearGradient.end, transform: _SlidingGradientTransform( slidePercent: _shimmerController.value, ), ); bool get isSized => (context.findRenderObject() as RenderBox?)?.hasSize ?? false; Size get size => (context.findRenderObject() as RenderBox).size; Offset getDescendantOffset({ required RenderBox descendant, Offset offset = Offset.zero, }) { final shimmerBox = context.findRenderObject() as RenderBox; return descendant.localToGlobal(offset, ancestor: shimmerBox); } Listenable get shimmerChanges => _shimmerController; @override Widget build(BuildContext context) { return widget.child ?? const SizedBox(); } } class _SlidingGradientTransform extends GradientTransform { const _SlidingGradientTransform({ required this.slidePercent, }); final double slidePercent; @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0); } } class ShimmerLoading extends StatefulWidget { const ShimmerLoading({ super.key, required this.isLoading, required this.child, }); final bool isLoading; final Widget child; @override State createState() => _ShimmerLoadingState(); } class _ShimmerLoadingState extends State { Listenable? _shimmerChanges; @override void didChangeDependencies() { super.didChangeDependencies(); if (_shimmerChanges != null) { _shimmerChanges!.removeListener(_onShimmerChange); } _shimmerChanges = Shimmer.of(context)?.shimmerChanges; if (_shimmerChanges != null) { _shimmerChanges!.addListener(_onShimmerChange); } } @override void dispose() { _shimmerChanges?.removeListener(_onShimmerChange); super.dispose(); } void _onShimmerChange() { if (widget.isLoading) { setState(() {}); } } @override Widget build(BuildContext context) { if (!widget.isLoading) { return widget.child; } final shimmer = Shimmer.of(context)!; if (!shimmer.isSized) { return const SizedBox(); } final shimmerSize = shimmer.size; final gradient = shimmer.gradient; final offsetWithinShimmer = shimmer.getDescendantOffset( descendant: context.findRenderObject() as RenderBox, ); return ShaderMask( blendMode: BlendMode.srcATop, shaderCallback: (bounds) { return gradient.createShader( Rect.fromLTWH( -offsetWithinShimmer.dx, -offsetWithinShimmer.dy, shimmerSize.width, shimmerSize.height, ), ); }, child: widget.child, ); } }