【问题标题】:How to Dynamically Size a CustomPainter如何动态调整 CustomPainter 的大小
【发布时间】:2019-09-18 09:19:16
【问题描述】:

我需要使用自定义绘制器在 ListTile 中渲染我的自定义对象,以便绘制一些自定义文本。

ListTile(
  title: CustomPaint(
    painter: RowPainter.name(
      _titleFontSelected,
      _titleFont,
      text,
      index,
      MediaQuery.of(context),
      currentRow,
    ),
  ),
);

在我的RowPainter 中,我使用选定的字体绘制文本。 当行太大时,它会自动换行并在给定的绘制大小之外绘制。

void paint(Canvas canvas, Size size)

我喜欢这种行为,但是如何调整我的绘画区域的高度?因为这是一个问题,因为它与下一个 List 行重叠。 我知道CustomPaint 有一个属性Size 可设置,但我只知道使用TextPainter getBoxesForSelection 绘制函数内部的文本尺寸,但为时已晚。

如果文本换行,我如何动态“调整”我的行画家高度?

【问题讨论】:

    标签: flutter dart flutter-layout


    【解决方案1】:

    TL;DR

    您无法动态调整自定义画家的大小,但是,您的问题可以使用CustomPaint 来解决。
    我将首先详细说明动态大小,然后解释如何使用恒定大小来解决这个问题。

    动态大小

    这实际上是CustomPaint 有其限制的地方,因为它没有提供一种方法让您根据内容调整画家的大小。

    执行此操作的正确方法是实现您自己的 RenderBox 并覆盖 performLayout 以根据内容调整渲染对象的大小。
    RenderBox documentation 对此进行了非常详细的介绍,但是,您可能仍然会发现很难进入它,因为它与构建小部件完全不同。

    固定大小

    以上所有不应该在您的情况下是必需的,因为您没有用于自定义绘画的 child
    您可以简单地将size parameter 提供给您的CustomPaint 并在父窗口小部件中计算所需的高度。

    您可以使用LayoutBuilder 来获取可用宽度:

    LayoutBuilder(
      builder: (context, constraints) {
        final maxWidth = constraints.maxWidth;
        ...
      }
    )
    

    现在,您甚至可以在输入自定义绘画之前简单地使用 TextPainter 检索所需的大小:

    builder: (context, constraints) {
      ...
      final textPainter = TextPainter(
        text: TextSpan(
          text: 'Your text',
          style: yourTextStyle,
        ),
        textDirection: TextDirection.ltr,
      );
      textPainter.layout(maxWidth: maxWidth); // This will make the size available.
    
      return CustomPaint(
        size: textPainter.size,
        ...
      );
    }
    

    现在,您甚至可以将 textPainter 直接传递给您的自定义画家,而不是传递样式参数。


    您的逻辑可能有点复杂,但是,关键是您可以在创建 CustomPaint 之前计算大小,这允许您设置大小。
    如果您需要更复杂的东西,您可能需要实现自己的RenderBox

    【讨论】:

      【解决方案2】:

      我还没有测试过,但这可能有效:

      首先,您将CustomPaint 包装到一个有状态的小部件(例如DynamicCustomPaint)中,以动态操作您的小部件。

      你给你的CustomPainter一个函数onResize,当你知道它时,它会给你画布的新大小。

      一旦知道 Canvas 的确切尺寸,就可以调用此函数。例如,通过使用this technique,您无需绘制文本即可知道它的大小。

      onResize 函数将被调用时,您将获得画布的新尺寸并在DynamicCustomPaint 状态下调用setState

      这可能看起来像这样:

      class DynamicCustomPaint extends StatefulWidget {
        @override
        _DynamicCustomPaintState createState() => _DynamicCustomPaintState();
      }
      
      class _DynamicCustomPaintState extends State<DynamicCustomPaint> {
        Size canvasSize;
      
        @override
        Widget build(BuildContext context) {
          // Set inital size, maybe move this to initState function
          if (canvasSize == null) {
            // Decide what makes sense in your use-case as inital size
            canvasSize = MediaQuery.of(context).size;
          }
          return CustomPaint(
            size: canvasSize,
            painter: RowPainter.name(_titleFontSelected, _titleFont, text, index, currentRow, onResize: (size) {
              setState(() {
                canvasSize = size;
              });
            }),
          );
        }
      }
      
      typedef OnResize = void Function(Size size);
      
      class RowPainter extends CustomPainter {
        RowPainter.name(
          this._titleFontSelected,
          this._titleFont,
          this.text,
          this.index,
          this.currentRow,
          { this.onResize },
        );
      
        final FontStyle _titleFontSelected;
        final FontStyle _titleFont;
        final String text;
        final int index;
        final int currentRow;
        final OnResize onResize;
      
        @override
        void paint(Canvas canvas, Size size) {
          // TODO: implement paint
      
          // call onResize somewhere in here
          // onResize(newSize);
        }
      
        @override
        bool shouldRepaint(CustomPainter oldDelegate) => false;
      }
      

      【讨论】:

      • 我认为在绘画中调整大小可能不是一个好主意,因为这会产生不必要的副作用。我试图在我的回答中详细说明(:
      【解决方案3】:

      请改用SingleChildRenderObjectWidgetRenderBox。具有动态调整大小的完整简单示例。

      DartPad

      import 'dart:async';
      import 'package:flutter/material.dart';
      
      void main() {
        runApp(MaterialApp(
          home: Scaffold(
            body: Center(
              child: Column(
                children: [
                  SizedBox(height: 100,),
                  Text('I am above'),
                  MyWidget(),
                  Text('I am below')
                ],
              ),
            ),
          ),
        ));
      }
      
      class MyWidget extends SingleChildRenderObjectWidget {
        @override
        MyRenderBox createRenderObject(BuildContext context) {
          return MyRenderBox();
        }
      }
      
      class MyRenderBox extends RenderBox {
        double myHeight = 200;
      
        @override
        void paint(PaintingContext context, Offset offset) {
          Paint paint = Paint()
            ..color = Colors.black..style = PaintingStyle.fill;
          context.canvas.drawRect(
              Rect.fromLTRB(offset.dx, offset.dy,
                offset.dx + size.width, offset.dy + size.height,), paint);
        }
      
        @override
        void performLayout() {
          size = Size(
            constraints.constrainWidth(200),
            constraints.constrainHeight(myHeight),
          );
        }
      
        // Timer just an example to show dynamic behavior
        MyRenderBox(){
          Timer.periodic(Duration(seconds: 2), handleTimeout);
        }
        void handleTimeout(timer) {
          myHeight += 40;
          markNeedsLayoutForSizedByParentChange();
          layout(constraints);
        }
      }
      
      

      CustomPainter 只会根据其子项的大小或传递给构造函数的初始值调整大小。 Documentation:

      自定义画家通常会根据自己的孩子调整自己的大小。如果他们没有孩子,他们会尝试调整自己的大小,默认为 Size.zero。 size 不能为空。

      RenderBox 的基础知识

      【讨论】:

        猜你喜欢
        • 2021-12-26
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 1970-01-01
        • 1970-01-01
        • 2014-03-04
        • 1970-01-01
        相关资源
        最近更新 更多