Guía de implementación de RenderObject personalizado en Flutter para efectos de UI avanzados

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.

Etiquetas: Flutter RenderObject Flutter-layout custom-rendering sticky-header

Publicado el 7-14 02:36