Row(children: [Expanded(child: LayoutBuilder(builder:(BuildContext context, BoxConstraints constraints) =>IntlWidget(maxWidth: constraints.maxWidth,child: Text('你好啊啊封疆大吏你好你好啊啊封疆大吏你好啊啊封疆大吏啊啊封疆大吏',),),),),// SizedBox(// width: 5,// ),// Spacer(), SizedBox(width: 5,),IntlWidget(maxWidth: calculateTextWidth('你好啊啊你好啊啊',expandedText: '你好啊啊封疆大吏你好你好啊啊封疆大吏你好啊啊封疆大吏啊啊封疆大吏',spacingWidth: 5,maxWidth: MediaQuery.of(context).size.width,maxLines: 1),child: Text('你好啊啊你好啊啊',),),],),
滚动部件
import 'dart:math';import 'package:flutter/material.dart';/// ⚠️direction目前切换方向需要重新运行才会正常,不要代码动态控制方向切换 class IntlWidget extends StatefulWidget {final Widget child;/// 动画方向, horizontal时要设置maxWidth才有效, vertical时要设置maxHeight才有效 final Axis direction;//// /// 固定宽// final double? width;//// /// 固定高// final double? height;/// 最大宽final double? maxWidth;/// 最大高final double? maxHeight;const IntlWidget({super.key,required this.child,this.direction = Axis.horizontal,// this.width,// this.height,this.maxWidth,this.maxHeight});@overrideState<IntlWidget> createState() => _IntlWidgetState(); }class _IntlWidgetState extends State<IntlWidget>with SingleTickerProviderStateMixin {GlobalKey _key = GlobalKey();AnimationController? _controller;Animation<double>? _animation;double? _autoWidth;set autoWidth(double value) {if (widget.maxWidth != null) {_autoWidth = min(value, widget.maxWidth!);} else {_autoWidth = value;}}double get autoWidth {// if (widget.width != null) {// return widget.width!;// }return _autoWidth ?? 0;}double? _autoHeight;set autoHeight(double value) {if (widget.maxHeight != null) {_autoHeight = min(value, widget.maxHeight!);} else {_autoHeight = value;}}double get autoHeight {// if (widget.height != null) {// return widget.height!;// }return _autoHeight ?? 0;}Size? _contentSize;/// 动画偏移值double offsetX = 0, offsetY = 0;/// 是否缩放bool scaleDown = false;/// 横向差值范围小于此值时做缩放final scaleDownValue = 6;@overridevoid initState() {super.initState();WidgetsBinding.instance.addPostFrameCallback((_) {final renderBox = _key.currentContext?.findRenderObject() as RenderBox?;if (renderBox != null) {final size = renderBox.size;checkAnimate(size);}});}@overrideWidget build(BuildContext context) {return Container(// color: Colors.red, width: autoWidth,height: autoHeight,child: scaleDown? FittedBox(fit: BoxFit.scaleDown,child: Container(key: _key,child: widget.child,),): Stack(// clipBehavior: Clip.none, children: [Positioned(top: offsetY,left: offsetX,// width: widget.direction == Axis.horizontal// ? null// : _autoWidth,//(widget.width ?? _autoWidth),// height: widget.direction == Axis.vertical// ? null// : _autoHeight,//(widget.height ?? _autoHeight), child: Container(key: _key,child: widget.child,))],),);}@overridevoid didUpdateWidget(covariant IntlWidget oldWidget) {if (oldWidget.direction != widget.direction) {// direction = true;// offsetY = 0;// offsetX = 0;// setState(() {});// direction = false;// setState(() {}); }super.didUpdateWidget(oldWidget);WidgetsBinding.instance.addPostFrameCallback((_) {final renderBox = _key.currentContext?.findRenderObject() as RenderBox?;if (renderBox != null) {final size = renderBox.size;checkAnimate(size);}});}@overridevoid dispose() {_controller?.dispose();super.dispose();}checkAnimate(Size contentSize) {_contentSize = contentSize;// if (widget.direction == )double? startAnimateWidth = widget.maxWidth;// ?? widget.width;double? startAnimateHeight = widget.maxHeight;// ?? widget.height;// print('child 宽度: ${contentSize.width};;;$startAnimateWidth, 高度: ${contentSize.height};; $startAnimateHeight');if (widget.direction == Axis.horizontal &&startAnimateWidth != null &&contentSize.width > startAnimateWidth) {_controller?.stop();final animateValue = contentSize.width - startAnimateWidth;autoWidth = startAnimateWidth;autoHeight = contentSize.height;scaleDown = animateValue.abs() < scaleDownValue;offsetY = (autoHeight - contentSize.height) * 0.5;// print("开始动画 scaleDownValue = $scaleDownValue");if (scaleDown) {offsetX = 0;setState(() {});return;}// print("开始动画 scaleDown = $scaleDown");final animateSec = (animateValue / 10).ceil();_controller ??= AnimationController(vsync: this,);_controller?.duration = Duration(seconds: animateSec);_animation?.removeListener(_updateOffsetX);_animation?.removeListener(_updateOffsetY);_animation =Tween<double>(begin: 0.0, end: animateValue).animate(CurvedAnimation(parent: _controller!,curve: Curves.easeInOutSine,));_animation?.addListener(_updateOffsetX);// 启动动画_controller?.repeat(reverse: true,);} else if (widget.direction == Axis.vertical &&startAnimateHeight != null &&contentSize.height > startAnimateHeight) {_controller?.stop();final animateValue = contentSize.height - startAnimateHeight;autoWidth = contentSize.width;autoHeight = startAnimateHeight;scaleDown = animateValue.abs() < scaleDownValue;offsetX = (autoWidth - contentSize.width) * 0.5;// print("开始动画 vertical scaleDownValue = $scaleDownValue");if (scaleDown) {offsetY = 0;setState(() {});_controller?.stop();return;}// print("开始动画 vertical scaleDown = $scaleDown");final animateSec = (animateValue / 4).ceil();_controller ??= AnimationController(vsync: this,);_controller?.duration = Duration(seconds: animateSec);_animation?.removeListener(_updateOffsetX);_animation?.removeListener(_updateOffsetY);_animation =Tween<double>(begin: 0.0, end: animateValue).animate(CurvedAnimation(parent: _controller!,curve: Curves.easeInOutSine,));_animation?.addListener(_updateOffsetY);// 启动动画_controller?.repeat(reverse: true,);} else {setState(() {autoWidth = contentSize.width;autoHeight = contentSize.height;offsetX = 0;offsetY = 0;});_controller?.stop();}}// 更新横向偏移void _updateOffsetX() {setState(() {offsetX = -_animation!.value;});}// 更新纵向偏移void _updateOffsetY() {setState(() {offsetY = -_animation!.value;});} }