Pressing an Individual Node
Opened this issue · 1 comments
Is there a way to change the colour of a node when it's pressed? I can't seem to see any onTap or onPress methods for a specific node.
I was able to add GestureDetector
by essentially recreating a custom TimelineTileBuilder
. I was specifically using the TimelineTileBuilder.connected()
factory so I based it off of that.
In order to add the GestureDetector
you need access to the individual TimelineTile
, which is only created in a private constructor.
This is a brute force method but it may be helpful in a hack-y way:
import 'package:flutter/material.dart';
import 'package:timelines/timelines.dart';
/// Custom TimelineTileBuilder created to add GestureDetector to the original TimelineTileBuilder
/// Reuses code from the original with parameters based on TimelineTileBuilder.connected() factory
class CustomTimelineTileBuilder implements TimelineTileBuilder {
CustomTimelineTileBuilder(
{required this.itemCount,
ContentsAlign contentsAlign = ContentsAlign.basic,
ConnectionDirection connectionDirection = ConnectionDirection.after,
NullableIndexedWidgetBuilder? contentsBuilder,
NullableIndexedWidgetBuilder? oppositeContentsBuilder,
NullableIndexedWidgetBuilder? indicatorBuilder,
ConnectedConnectorBuilder? connectorBuilder,
WidgetBuilder? firstConnectorBuilder,
WidgetBuilder? lastConnectorBuilder,
double? itemExtent,
IndexedValueBuilder<double>? itemExtentBuilder,
IndexedValueBuilder<double>? nodePositionBuilder,
IndexedValueBuilder<double>? indicatorPositionBuilder,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
Function? onTap,
Function? onLongPress}) {
assert(
itemExtent == null || itemExtentBuilder == null,
'Cannot provide both a itemExtent and a itemExtentBuilder.',
);
final startConnectorBuilder = _createConnectedStartConnectorBuilder(
connectionDirection: connectionDirection,
firstConnectorBuilder: firstConnectorBuilder,
connectorBuilder: connectorBuilder,
);
final endConnectorBuilder = _createConnectedEndConnectorBuilder(
connectionDirection: connectionDirection,
lastConnectorBuilder: lastConnectorBuilder,
connectorBuilder: connectorBuilder,
itemCount: itemCount,
);
final effectiveContentsBuilder = _createAlignedContentsBuilder(
align: contentsAlign,
contentsBuilder: contentsBuilder,
oppositeContentsBuilder: oppositeContentsBuilder,
);
final effectiveOppositeContentsBuilder = _createAlignedContentsBuilder(
align: contentsAlign,
contentsBuilder: oppositeContentsBuilder,
oppositeContentsBuilder: contentsBuilder,
);
_builder = (context, index) {
final tile = GestureDetector(
onTap: () => onTap?.call(index),
onLongPress: () => onLongPress?.call(index),
child: TimelineTile(
mainAxisExtent: itemExtent ?? itemExtentBuilder?.call(context, index),
node: TimelineNode(
indicator: indicatorBuilder?.call(context, index) ?? Indicator.transparent(),
startConnector: startConnectorBuilder?.call(context, index),
endConnector: endConnectorBuilder?.call(context, index),
position: nodePositionBuilder?.call(context, index),
indicatorPosition: indicatorPositionBuilder?.call(context, index),
),
contents: effectiveContentsBuilder(context, index),
oppositeContents: effectiveOppositeContentsBuilder(context, index),
),
);
return tile;
};
}
late IndexedWidgetBuilder _builder;
@override
int itemCount;
@override
Widget build(BuildContext context, int index) {
return _builder(context, index);
}
static NullableIndexedWidgetBuilder _createConnectedStartConnectorBuilder({
ConnectionDirection? connectionDirection,
WidgetBuilder? firstConnectorBuilder,
ConnectedConnectorBuilder? connectorBuilder,
}) =>
(context, index) {
if (index == 0) {
if (firstConnectorBuilder != null) {
return firstConnectorBuilder.call(context);
} else {
return null;
}
}
if (connectionDirection == ConnectionDirection.before) {
return connectorBuilder?.call(context, index, ConnectorType.start);
} else {
return connectorBuilder?.call(context, index - 1, ConnectorType.start);
}
};
static NullableIndexedWidgetBuilder _createConnectedEndConnectorBuilder({
ConnectionDirection? connectionDirection,
WidgetBuilder? lastConnectorBuilder,
ConnectedConnectorBuilder? connectorBuilder,
required int itemCount,
}) =>
(context, index) {
if (index == itemCount - 1) {
if (lastConnectorBuilder != null) {
return lastConnectorBuilder.call(context);
} else {
return null;
}
}
if (connectionDirection == ConnectionDirection.before) {
return connectorBuilder?.call(context, index + 1, ConnectorType.end);
} else {
return connectorBuilder?.call(context, index, ConnectorType.end);
}
};
static NullableIndexedWidgetBuilder _createAlignedContentsBuilder({
required ContentsAlign align,
NullableIndexedWidgetBuilder? contentsBuilder,
NullableIndexedWidgetBuilder? oppositeContentsBuilder,
}) {
return (context, index) {
switch (align) {
case ContentsAlign.alternating:
if (index.isOdd) {
return oppositeContentsBuilder?.call(context, index);
}
return contentsBuilder?.call(context, index);
case ContentsAlign.reverse:
return oppositeContentsBuilder?.call(context, index);
case ContentsAlign.basic:
default:
return contentsBuilder?.call(context, index);
}
};
}
}
With this you'd get a callback to the index that was tapped or long pressed and use that information to change the theme details via changes in view state.
Please let me know if there's a better way to do this. Still getting used to Function being a first-class datatype.