text_liquid_fill.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import 'dart:math';
  2. import 'package:fis_ui/define.dart';
  3. import 'package:flutter/material.dart';
  4. /// Animation that displays a [text] element, coloring it to look like sloshing
  5. /// water is filling it up.
  6. ///
  7. /// ![TextLiquidFill example](https://raw.githubusercontent.com/aagarwal1012/Animated-Text-Kit/master/display/text_liquid_fill.gif)
  8. class TextLiquidFill extends StatefulWidget implements FWidget {
  9. final Widget? child;
  10. /// Gives [TextStyle] to the text string.
  11. ///
  12. /// By default it is `TextStyle(fontSize: 140, fontWeight: FontWeight.bold)`
  13. final TextStyle textStyle;
  14. /// Gives [TextAlign] to the text string.
  15. ///
  16. /// By default it is [TextAlign.left].
  17. final TextAlign textAlign;
  18. /// Specifies the duration the text should fill with liquid.
  19. ///
  20. /// By default it is set to 6 seconds.
  21. final Duration loadDuration;
  22. /// Specifies the duration that one wave takes to pass the screen.
  23. ///
  24. /// By default it is set to 2 seconds.
  25. final Duration waveDuration;
  26. /// Specifies the height of the box around text
  27. ///
  28. /// By default it is set to 250
  29. final double boxHeight;
  30. /// Specifies the width of the box around text
  31. ///
  32. /// By default it is set to 400
  33. final double boxWidth;
  34. /// String which would be filled by liquid animation
  35. final String text;
  36. /// Specifies the backgroundColor of the box
  37. ///
  38. /// By default it is set to black color
  39. final Color boxBackgroundColor;
  40. /// Specifies the color of the wave
  41. ///
  42. /// By default it is set to blueAccent color
  43. final Color waveColor;
  44. /// Specifies the load limit: (0, 1.0]. This may be used to limit the liquid
  45. /// fill effect to less than 100%.
  46. ///
  47. /// By default, the animation will load to 1.0 (100%).
  48. final double loadUntil;
  49. /// Enable loop. Default false.
  50. final bool loop;
  51. const TextLiquidFill({
  52. Key? key,
  53. required this.text,
  54. this.child,
  55. this.textStyle =
  56. const TextStyle(fontSize: 140, fontWeight: FontWeight.bold),
  57. this.textAlign = TextAlign.left,
  58. this.loadDuration = const Duration(seconds: 6),
  59. this.waveDuration = const Duration(seconds: 2),
  60. this.boxHeight = 250,
  61. this.boxWidth = 400,
  62. this.boxBackgroundColor = Colors.black,
  63. this.waveColor = Colors.blueAccent,
  64. this.loadUntil = 1.0,
  65. this.loop = false,
  66. }) : assert(loadUntil > 0 && loadUntil <= 1.0),
  67. super(key: key);
  68. /// Creates the mutable state for this widget. See [StatefulWidget.createState].
  69. @override
  70. _TextLiquidFillState createState() => _TextLiquidFillState();
  71. }
  72. class _TextLiquidFillState extends State<TextLiquidFill>
  73. with TickerProviderStateMixin {
  74. final _textKey = GlobalKey();
  75. late AnimationController _waveController, _loadController;
  76. late Animation<double> _loadValue;
  77. @override
  78. void initState() {
  79. super.initState();
  80. _waveController = AnimationController(
  81. vsync: this,
  82. duration: widget.waveDuration,
  83. );
  84. _loadController = AnimationController(
  85. vsync: this,
  86. duration: widget.loadDuration,
  87. );
  88. _loadValue = Tween<double>(
  89. begin: 0.1,
  90. end: widget.loadUntil,
  91. ).animate(_loadController);
  92. if (1.0 == widget.loadUntil && widget.loop == false) {
  93. _loadValue.addStatusListener((status) {
  94. if (AnimationStatus.completed == status) {
  95. // Stop the repeating wave when the load has completed to 100%
  96. _waveController.stop();
  97. }
  98. });
  99. }
  100. _waveController.repeat();
  101. if (!widget.loop) _loadController.forward();
  102. if (widget.loop) _loadController.repeat();
  103. }
  104. @override
  105. void dispose() {
  106. _waveController.dispose();
  107. _loadController.dispose();
  108. super.dispose();
  109. }
  110. @override
  111. Widget build(BuildContext context) {
  112. return Stack(
  113. children: <Widget>[
  114. Container(
  115. margin: const EdgeInsets.symmetric(horizontal: 1), // 解决Mobile多出1像素问题
  116. height: widget.boxHeight,
  117. width: widget.boxWidth - 2, // 同上
  118. child: AnimatedBuilder(
  119. animation: _waveController,
  120. builder: (BuildContext context, Widget? child) {
  121. return CustomPaint(
  122. painter: _WavePainter(
  123. textKey: _textKey,
  124. waveValue: _waveController.value,
  125. loadValue: _loadValue.value,
  126. boxHeight: widget.boxHeight,
  127. waveColor: widget.waveColor,
  128. ),
  129. );
  130. },
  131. ),
  132. ),
  133. SizedBox(
  134. height: widget.boxHeight,
  135. width: widget.boxWidth,
  136. child: ShaderMask(
  137. blendMode: BlendMode.srcOut,
  138. shaderCallback: (bounds) => LinearGradient(
  139. colors: [widget.boxBackgroundColor],
  140. stops: const [0.0],
  141. ).createShader(bounds),
  142. child: Container(
  143. color: Colors.transparent,
  144. child: Center(
  145. child: widget.child != null
  146. ? Container(
  147. key: _textKey,
  148. child: widget.child,
  149. )
  150. : Text(
  151. widget.text,
  152. key: _textKey,
  153. style: widget.textStyle,
  154. textAlign: widget.textAlign,
  155. ),
  156. ),
  157. ),
  158. ),
  159. )
  160. ],
  161. );
  162. }
  163. }
  164. class _WavePainter extends CustomPainter {
  165. static const _pi2 = 2 * pi;
  166. final GlobalKey textKey;
  167. final double waveValue;
  168. final double loadValue;
  169. final double boxHeight;
  170. final Color waveColor;
  171. _WavePainter({
  172. required this.textKey,
  173. required this.waveValue,
  174. required this.loadValue,
  175. required this.boxHeight,
  176. required this.waveColor,
  177. });
  178. @override
  179. void paint(Canvas canvas, Size size) {
  180. final RenderBox? textBox =
  181. textKey.currentContext!.findRenderObject() as RenderBox;
  182. if (textBox == null) return;
  183. final textHeight = textBox.size.height;
  184. final baseHeight =
  185. (boxHeight / 2) + (textHeight / 2) - (loadValue * textHeight);
  186. final width = size.width;
  187. final height = size.height;
  188. final path = Path();
  189. path.moveTo(0.0, baseHeight);
  190. for (var i = 0.0; i < width; i++) {
  191. path.lineTo(i, baseHeight + sin(_pi2 * (i / width + waveValue)) * 8);
  192. }
  193. path.lineTo(width, height);
  194. path.lineTo(0.0, height);
  195. path.close();
  196. final wavePaint = Paint()..color = waveColor;
  197. canvas.drawPath(path, wavePaint);
  198. }
  199. @override
  200. bool shouldRepaint(CustomPainter oldDelegate) {
  201. return true;
  202. }
  203. }