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實(shí)現(xiàn)加載狀態(tài)視圖切換效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)加載狀態(tài)視圖切換效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android實(shí)現(xiàn)后臺(tái)開啟服務(wù)默默拍照功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)后臺(tái)開啟服務(wù)默默拍照功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
listview的上滑下滑監(jiān)聽,上下滑監(jiān)聽隱藏頂部選項(xiàng)欄的實(shí)例
下面小編就為大家分享一篇listview的上滑下滑監(jiān)聽,上下滑監(jiān)聽隱藏頂部選項(xiàng)欄的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Android?Studio打包?aar實(shí)現(xiàn)步驟示例詳解
這篇文章主要為大家介紹了Android?Studio打包aar步驟示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Android中使用DialogFragment編寫對(duì)話框的實(shí)例教程
這篇文章主要介紹了Android中使用DialogFragment編寫對(duì)話框的實(shí)例教程,DialogFragment也是一種Fragment,因而管理生命周期時(shí)比較給力,需要的朋友可以參考下2016-04-04
微信支付僅能成功調(diào)用一次問(wèn)題的解決方法(Android)
這篇文章主要介紹了微信支付僅能成功調(diào)用一次問(wèn)題的解決方法,感興趣的小伙伴們可以參考一下2016-08-08
Android編程獲取地理位置的經(jīng)度和緯度實(shí)例
這篇文章主要介紹了Android編程獲取地理位置的經(jīng)度和緯度實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了Android操作系統(tǒng)服務(wù)調(diào)用GPS實(shí)現(xiàn)定位的相關(guān)技巧,需要的朋友可以參考下2016-01-01
Android利用ViewPager實(shí)現(xiàn)用戶引導(dǎo)界面效果的方法
這篇文章主要介紹了Android利用ViewPager實(shí)現(xiàn)用戶引導(dǎo)界面效果的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android軟件功能界面的初始化、view實(shí)例化、動(dòng)畫功能實(shí)現(xiàn)與布局相關(guān)技巧,需要的朋友可以參考下2016-07-07

