192 lines
4.4 KiB
Dart
192 lines
4.4 KiB
Dart
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.surface.withAlpha(10),
|
|
Theme.of(context).colorScheme.surface.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<ShimmerState>();
|
|
}
|
|
|
|
const Shimmer({
|
|
super.key,
|
|
required this.linearGradient,
|
|
this.child,
|
|
});
|
|
|
|
final LinearGradient linearGradient;
|
|
final Widget? child;
|
|
|
|
@override
|
|
ShimmerState createState() => ShimmerState();
|
|
}
|
|
|
|
class ShimmerState extends State<Shimmer> 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<ShimmerLoading> createState() => _ShimmerLoadingState();
|
|
}
|
|
|
|
class _ShimmerLoadingState extends State<ShimmerLoading> {
|
|
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,
|
|
);
|
|
}
|
|
}
|