Reputation: 790
I followed the official tutorial by the Flutter team for creating a shimmer effect: Create a shimmer loading effect
I copied most of the code into a file and updated some lines with e.g. custom colors:
LinearGradient scGradient(BuildContext context) {
SCThemeData theme = SCTheme.of(context);
return LinearGradient(
colors: [
theme.colors.shimmerBackground,
theme.colors.shimmerLine,
theme.colors.shimmerBackground
],
stops: const [
0.1,
0.3,
0.4,
],
begin: const Alignment(-1.0, -0.3),
end: const Alignment(1.0, 0.3),
tileMode: TileMode.clamp,
);
}
class SCShimmerLoading extends StatefulWidget {
const SCShimmerLoading({
super.key,
required this.isLoading,
required this.child,
});
final bool isLoading;
final Widget child;
@override
State<SCShimmerLoading> createState() => _SCShimmerLoadingState();
}
class _SCShimmerLoadingState extends State<SCShimmerLoading> {
Listenable? _shimmerChanges;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_shimmerChanges != null) {
_shimmerChanges!.removeListener(_onShimmerChange);
}
_shimmerChanges = SCShimmer.of(context)?.shimmerChanges;
if (_shimmerChanges != null) {
_shimmerChanges!.addListener(_onShimmerChange);
}
}
@override
void dispose() {
_shimmerChanges?.removeListener(_onShimmerChange);
super.dispose();
}
void _onShimmerChange() {
if (widget.isLoading) {
setState(() {
// update the shimmer painting.
});
}
}
@override
Widget build(BuildContext context) {
if (!widget.isLoading) {
return widget.child;
}
final shimmer = SCShimmer.of(context)!;
if (!shimmer.isSized) {
// The ancestor Shimmer widget isn’t laid
// out yet. Return an empty box.
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,
);
}
}
class SCShimmer extends StatefulWidget {
static SCShimmerState? of(BuildContext context) {
return context.findAncestorStateOfType<SCShimmerState>();
}
const SCShimmer({
super.key,
required this.linearGradient,
this.child,
});
final LinearGradient linearGradient;
final Widget? child;
@override
SCShimmerState createState() => SCShimmerState();
}
class SCShimmerState extends State<SCShimmer>
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();
}
Gradient get gradient => LinearGradient(
colors: widget.linearGradient.colors,
stops: widget.linearGradient.stops,
begin: widget.linearGradient.begin,
end: widget.linearGradient.end,
transform: _SCSlidingGradientTransform(
slidePercent: _shimmerController.value,
),
);
Listenable get shimmerChanges => _shimmerController;
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);
}
@override
Widget build(BuildContext context) {
return widget.child ?? const SizedBox();
}
}
class _SCSlidingGradientTransform extends GradientTransform {
const _SCSlidingGradientTransform({
required this.slidePercent,
});
final double slidePercent;
@override
Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
}
}
class SCShimmerLoadingBox extends StatelessWidget {
const SCShimmerLoadingBox({
super.key,
required this.size,
this.borderRadius,
});
final Size size;
final double? borderRadius;
@override
Widget build(BuildContext context) {
return Container(
width: size.width,
height: size.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius ?? size.height / 2.0),
color: Colors.black,
),
);
}
}
I am then using it e.g. for displaying a shimmer loading effect everytime the text inside a TextField
changes to fetch new results from an API:
class _LocationPageState extends ConsumerState<LocationPage> {
@override
Widget build(BuildContext context) {
final AsyncValue<List<LocalLocation>> locationsValue = ref.watch(fetchLocalLocationsProvider);
return SCShimmer(
linearGradient: scGradient(context),
child: SCScaffold(
body: searchValue.when(
data: (List<LocationModel> locations) => ...
error: (Object e, StackTrace s) => ...,
loading: () => SCShimmerLoading(
isLoading: true,
child: ListView.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) => SCPadding(
padding: const SCEdgeInsets.symmetric(vertical: SCGapSize.regular),
child: Column(
children: [
SCShimmerLoadingBox(
size: Size(
MediaQuery.of(context).size.width - 2.0 * SCGapSize.semiBig.getSpacing(theme),
14.0,
),
borderRadius: 7.0
),
...
],
),
),
),
),
),
),
),
);
}
}
For whatever reason it always throws the following exception everytime the shimmer effect rebuilds / gets shown again after initially showing it:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════ The
following _TypeError was thrown building SCShimmerLoading(dirty,
state:
_SCShimmerLoadingState#d5a76): type 'Null' is not a subtype of type 'RenderBox' in type cast
The relevant error-causing widget was: SCShimmerLoading
SCShimmerLoading:file:///Users/fleeser/Desktop/scial_app/sci
al_app_ui/lib/src/widgets/location/sc_location_loading.dart: 18:12
When the exception was thrown, this was the stack:
#0 _SCShimmerLoadingState.build (package:scial_app_ui/src/widgets/base/sc_shimmer.dart:84:46)
#1 StatefulElement.build (package:flutter/src/widgets/framework.dart:5198:27)
#2 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5086:15)
#3 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#4 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#5 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:5068:5)
#6 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5242:11)
#7 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5062:5) ... Normal element mounting (7 frames)
#14 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3971:16)
#15 Element.updateChild (package:flutter/src/widgets/framework.dart:3702:20)
#16 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#17 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#18 StatelessElement.update (package:flutter/src/widgets/framework.dart:5162:5)
#19 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#20 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#21 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#22 StatelessElement.update (package:flutter/src/widgets/framework.dart:5162:5)
#23 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#24 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#25 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#26 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#27 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#28 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#29 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#30 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#31 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#32 RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:6093:32)
#33 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6595:17)
#34 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#35 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#36 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#37 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#38 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#39 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#40 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#41 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#42 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#43 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#44 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#45 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#46 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#47 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#48 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#49 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#50 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#51 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#52 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#53 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#54 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#55 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#56 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#57 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#58 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6442:14)
#59 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#60 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#61 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#62 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#63 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#64 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6442:14)
#65 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#66 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#67 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#68 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#69 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#70 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#71 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#72 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#73 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#74 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#75 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#76 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#77 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#78 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#79 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#80 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#81 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#82 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#83 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#84 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#85 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#86 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#87 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#88 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#89 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#90 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#91 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#92 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#93 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#94 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#95 ProxyElement.update (package:flutter/src/widgets/framework.dart:5417:5)
#96 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#97 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#98 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#99 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#100 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#101 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#102 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6442:14)
#103 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#104 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#105 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#106 StatelessElement.update (package:flutter/src/widgets/framework.dart:5162:5)
#107 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#108 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#109 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#110 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#111 StatefulElement.update (package:flutter/src/widgets/framework.dart:5274:5)
#112 Element.updateChild (package:flutter/src/widgets/framework.dart:3686:15)
#113 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5111:16)
#114 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#115 Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#116 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2780:19)
#117 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:903:21)
#118 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:358:5)
#119 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1284:15)
#120 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1214:9)
#121 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1072:5)
#122 _invoke (dart:ui/hooks.dart:142:13)
#123 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:359:5)
#124 _drawFrame (dart:ui/hooks.dart:112:31)
══════════════════════════════════════════════════════════════
══════════════════════════════════════
Upvotes: 1
Views: 542
Reputation: 790
Replacing
final offsetWithinShimmer = shimmer.getDescendantOffset(
descendant: context.findRenderObject() as RenderBox,
);
with
Offset offsetWithinShimmer = Offset.zero;
if(context.findRenderObject() != null) {
final box = context.findRenderObject() as RenderBox;
offsetWithinShimmer = shimmer.getDescendantOffset(
descendant: box,
);
}
solves this issue.
Based on: https://github.com/flutter/website/issues/9372#issuecomment-1713651864
Upvotes: 0