如何在flutter中裁剪可变大小的小部件?

问题描述 投票:0回答:1

我正在构建一个聊天应用程序,音频消息可以与文本一起显示。显示此内容的小部件是一个非常标准的聊天气泡(认为是iMessage),带有一个转弯-播放音频时,气泡变为进度栏。进度条本质上只是背景颜色的逐渐变化。您可以在此dartpad(命中运行)中查看布局。模拟屏幕截图在这里:

enter image description here

由于聊天气泡是四舍五入的,因此我需要进度条仅在末尾四舍五入。换句话说,它应始终在左侧舍入,因为它与消息的左侧齐平,但应在右侧平坦,直到到达消息的末尾。我认为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以使其可裁剪吗?还是一种更好的解决方案?

flutter flutter-layout
1个回答
0
投票

请尝试以下代码。

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)));
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.