我正在构建一个聊天应用程序,音频消息可以与文本一起显示。显示此内容的小部件是一个非常标准的聊天气泡(认为是iMessage),带有一个转弯-播放音频时,气泡变为进度栏。进度条本质上只是背景颜色的逐渐变化。您可以在此dartpad(命中运行)中查看布局。模拟屏幕截图在这里:
由于聊天气泡是四舍五入的,因此我需要进度条仅在末尾四舍五入。换句话说,它应始终在左侧舍入,因为它与消息的左侧齐平,但应在右侧平坦,直到到达消息的末尾。我认为ClipRRect
是执行此操作的正确工具。
但是,由于消息气泡宽度是可变的,因此我使用FractionallySizedBox
作为进度条。音频播放方式的1/10,应为聊天气泡宽度的1/10。然后,我使用Positioned.fill
包裹进度条,以便从整个消息气泡宽度中取出分数。 ClipRRect
似乎不能很好地包装Positioned.fill
,我相信它希望孩子的尺码固定不变:
RenderBox was not laid out: RenderClipRRect#3b019 relayoutBoundary=up13
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1694 pos 12: 'hasSize'
[This is a dartpad改为无效的ClipRRect
版本。我还可以包装Position.fill
以使其可裁剪吗?还是一种更好的解决方案?
请尝试以下代码。
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ChatAudioTextBubble("Test message"),
),
),
);
}
}
class ChatAudioTextBubble extends StatefulWidget {
final String text;
ChatAudioTextBubble(this.text);
@override
_ChatAudioTextBubbleState createState() => _ChatAudioTextBubbleState();
}
class _ChatAudioTextBubbleState extends State<ChatAudioTextBubble>
with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
_controller = AnimationController(
lowerBound: 0.0,
upperBound: 1.0,
value: 0.0,
duration: const Duration(milliseconds: 1000),
vsync: this,
)..addListener(() {
this.setState(() {});
});
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onTapHandler() {
_controller.reset();
_controller.forward(from: 0.0);
}
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
_onTapHandler();
print(
"Plays the audio and starts the animation which takes _controller.value from 0-1");
},
child: Stack(children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFFE6E6E6)),
)),
Container(
constraints: BoxConstraints(
minWidth: 40,
maxWidth: MediaQuery.of(context).size.width * 0.70),
child: _buildTextChild()),
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10.0),
bottomRight: Radius.circular(10.0),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
heightFactor: 1,
widthFactor: _controller == null ? 0 : _controller.value,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.blue.withOpacity(0.3),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.zero,
bottomLeft: Radius.circular(10.0),
bottomRight: Radius.zero,
),
),
),
),
),
),
]));
}
Widget _buildTextChild() {
final color = Colors.black;
return Padding(
padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
child: Text(widget.text, style: TextStyle(color: color, fontSize: 18)));
}
}