我构建了一个解决方案,该解决方案可以解决我认为简单的问题,但是花了很多时间才能使它起作用。当我的解决方案有效时,我想知道我是否过度设计了一些应该简单得多的东西。
我正在尝试为两个不同的文本小部件之间的下划线动画制作动画。在示例解决方案中,我包括两个文本小部件“ Sign In”和“ Sign Up”。我的应用程序已本地化为多种语言,因此这些单词可能会因地区而异。我的应用程序还需要响应(窗口/方向更改)。我尝试了我能想到的Stack,Positioned,Row,Column的所有变体,但无法正常工作。
我的最终解决方案是将覆盖动画容器窗口小部件与GetX状态管理结合使用。为了使我的解决方案在下面起作用,您需要将get: ^3.15.0
添加到pubspec.yaml文件中。此解决方案不适用于setState((){})
。
我想知道是否有一种更简单/更好的解决方案来对PositionedContainer进行动画处理,以使其与其他两个小部件的位置和大小对齐。请记住需要支持本地化和响应能力。无论哪种方式,也许这都可以作为示例叠加层的示例,对他人有用。
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:get/get.dart';
void main() {
runApp(OverlayApp());
}
class OverlayApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Overlay Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: OverlayHome(),
);
}
}
class OverlayHome extends StatelessWidget {
var _isSignIn = true.obs;
final _signinKey = GlobalKey();
final _signupKey = GlobalKey();
OverlayEntry _indicatorOverlayEntry;
void _setIndicator() {
_indicatorOverlayEntry?.remove();
SchedulerBinding.instance.addPostFrameCallback((_) {
_indicatorOverlayEntry = _overlayEntry();
Overlay.of(_signinKey.currentContext).insert(_indicatorOverlayEntry);
});
}
OverlayEntry _overlayEntry() {
RenderBox signinRenderBox = _signinKey.currentContext.findRenderObject();
RenderBox signupRenderBox = _signupKey.currentContext.findRenderObject();
final signinWidgetPosition = signinRenderBox.localToGlobal(Offset.zero);
final signupWidgetPosition = signupRenderBox.localToGlobal(Offset.zero);
final signinWidgetSize = signinRenderBox.size;
final signupWidgetSize = signupRenderBox.size;
return OverlayEntry(
builder: (context) => Stack(
children: [
Obx(
() => AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutQuint,
top: signinWidgetPosition.dy + signinWidgetSize.height,
height: 3.0,
left: (_isSignIn.value)
? signinWidgetPosition.dx
: signupWidgetPosition.dx,
width: (_isSignIn.value)
? signinWidgetSize.width
: signupWidgetSize.width,
child: Container(color: Theme.of(context).primaryColor),
),
)
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
_setIndicator();
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: 20),
Text(
'Animated Overlay Test',
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
SizedBox(height: 30),
Obx(
() => Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FlatButton(
child: Text(
"Sign In",
key: _signinKey,
style: (_isSignIn.value)
? TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold)
: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold),
),
onPressed: () {
_isSignIn.value = true;
},
),
FlatButton(
child: Text(
"Sign Up",
key: _signupKey,
style: (!_isSignIn.value)
? TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold)
: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold),
),
onPressed: () {
_isSignIn.value = false;
},
),
],
),
),
],
);
},
),
),
);
}
}
答案 0 :(得分:-2)
您可以使用类似这样的内容:
child: Column(
children: [
TwinRow(
twins: [
Text('short'),
Text('looooooooooooooooooooong'),
],
),
TwinRow(
twins: [
Text('a bit longer'),
Text('even looooooooooooooooooooonger'),
],
tabColor: Colors.red,
tabThickness: 1.5,
),
TwinRow(
twins: [
Text('Sign In'),
Text('Sign Up'),
],
onPressed: (i) => print('pressed $i'),
tabColor: Colors.orange,
tabThickness: 4.5,
),
],
),
主要思想是使用由CustomPainter
驱动的AnimationController
:
class TwinRow extends StatefulWidget {
final List<Widget> twins;
final Function(int) onPressed;
final Color tabColor;
final double tabThickness;
const TwinRow({
Key key,
@required this.twins,
this.onPressed,
this.tabColor,
this.tabThickness = 3.0,
}) : assert(twins.length == 2), super(key: key);
@override
_TwinRowState createState() => _TwinRowState();
}
class _TwinRowState extends State<TwinRow> with SingleTickerProviderStateMixin {
GlobalKey _rowKey = GlobalKey();
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: kTabScrollDuration,
vsync: this,
);
}
@override
Widget build(BuildContext context) {
var tabColor = widget.tabColor ?? Theme.of(context).indicatorColor;
return CustomPaint(
painter: _TabPainter(_controller, _rowKey, tabColor, widget.tabThickness),
child: Padding(
padding: EdgeInsets.only(bottom: widget.tabThickness),
child: Row(
key: _rowKey,
children: [
FlatButton(onPressed: () => _handlePress(0.0, 0), child: widget.twins[0]),
FlatButton(onPressed: () => _handlePress(1.0, 1), child: widget.twins[1]),
],
),
),
);
}
_handlePress(double target, int index) {
_controller.animateTo(target, curve: Curves.ease);
widget.onPressed?.call(index);
}
}
class _TabPainter extends CustomPainter {
final Animation<double> animation;
final GlobalKey rowKey;
final Color tabColor;
final double tabThickness;
_TabPainter(this.animation, this.rowKey, this.tabColor, this.tabThickness) : super(repaint: animation);
@override
void paint(Canvas canvas, Size size) {
var rects = (rowKey.currentContext.findRenderObject() as RenderFlex)
.getChildrenAsList()
.map((e) => (e.parentData as BoxParentData).offset & e.size);
// print(rects);
var rect = Rect.lerp(rects.elementAt(0), rects.elementAt(1), animation.value);
canvas.drawRect(rect.bottomLeft & Size(rect.width, tabThickness), Paint()..color = tabColor);
/// elevated version with some shadow:
// var path = Path()..addRect(rect.bottomLeft & Size(rect.width, tabThickness));
// canvas.drawShadow(path, Colors.black, 2, true);
// canvas.drawPath(path, Paint()..color = tabColor);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}