Flutter?文字中劃線動(dòng)畫StrikeThroughTextAnimation
概述
接上文 CheckBoxAnimation 動(dòng)畫,在加上文字的動(dòng)畫,剛好可以做一個(gè)組合的列表動(dòng)畫。文字部分動(dòng)畫主要就是左右移動(dòng)、顏色變化以及在繪制文字中劃線。
效果預(yù)覽
基本使用
StrikeThroughText( text: "1. Task Item StrikeThroughText", textStyle: const TextStyle( fontSize: 18, ), inactiveTextColor: Colors.red, textColor: Colors.blue, strikethrough: isCheck, onChange: (value) { setState(() { isCheck = value; }); }, )
實(shí)現(xiàn)
1、布局
首先完成 widget 的布局和樣式,這里采用了 Stack 布局,首先添加文字和文字樣式,在文字的中間放置一個(gè)橫線作為中劃線。 大致布局如下:
Stack( children: [ Text( "Task Item", maxLines: 1, softWrap: false, style: TextStyle( fontSize: 18, ), ), Positioned( top: 0, bottom: 0, left: 0, right: 0, child: CustomPaint( painter: StrikeThroughTextPainter( ..., ), ), ), ], );
2、繪制中劃線
繪制中劃線,首先需要知道要繪制多長(zhǎng)。這里可以使用 TextPainter
來(lái)測(cè)繪文字的寬高,這里寫成一個(gè)通用的方法,傳入 Text 的text和textStyle,返回文字的寬高:
class TextSizeBox { final double width; final double height; TextSizeBox({required this.width, required this.height}); factory TextSizeBox.fromText(String text, {TextStyle? textStyle}) { final TextPainter textPainter = TextPainter( text: TextSpan(text: text, style: textStyle), maxLines: 1, textDirection: TextDirection.ltr, )..layout(minWidth: 0, maxWidth: double.infinity); return TextSizeBox(width: textPainter.width, height: textPainter.height); } }
知道了文字的寬就等于知道繪制文字的中劃線寬度了。
StrikeThroughTextPainter( width: TextSizeBox.fromText(widget.text, textStyle: widget.textStyle).width, height: 2.0, color: Colors.grey, )
class StrikeThroughTextPainter extends CustomPainter { final double width; final double height; final Color color; StrikeThroughTextPainter( {required this.width, required this.height, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..strokeWidth = height ..strokeCap = StrokeCap.round; if (width > 0) { canvas.drawLine( Offset(0, size.height / 2), Offset(width > size.width ? size.width : width, size.height / 2), paint); } } @override bool shouldRepaint(StrikeThroughTextPainter oldDelegate) { return width != oldDelegate.width || height != oldDelegate.height; } }
3、動(dòng)畫
首先是左右移動(dòng)動(dòng)畫,先創(chuàng)建一個(gè) AnimationController
,在創(chuàng)建一個(gè)Tween<Offset>來(lái)控制左右移動(dòng)的偏移量
_offsetController = AnimationController( vsync: this, duration: const Duration(milliseconds: 100), ); _offsetAnimation = Tween<Offset>( begin: const Offset(0.0, 0.0), end: const Offset(0.2, 0.0), ).animate(CurvedAnimation( parent: _offsetController, curve: Curves.easeInOut, ));
使用 SlideTransition
來(lái)控制左右平移偏移量
SlideTransition( position: _offsetAnimation, child: Stack( ...... ), )
因?yàn)轭伾兓蛣澲袆澗€是同步進(jìn)行的,所以只需要?jiǎng)?chuàng)建一個(gè)AnimationController
來(lái)控制顏色和進(jìn)度的動(dòng)畫
_animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), value: 1, ); _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController); _animationColor = ColorTween( begin: Colors.black87, end: Colors.grey) .animate(_animationController);
接下來(lái)就是在需要?jiǎng)赢嫷?widget 上放上動(dòng)畫就可以了.
完整代碼
import 'package:flutter/material.dart'; class StrikeThroughText extends StatefulWidget { final String text; final TextStyle textStyle; final bool strikethrough; final Color? textColor; final Color? inactiveTextColor; final ValueChanged? onChange; const StrikeThroughText({ Key? key, required this.text, required this.textStyle, this.strikethrough = false, this.textColor, this.inactiveTextColor, this.onChange, }) : super(key: key); @override StrikeThroughTextState createState() => StrikeThroughTextState(); } class StrikeThroughTextState extends State<StrikeThroughText> with TickerProviderStateMixin { late AnimationController _animationController; late Animation<double> _animation; late Animation _animationColor; late AnimationController _offsetController; late Animation<Offset> _offsetAnimation; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), value: widget.strikethrough ? 1 : 0, ); _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController); _animationColor = ColorTween( begin: widget.textColor ?? Colors.black87, end: widget.inactiveTextColor ?? Colors.grey) .animate(_animationController); _offsetController = AnimationController( vsync: this, duration: const Duration(milliseconds: 100), ); _offsetAnimation = Tween<Offset>( begin: const Offset(0.0, 0.0), end: const Offset(0.2, 0.0), ).animate(CurvedAnimation( parent: _offsetController, curve: Curves.easeInOut, )); } @override void didUpdateWidget(covariant StrikeThroughText oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.strikethrough != widget.strikethrough) { if (widget.strikethrough) { startAnimation(); } else { reset(); } } } @override void dispose() { _animationController.dispose(); _offsetController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () { if (widget.strikethrough) { widget.onChange?.call(false); } else { widget.onChange?.call(true); } }, child: SlideTransition( position: _offsetAnimation, child: Stack( children: [ AnimatedBuilder( animation: _animationController, builder: (context, child) { return Text( widget.text, maxLines: 1, softWrap: false, style: widget.textStyle.copyWith( color: _animationColor.value, overflow: TextOverflow.clip, ), ); }), // AnimatedDefaultTextStyle( // style: widget.textStyle..copyWith(color: _animationColor.value), // duration: const Duration(milliseconds: 500), // child: Text(widget.text), // ), AnimatedBuilder( animation: _animation, builder: (context, child) { return Positioned( left: 0, right: 0, top: 0, bottom: 0, child: CustomPaint( painter: StrikeThroughTextPainter( width: TextSizeBox.fromText(widget.text, textStyle: widget.textStyle) .width * _animation.value, height: 2.0, color: widget.inactiveTextColor ?? Colors.grey, ), ), ); }, ), ], ), ), ); } void startAnimation() async { _animationController.reset(); await _offsetController.forward(); await _offsetController.reverse(); _animationController.forward(); } void reset() { _animationController.reset(); } } class StrikeThroughTextPainter extends CustomPainter { final double width; final double height; final Color color; StrikeThroughTextPainter( {required this.width, required this.height, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..strokeWidth = height ..strokeCap = StrokeCap.round; if (width > 0) { canvas.drawLine( Offset(0, size.height / 2), Offset(width > size.width ? size.width : width, size.height / 2), paint); } } @override bool shouldRepaint(StrikeThroughTextPainter oldDelegate) { return width != oldDelegate.width || height != oldDelegate.height; } } class TextSizeBox { final double width; final double height; TextSizeBox({required this.width, required this.height}); factory TextSizeBox.fromText(String text, {TextStyle? textStyle}) { final TextPainter textPainter = TextPainter( text: TextSpan(text: text, style: textStyle), maxLines: 1, textDirection: TextDirection.ltr, )..layout(minWidth: 0, maxWidth: double.infinity); return TextSizeBox(width: textPainter.width, height: textPainter.height); } }
以上就是Flutter 文字中劃線動(dòng)畫StrikeThroughTextAnimation的詳細(xì)內(nèi)容,更多關(guān)于Flutter 文字中劃線的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android基于ListView和CheckBox實(shí)現(xiàn)多選和全選記錄的功能
本篇文章主要介紹了android基于ListView和CheckBox實(shí)現(xiàn)多選和全選記錄的功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-11-11android列表控件實(shí)現(xiàn)展開、收縮功能
這篇文章主要為大家詳細(xì)介紹了android支持展開/收縮功能的列表控件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11Android自定義View實(shí)現(xiàn)彈性小球效果
前段時(shí)間看到一個(gè)功能,是一個(gè)小球沿著固定軌跡彈動(dòng)的效果,那么這篇文章小編給大家分享在Android中如何自定義View來(lái)實(shí)現(xiàn)彈性小球的效果,有需要的可以參考借鑒。2016-09-09Android賬號(hào)注冊(cè)實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果
這篇文章主要為大家詳細(xì)介紹了Android賬號(hào)注冊(cè)過(guò)程中實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05Android UI設(shè)計(jì)系列之ImageView實(shí)現(xiàn)ProgressBar旋轉(zhuǎn)效果(1)
這篇文章主要為大家詳細(xì)介紹了Android UI設(shè)計(jì)之ImageView實(shí)現(xiàn)ProgressBar旋轉(zhuǎn)效果,具有一定的實(shí)用性和參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06Android CountDownTimer實(shí)現(xiàn)倒計(jì)時(shí)器
這篇文章主要為大家詳細(xì)介紹了Android CountDownTimer實(shí)現(xiàn)倒計(jì)時(shí)效果的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02RecyclerVIew實(shí)現(xiàn)懸浮吸頂效果
這篇文章主要為大家詳細(xì)介紹了RecyclerVIew實(shí)現(xiàn)懸浮吸頂效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09