Responsabilidades fundamentales de un RenderObject
- Layout: Determina las dimensiones del nodo y de sus descendientes, estableciendo coordenadas relativas dentro del árbol.
- Pintura: Ejecuta las instrucciones gráficas necesarias para proyectar el contenido visual en el lienzo.
- Detección de toques: Verifica si un punto táctil cae dentro de los límites del objeto y propaga el evento.
Anatomía de un RenderObject personalizado: StickyHeaderRender
Para ilustrar los conceptos, analizaremos StickyHeaderRender, una implementación que produce un encabezado con comportamiento adhesivo durante el desplazamiento. La clase hereda de RenderBox e incorpora mixins que facilitan la gestión de múltiples hijos.
class StickyHeaderRender extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, StickySlotData>,
RenderBoxContainerDefaultsMixin<RenderBox, StickySlotData> {
StickyHeaderRender({required this.scrollableState});
ScrollableState scrollableState;
@override
void performLayout() {
final headerNode = lastChild!;
final bodyNode = firstChild!;
final flexibleConstraints = constraints.loosen();
bodyNode.layout(flexibleConstraints, parentUsesSize: true);
headerNode.layout(flexibleConstraints, parentUsesSize: true);
final bodyExtent = bodyNode.size.height;
final headerExtent = headerNode.size.height;
final totalWidth = bodyNode.size.width;
size = Size(totalWidth, bodyExtent + headerExtent);
StickySlotData.setOffset(bodyNode, Offset(0, headerExtent));
final maxScrollDistance = computeAvailableScrollDistance();
final clampedHeaderShift = math.min(-maxScrollDistance, stickyOffset);
StickySlotData.setOffset(headerNode, Offset(0, math.max(0, clampedHeaderShift)));
}
}
Envoltorio Widget para el RenderObject
Para utilizar el RenderObject desde el árbol de widgets, se implementa un MultiChildRenderObjectWidget que actúa como puente entre la declaración declarativa y la capa de renderizado:
class StickyHeaderContainer extends MultiChildRenderObjectWidget {
StickyHeaderContainer({
super.key,
required Widget stickyHeader,
required Widget stickyBody,
}) : super(children: [stickyBody, stickyHeader]);
@override
StickyHeaderRender createRenderObject(BuildContext ctx) {
return StickyHeaderRender(
scrollableState: Scrollable.of(ctx)!,
);
}
@override
void updateRenderObject(BuildContext ctx, StickyHeaderRender renderObj) {
renderObj.scrollableState = Scrollable.of(ctx)!;
renderObj.markNeedsLayout();
}
}
Ejemplo de integración
StickyHeaderContainer(
stickyHeader: ColoredBox(
color: Colors.indigo,
child: SizedBox(
height: 48,
child: Center(child: Text('Encabezado persistente')),
),
),
stickyBody: ListView.builder(
itemCount: 60,
itemBuilder: (context, idx) {
return ListTile(
title: Text('Elemento número $idx'),
);
},
),
)
Casos de uso recomendados
La creación de RenderObject personalizados se justifica cuando:
- Se requieren algoritmos de layout específicos no cubiertos por los widgets del framework.
- El componente necesita optimización de rendimiento para listas grandes o animaciones exigentes.
- Se implementan interacciones táctiles no convencionales que demandan control fino del hit-testing.
Otras implementaciones de interés en el reposorio incluyen CustomSliver para slivers personalizados, LinkSliverHeader para encabezados sincronizados entre listas, y CloudWidget para composiciones animadas.