Sonnat Project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
5.6 KiB

1 year ago
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/rendering.dart';
  3. enum ShimmerDirection { ltr, rtl, ttb, btt }
  4. @immutable
  5. class Shimmer extends StatefulWidget {
  6. final Widget child;
  7. final Duration period;
  8. final ShimmerDirection direction;
  9. final Gradient gradient;
  10. final int loop;
  11. final bool enabled;
  12. const Shimmer({
  13. super.key,
  14. required this.child,
  15. required this.gradient,
  16. this.direction = ShimmerDirection.rtl,
  17. this.period = const Duration(milliseconds: 1500),
  18. this.loop = 0,
  19. this.enabled = true,
  20. });
  21. Shimmer.fromColors({
  22. super.key,
  23. required this.child,
  24. required Color baseColor,
  25. required Color highlightColor,
  26. this.period = const Duration(milliseconds: 1500),
  27. this.direction = ShimmerDirection.rtl,
  28. this.loop = 0,
  29. this.enabled = true,
  30. }) : gradient = LinearGradient(
  31. begin: Alignment.topLeft,
  32. end: Alignment.centerRight,
  33. colors: <Color>[baseColor, baseColor, highlightColor, baseColor, baseColor],
  34. stops: const <double>[0.0, 0.35, 0.5, 0.65, 1.0],
  35. );
  36. @override
  37. State<Shimmer> createState() => _ShimmerState();
  38. @override
  39. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  40. super.debugFillProperties(properties);
  41. properties.add(DiagnosticsProperty<Gradient>('gradient', gradient, defaultValue: null));
  42. properties.add(EnumProperty<ShimmerDirection>('direction', direction));
  43. properties.add(DiagnosticsProperty<Duration>('period', period, defaultValue: null));
  44. properties.add(DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null));
  45. properties.add(DiagnosticsProperty<int>('loop', loop, defaultValue: 0));
  46. }
  47. }
  48. class _ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
  49. late AnimationController _controller;
  50. int _count = 0;
  51. @override
  52. void initState() {
  53. super.initState();
  54. _controller = AnimationController(vsync: this, duration: widget.period)
  55. ..addStatusListener((status) {
  56. if (status != AnimationStatus.completed) {
  57. return;
  58. }
  59. _count++;
  60. if (widget.loop <= 0) {
  61. _controller.repeat();
  62. } else if (_count < widget.loop) {
  63. _controller.forward(from: 0.0);
  64. }
  65. });
  66. if (widget.enabled) {
  67. _controller.forward();
  68. }
  69. }
  70. @override
  71. void didUpdateWidget(Shimmer oldWidget) {
  72. if (widget.enabled) {
  73. _controller.forward();
  74. } else {
  75. _controller.stop();
  76. }
  77. super.didUpdateWidget(oldWidget);
  78. }
  79. @override
  80. Widget build(BuildContext context) {
  81. return AnimatedBuilder(
  82. animation: _controller,
  83. child: widget.child,
  84. builder: (context, child) => _Shimmer(
  85. direction: widget.direction,
  86. gradient: widget.gradient,
  87. percent: _controller.value,
  88. child: child,
  89. ),
  90. );
  91. }
  92. @override
  93. void dispose() {
  94. _controller.dispose();
  95. super.dispose();
  96. }
  97. }
  98. @immutable
  99. class _Shimmer extends SingleChildRenderObjectWidget {
  100. final double percent;
  101. final ShimmerDirection direction;
  102. final Gradient gradient;
  103. const _Shimmer({
  104. super.child,
  105. required this.percent,
  106. required this.direction,
  107. required this.gradient,
  108. });
  109. @override
  110. _ShimmerFilter createRenderObject(BuildContext context) {
  111. return _ShimmerFilter(percent, direction, gradient);
  112. }
  113. @override
  114. void updateRenderObject(BuildContext context, _ShimmerFilter shimmer) {
  115. shimmer.percent = percent;
  116. shimmer.gradient = gradient;
  117. shimmer.direction = direction;
  118. }
  119. }
  120. class _ShimmerFilter extends RenderProxyBox {
  121. ShimmerDirection _direction;
  122. Gradient _gradient;
  123. double _percent;
  124. _ShimmerFilter(this._percent, this._direction, this._gradient);
  125. @override
  126. ShaderMaskLayer? get layer => super.layer as ShaderMaskLayer?;
  127. @override
  128. bool get alwaysNeedsCompositing => child != null;
  129. set percent(double newValue) {
  130. if (newValue == _percent) {
  131. return;
  132. }
  133. _percent = newValue;
  134. markNeedsPaint();
  135. }
  136. set gradient(Gradient newValue) {
  137. if (newValue == _gradient) {
  138. return;
  139. }
  140. _gradient = newValue;
  141. markNeedsPaint();
  142. }
  143. set direction(ShimmerDirection newDirection) {
  144. if (newDirection == _direction) {
  145. return;
  146. }
  147. _direction = newDirection;
  148. markNeedsLayout();
  149. }
  150. @override
  151. void paint(PaintingContext context, Offset offset) {
  152. if (child != null) {
  153. assert(needsCompositing);
  154. final double width = child!.size.width;
  155. final double height = child!.size.height;
  156. Rect rect;
  157. double dx, dy;
  158. if (_direction == ShimmerDirection.rtl) {
  159. dx = _offset(width, -width, _percent);
  160. dy = 0.0;
  161. rect = Rect.fromLTWH(dx - width, dy, 3 * width, height);
  162. } else if (_direction == ShimmerDirection.ttb) {
  163. dx = 0.0;
  164. dy = _offset(-height, height, _percent);
  165. rect = Rect.fromLTWH(dx, dy - height, width, 3 * height);
  166. } else if (_direction == ShimmerDirection.btt) {
  167. dx = 0.0;
  168. dy = _offset(height, -height, _percent);
  169. rect = Rect.fromLTWH(dx, dy - height, width, 3 * height);
  170. } else {
  171. dx = _offset(-width, width, _percent);
  172. dy = 0.0;
  173. rect = Rect.fromLTWH(dx - width, dy, 3 * width, height);
  174. }
  175. layer ??= ShaderMaskLayer();
  176. layer!
  177. ..shader = _gradient.createShader(rect)
  178. ..maskRect = offset & size
  179. ..blendMode = BlendMode.srcIn;
  180. context.pushLayer(layer!, super.paint, offset);
  181. } else {
  182. layer = null;
  183. }
  184. }
  185. double _offset(double start, double end, double percent) {
  186. return start + (end - start) * percent;
  187. }
  188. }