I'm building a chat app where audio messages can accompany the text. The widget that displays this is a very standard looking chat bubble (think iMessage) with one twist - the bubble becomes a progress bar when the audio is playing. The progress bar is essentially just a gradual change in the background color. You can see the layout in this dartpad (hit run). A screenshot of the mock is here:
Because the chat bubble is rounded, I need the progress bar to round only at the end. In other words, it should always be rounded on the left as it is flush with the left side of the message but it should be flat on the right until it hits the end of the message. I think ClipRRect
is the right widget to do this.
However, because the message bubble width is variable, I use a FractionallySizedBox
as the progress bar. 1/10th the way through the audio, it should be 1/10th the width of the chat bubble. Then I use Positioned.fill
to wrap the progress bar so that the fraction is taken out of the full message bubble width. ClipRRect
does not seem to play nicely wrapping Positioned.fill
I believe it expects the child to have a set size:
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 to the version with the not-working ClipRRect
. Is there some other widget I can wrap Position.fill
in to make it clippable? Or maybe a better way to lay this out?
Please try the following code.
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)));
}
}
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments