Designing a New Rendering Architecture for Flutter Dynamic Templates
We redesigned Flutter’s dynamic‑template rendering by replacing the widget‑centric approach with a three‑layer architecture inspired by Android’s MeasureSpec model, implementing DXRenderBox to handle sizedByParent, performResize, and performLayout, solving two‑measure layout issues and achieving faster, more consistent rendering across platforms.
Our team explored using the DinamicX DSL to render Flutter UI dynamically. After solving performance problems, we faced a new challenge: how to improve rendering consistency between Flutter and native platforms without sacrificing speed.
Idea
The initial widget‑centric architecture converted DSL nodes to Widgets. As template complexity grew, several Bad Cases appeared because:
Stack was used to emulate FrameLayout and Column/Row to emulate LinearLayout; although they look similar, their internal implementations differ greatly.
Custom Widgets only partially aligned with the DSL layout philosophy, leaving many edge cases unresolved.
To solve these issues we redesigned the rendering architecture based on Android’s MeasureSpec model.
New Rendering Architecture
Decoration layer – supports background, border, corner radius, touch events, etc.
Render layer – expresses the layout rules and size of each DSL node.
Content layer – displays actual content; for layout widgets it is the children, for leaf widgets it uses RenderParagraph , RenderImage , etc.
The core class is DXRenderBox , a subclass of RenderBox . It implements three essential methods:
sizedByParent – determines whether the size is dictated solely by the parent.
performResize – calculates size when sizedByParent is true.
performLayout – lays out children and computes the final size when sizedByParent is false.
Key Code Snippets
abstract class RenderObject {
Constraints get constraints => _constraints;
Constraints _constraints;
bool get sizedByParent => false;
void layout(Constraints constraints, {bool parentUsesSize = false}) {
// calculate relayoutBoundary
// ...
if (sizedByParent) {
performResize();
} else {
performLayout();
}
// ...
}
}In DXRenderBox we override sizedByParent to reflect the DSL’s MeasureSpec mode:
bool get sizedByParent =>
nodeData.widthMeasureMode == DXMeasureMode.DX_EXACTLY &&
nodeData.heightMeasureMode == DXMeasureMode.DX_EXACTLY;The performResize implementation extracts explicit width/height or falls back to the parent constraints:
void performResize() {
double width = nodeData.width ?? constraints.maxWidth;
double height = nodeData.height ?? constraints.maxHeight;
size = constraints.constrain(Size(width, height));
}For non‑layout widgets we use DXSingleChildLayoutRender :
void performLayout() {
BoxConstraints childBoxConstraints = computeChildBoxConstraints();
if (sizedByParent) {
child.layout(childBoxConstraints);
} else {
child.layout(childBoxConstraints, parentUsesSize: true);
size = defaultComputeSize(child.size);
}
}Layout widgets such as DXFrameLayoutRender first layout all children, compute the maximum width/height, then determine their own size and finally calculate each child’s offset based on gravity‑to‑alignment conversion.
One tricky scenario is the “two‑measure” problem when a parent’s width is match_content and its children are match_parent . Android measures children twice; Flutter’s default layout order cannot satisfy this. We solve it by leveraging intrinsic methods ( getMinIntrinsicWidth , getMaxIntrinsicWidth , getMinIntrinsicHeight , getMaxIntrinsicHeight ) to obtain children’s preferred dimensions before the parent size is fixed.
@override
double getMaxIntrinsicWidth(double height) {
if (nodeData.width != null) return nodeData.width;
if (child != null) return child.getMaxIntrinsicWidth(height);
return 0.0;
}Using these intrinsic queries we can first determine the maximum width required by all children, set the parent’s width accordingly, and then perform a second layout pass with the exact width, achieving the same effect as Android’s two‑measure flow.
Performance tests show that the new architecture reduces rendering time and improves FPS for dynamic templates, while also eliminating the previously observed Bad Cases.
Future Outlook
After the architecture upgrade we have fully resolved the earlier Bad Cases and further boosted rendering performance, making Flutter dynamic template rendering practical. We will continue to refine the solution and expand its capabilities to empower business scenarios.
Xianyu Technology
Official account of the Xianyu technology team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.