Created
May 14, 2020 12:15
-
-
Save JonasWanke/36aa21d4747d8084f46a030d0d81508d to your computer and use it in GitHub Desktop.
Event content layout for timetable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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