MST

星途 面试题库

面试题:Flutter自定义布局中BoxConstraints的深度应用

在Flutter中实现一个自定义布局Widget,要求该布局能够根据子Widget的数量以及BoxConstraints动态地调整子Widget的排列方式和尺寸。例如,当BoxConstraints的宽度足够时,子Widget水平排列;当宽度不足时,切换为垂直排列。请详细说明实现思路,并给出完整的代码实现。
24.0万 热度难度
前端开发Flutter

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 创建一个继承自 MultiChildRenderObjectWidget 的自定义布局类,因为我们需要管理多个子Widget。
  2. createRenderObject 方法中返回一个继承自 RenderBox 的自定义渲染对象,用于实际的布局逻辑。
  3. 在渲染对象的 performLayout 方法中,根据 BoxConstraints 的宽度和子Widget的数量来决定是水平排列还是垂直排列。
  4. 对于水平排列,计算每个子Widget的宽度为 constraints.maxWidth / 子Widget数量,高度为 constraints.maxHeight
  5. 对于垂直排列,计算每个子Widget的高度为 constraints.maxHeight / 子Widget数量,宽度为 constraints.maxWidth

代码实现

import 'package:flutter/material.dart';

class DynamicLayout extends MultiChildRenderObjectWidget {
  const DynamicLayout({Key? key, required List<Widget> children})
      : super(key: key, children: children);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _DynamicLayoutRenderObject();
  }
}

class _DynamicLayoutRenderObject extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
  @override
  void performLayout() {
    final constraints = this.constraints;
    final childCount = this.childCount;
    if (childCount == 0) {
      size = constraints.smallest;
      return;
    }

    if (constraints.maxWidth >= childCount * constraints.maxHeight) {
      // 水平排列
      double childWidth = constraints.maxWidth / childCount;
      double childHeight = constraints.maxHeight;
      forEachChild((child, index) {
        if (child != null) {
          child.layout(BoxConstraints.tightFor(width: childWidth, height: childHeight),
              parentUsesSize: true);
          final childParentData = child.parentData! as MultiChildLayoutParentData;
          childParentData.offset = Offset(index * childWidth, 0);
        }
      });
      size = constraints.biggest;
    } else {
      // 垂直排列
      double childWidth = constraints.maxWidth;
      double childHeight = constraints.maxHeight / childCount;
      forEachChild((child, index) {
        if (child != null) {
          child.layout(BoxConstraints.tightFor(width: childWidth, height: childHeight),
              parentUsesSize: true);
          final childParentData = child.parentData! as MultiChildLayoutParentData;
          childParentData.offset = Offset(0, index * childHeight);
        }
      });
      size = constraints.biggest;
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
  }
}

使用示例

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Dynamic Layout Example')),
        body: DynamicLayout(
          children: const [
            Container(color: Colors.red),
            Container(color: Colors.green),
            Container(color: Colors.blue),
          ],
        ),
      ),
    );
  }
}