Skip to content

Instantly share code, notes, and snippets.

@JonasWanke
Created May 14, 2020 12:15
Show Gist options
  • Save JonasWanke/36aa21d4747d8084f46a030d0d81508d to your computer and use it in GitHub Desktop.
Save JonasWanke/36aa21d4747d8084f46a030d0d81508d to your computer and use it in GitHub Desktop.
Event content layout for timetable
import 'dart:ui';
import 'package:black_hole_flutter/black_hole_flutter.dart';
import 'package:dartx/dartx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class EventContent extends MultiChildRenderObjectWidget {
EventContent({
@required Widget title,
Widget subtitle,
Widget footer,
}) : assert(title != null),
super(
children: [
_EventContentParentDataWidget(
position: _Position.title,
child: title,
),
if (subtitle != null)
_EventContentParentDataWidget(
position: _Position.subtitle,
child: subtitle,
),
if (footer != null)
_EventContentParentDataWidget(
position: _Position.footer,
child: footer,
),
],
);
@override
RenderObject createRenderObject(BuildContext context) {
return _EventContentLayout();
}
}
class _EventContentParentData extends ContainerBoxParentData<RenderBox> {
_Position position;
}
enum _Position { title, subtitle, footer }
class _EventContentParentDataWidget
extends ParentDataWidget<_EventContentParentData> {
const _EventContentParentDataWidget({
@required this.position,
@required Widget child,
}) : assert(position != null),
super(child: child);
final _Position position;
@override
Type get debugTypicalAncestorWidgetClass => _EventContentLayout;
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is _EventContentParentData);
final _EventContentParentData parentData = renderObject.parentData;
if (parentData.position == position) {
return;
}
parentData.position = position;
final targetParent = renderObject.parent;
if (targetParent is RenderObject) {
targetParent.markNeedsLayout();
}
}
}
class _EventContentLayout extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, _EventContentParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, _EventContentParentData> {
_EventContentLayout() : super();
@override
void setupParentData(RenderObject child) {
if (child.parentData is! _EventContentParentData) {
child.parentData = _EventContentParentData();
}
}
@override
double computeMinIntrinsicWidth(double height) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0;
}
@override
double computeMaxIntrinsicWidth(double height) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0;
}
@override
double computeMinIntrinsicHeight(double width) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0;
}
@override
double computeMaxIntrinsicHeight(double width) {
assert(_debugThrowIfNotCheckingIntrinsics());
return 0;
}
bool _debugThrowIfNotCheckingIntrinsics() {
assert(() {
if (!RenderObject.debugCheckingIntrinsics) {
throw Exception("_EventContentLayout doesn't have an intrinsic size.");
}
return true;
}());
return true;
}
@override
bool get alwaysNeedsCompositing => true;
@override
bool get sizedByParent => true;
RenderBox _childForPosition(_Position position) =>
children.firstOrNullWhere((c) => c.data.position == position);
bool _hasOverflow = false;
@override
void performLayout() {
final title = _childForPosition(_Position.title);
final subtitle = _childForPosition(_Position.subtitle);
final footer = _childForPosition(_Position.footer);
var remainingHeight = size.height;
title.layout(
constraints.copyWith(minHeight: 0),
parentUsesSize: true,
);
title.data.offset = Offset.zero;
remainingHeight -= title.size.height;
if (footer != null) {
footer.layout(
constraints.copyWith(minHeight: 0),
parentUsesSize: true,
);
final titleHeight = title.size.height;
final footerHeight = footer.size.height;
final contentHeight = titleHeight + footerHeight;
if (contentHeight > size.height) {
// If we can't fit title & footer above one another, animate them to be
// next to each other.
// 0: collapsed to single line
// 1: stacked
double t = (size.height - titleHeight) / (contentHeight - titleHeight);
// Title's width percentage when collapsed to a single line.
const alpha = 2 / 3;
final titleWidth = lerpDouble(size.width * alpha, size.width, t);
title.layout(
constraints.copyWith(
minHeight: 0,
minWidth: titleWidth,
maxWidth: titleWidth,
),
parentUsesSize: true,
);
final footerWidth = lerpDouble(size.width * (1 - alpha), size.width, t);
footer.layout(
constraints.copyWith(
minHeight: 0,
minWidth: footerWidth,
maxWidth: footerWidth,
),
parentUsesSize: true,
);
footer.data.offset = Offset(
size.width - footerWidth,
size.height - footerHeight,
);
} else {
footer.data.offset = Offset(0, size.height - footerHeight);
}
remainingHeight -= footerHeight;
}
if (subtitle != null) {
subtitle.layout(
constraints.copyWith(
minHeight: 0,
maxHeight: remainingHeight.coerceAtLeast(0),
),
parentUsesSize: true,
);
subtitle.data.offset = Offset(0, title.size.height);
}
final contentHeight = title.size.height +
(subtitle?.size?.height ?? 0) +
(footer?.size?.height ?? 0);
_hasOverflow = contentHeight > size.height;
}
@override
bool hitTestChildren(BoxHitTestResult result, {Offset position}) {
return defaultHitTestChildren(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
if (!_hasOverflow) {
defaultPaint(context, offset);
return;
}
if (size.width <= 0) {
return;
}
context.pushClipRect(
needsCompositing, offset, Offset.zero & size, defaultPaint);
}
}
extension _ParentData on RenderBox {
_EventContentParentData get data => parentData as _EventContentParentData;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment