diff --git a/CHANGELOG.md b/CHANGELOG.md index 32532c7..1e41116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,4 +9,5 @@ Timeline( altOffset: Offset(0, -24), // ... ); -``` \ No newline at end of file +``` +## [0.1.1+12] - supports anchor & offset for timeline & indicator \ No newline at end of file diff --git a/example/lib/screen/plain_timeline_demo.dart b/example/lib/screen/plain_timeline_demo.dart index c6466bb..7396888 100644 --- a/example/lib/screen/plain_timeline_demo.dart +++ b/example/lib/screen/plain_timeline_demo.dart @@ -80,8 +80,10 @@ class _PlainTimelineDemoScreenState extends State { TimelineEventDisplay get plainEventDisplay { return TimelineEventDisplay( + anchor: IndicatorPosition.top, + indicatorOffset: Offset(0, 24), child: TimelineEventCard( - title: Text("just \n\n\n\n now"), + title: Text("multi\nline\ntitle\nawesome!"), content: Text("someone commented on your timeline ${DateTime.now()}"), ), indicator: randomIndicator); @@ -91,11 +93,12 @@ class _PlainTimelineDemoScreenState extends State { Widget _buildTimeline() { return TimelineTheme( - data: TimelineThemeData(lineColor: Colors.blueAccent, itemGap: 180), + data: TimelineThemeData( + lineColor: Colors.blueAccent, itemGap: 100, lineGap: 0), child: Timeline( - indicatorPosition: IndicatorPosition.top, - altOffset: Offset(0, -24), + anchor: IndicatorPosition.center, indicatorSize: 56, + altOffset: Offset(10, 40), events: events, )); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 10cb38b..942f092 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -73,7 +73,7 @@ packages: path: ".." relative: true source: path - version: "0.0.4+6" + version: "0.0.4+11" matcher: dependency: transitive description: diff --git a/lib/event_item.dart b/lib/event_item.dart index 2a3f2fb..d74c181 100644 --- a/lib/event_item.dart +++ b/lib/event_item.dart @@ -8,7 +8,8 @@ class TimelineEventDisplay { this.indicator, this.indicatorSize, this.forceLineDrawing = false, - this.indicatorPosition, + this.anchor, + this.indicatorOffset = const Offset(0, 0), }); final Widget child; @@ -20,8 +21,9 @@ class TimelineEventDisplay { /// enables indicator line drawing even no indicator is passed. final bool forceLineDrawing; - /// [indicatorPosition] overrides the default IndicatorPosition - final IndicatorPosition indicatorPosition; + /// [anchor] overrides the default IndicatorPosition + final IndicatorPosition anchor; + final Offset indicatorOffset; bool get hasIndicator { return indicator != null; diff --git a/lib/indicator_position.dart b/lib/indicator_position.dart index 5845b28..0e1d8ec 100644 --- a/lib/indicator_position.dart +++ b/lib/indicator_position.dart @@ -1,5 +1,20 @@ -enum IndicatorPosition{ - top, - center, - bottom -} \ No newline at end of file +import 'package:flutter/widgets.dart'; + +enum IndicatorPosition { top, center, bottom } + +extension Mapper on IndicatorPosition { + Alignment get asAlignment { + switch (this) { + case IndicatorPosition.top: + return Alignment.topCenter; + break; + case IndicatorPosition.center: + return Alignment.center; + break; + case IndicatorPosition.bottom: + return Alignment.bottomCenter; + break; + } + return Alignment.center; + } +} diff --git a/lib/timeline.dart b/lib/timeline.dart index fa562e4..b6ab5b7 100644 --- a/lib/timeline.dart +++ b/lib/timeline.dart @@ -20,7 +20,7 @@ class Timeline extends StatelessWidget { // item gap will be ignored when custom separatorBuilder is provided this.separatorBuilder, this.altOffset = const Offset(0, 0), - this.indicatorPosition = IndicatorPosition.center}) + this.anchor = IndicatorPosition.center}) : itemCount = events.length; final Offset altOffset; @@ -35,8 +35,8 @@ class Timeline extends StatelessWidget { final bool primary; final bool reverse; - /// [indicatorPosition] describes where the indicator drawing should start. use it with alt offset - final IndicatorPosition indicatorPosition; + /// [anchor] describes where the indicator drawing should start. use it with alt offset + final IndicatorPosition anchor; final IndexedWidgetBuilder separatorBuilder; @@ -98,11 +98,13 @@ class Timeline extends StatelessWidget { return event.hasIndicator; } - Widget buildWrappedIndicator(Widget child, {double width, double height}) { + Widget buildWrappedIndicator(Widget child, + {double width, double height, Offset indicatorOffset}) { + final offset = altOffset + indicatorOffset; return Container( width: width, height: height, - transform: Matrix4.translationValues(altOffset.dx, altOffset.dy, 0.0), + transform: Matrix4.translationValues(offset.dx, offset.dy, 0.0), child: child, ); } @@ -114,11 +116,11 @@ class Timeline extends StatelessWidget { bool nextHasIndicator, TimelineEventDisplay event, TimelineThemeData theme}) { - var overrideIndicatorSize = + final overrideIndicatorSize = event.indicatorSize != null ? event.indicatorSize : indicatorSize; - var overrideIndicatorPosition = event.indicatorPosition != null - ? event.indicatorPosition - : indicatorPosition; + final overrideIndicatorPosition = + event.anchor != null ? event.anchor : anchor; + final indicatorOffset = event.indicatorOffset; var line = CustomPaint( painter: _LineIndicatorPainter( @@ -137,6 +139,7 @@ class Timeline extends StatelessWidget { prevHasIndicator: prevHasIndicator, nextHasIndicator: nextHasIndicator, indicatorPosition: overrideIndicatorPosition, + indicatorOffset: indicatorOffset, ), child: SizedBox(height: double.infinity, width: indicatorSize), ); @@ -145,9 +148,10 @@ class Timeline extends StatelessWidget { line, Positioned.fill( child: Align( - alignment: Alignment.center, + alignment: overrideIndicatorPosition.asAlignment, child: buildWrappedIndicator( event.indicator, + indicatorOffset: indicatorOffset, width: overrideIndicatorSize, height: overrideIndicatorSize, )), @@ -173,6 +177,7 @@ class _LineIndicatorPainter extends CustomPainter { @required this.nextHasIndicator, @required this.prevHasIndicator, @required this.itemGap, + @required this.indicatorOffset, @required this.indicatorPosition}) : linePaint = Paint() ..color = lineColor @@ -183,6 +188,7 @@ class _LineIndicatorPainter extends CustomPainter { final Offset altOffset; final bool hideDefaultIndicator; final double indicatorSize; + final Offset indicatorOffset; final double maxIndicatorSize; final double lineGap; final StrokeCap strokeCap; @@ -198,39 +204,137 @@ class _LineIndicatorPainter extends CustomPainter { final IndicatorPosition indicatorPosition; double get altX { - return altOffset.dx; + return altOffset.dx + indicatorOffset.dx; } double get altY { - return altOffset.dy; + return altOffset.dy + indicatorOffset.dy; } @override void paint(Canvas canvas, Size size) { - final indicatorRadius = indicatorSize / 2; - final maxIndicatorRadius = maxIndicatorSize / 2; - final indicatorMargin = indicatorRadius + lineGap; - final safeItemGap = (indicatorRadius) + itemGap; - double topStartY = 0.0; + // indicator's radius + final radius = indicatorSize / 2; + final height = size.height; + final halfHeight = height / 2; + final double halfItemGap = itemGap / 2; - // region calculate starting point -/* + // initial start point + // works well + Offset indicatorCenterStartPoint; switch (indicatorPosition) { case IndicatorPosition.top: - topStartY = -size.height / 2; + indicatorCenterStartPoint = size.topCenter(Offset(0, radius)); break; case IndicatorPosition.center: - topStartY = 0; + indicatorCenterStartPoint = size.center(Offset.zero); break; case IndicatorPosition.bottom: - // startY = size.height / 2; + indicatorCenterStartPoint = size.bottomCenter(Offset(0, -radius)); break; - }*/ - // endregion + } + + // alt start point + Offset indicatorCenter = indicatorCenterStartPoint.translate(altX, altY); + + // region upper line + if (!isFirst) { + double additionalGap = 0; + if (!prevHasIndicator) additionalGap = halfItemGap; + final additionalTop = getAdditionalY(height, mode: "upper"); + + // works well + Offset topStart = indicatorCenter.translate( + 0, + // the altY + radius is the default start point. + // adding half item gap is also by default. + // the below two items does not get affected by the indicator position + -(((altY + radius) + halfItemGap) // + + + (additionalGap) + + (additionalTop) // + )); + + // works well + Offset topEnd = indicatorCenter.translate(0, -radius - lineGap); + + // draw upper line + if (!isFirst) canvas.drawLine(topStart, topEnd, linePaint); + } + // endregion upper line + + // endregion downer line + if (!isLast) { + double additionalGap = 0; + if (!nextHasIndicator) additionalGap = halfItemGap; + + final additionalBottom = getAdditionalY(height, mode: "downer"); + + // works well + Offset bottomEnd = indicatorCenter.translate( + 0, + (radius + halfItemGap - altY) + + (additionalGap) // + + + (additionalBottom) // + ); + + // works well + Offset bottomStart = indicatorCenter.translate(0, radius + lineGap); + if (!isLast) canvas.drawLine(bottomStart, bottomEnd, linePaint); + } + // endregion downer line + } + + double getAdditionalY(double height, {@required String mode}) { + double add = 0; + // the additional size should be + if (mode == "upper") { + switch (indicatorPosition) { + case IndicatorPosition.top: + add = 0; + break; + case IndicatorPosition.center: + add = (height - indicatorSize) / 2; + break; + case IndicatorPosition.bottom: + add = height - indicatorSize; + break; + } + return add; + } + + if (mode == "downer") { + switch (indicatorPosition) { + case IndicatorPosition.top: + add = height - indicatorSize; + break; + case IndicatorPosition.center: + add = (height - indicatorSize) / 2; + break; + case IndicatorPosition.bottom: + add = 0; + break; + } + return add; + } + + throw FlutterError("$mode is not a supported mode"); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} + +// painter v1 +/* // region override top, bottom calculator for filling empty space between events double overrideOffsetYForTop = altY; double overrideOffsetYForBottom = altY; + if (!prevHasIndicator) { overrideOffsetYForTop = 0.0; } @@ -239,26 +343,39 @@ class _LineIndicatorPainter extends CustomPainter { } // endregion - final top = size.topLeft(Offset(maxIndicatorRadius + altX, - topStartY - safeItemGap + overrideOffsetYForTop)); - final topOfCenter = size.centerLeft( - Offset(maxIndicatorRadius + altX, -indicatorMargin + altY), - ); + final inboundTop = size.topCenter(Offset.zero); + final outboundTop = inboundTop.translate( + altX, topStartY - safeItemGap + overrideOffsetYForTop); +// final outboundTop = size.topLeft(Offset(maxIndicatorRadius + altX, +// topStartY - safeItemGap + overrideOffsetYForTop)); - final bottom = size.bottomLeft(Offset(maxIndicatorRadius + altX, - 0.0 + safeItemGap + overrideOffsetYForBottom)); - final bottomOfCenter = size.centerLeft( - Offset(maxIndicatorRadius + altX, indicatorMargin + altY), - ); + // region center + // FIXME + final center = size.center(Offset.zero); + final topOfCenter = center.translate(altX, -indicatorMargin + altY); + final bottomOfCenter = center.translate(altX, indicatorMargin + altY); + // endregion + final inboundBottom = size.bottomCenter(Offset.zero); + final outboundBottom = + inboundBottom.translate(altX, safeItemGap + overrideOffsetYForBottom); + + // region calculate starting point +/* + switch (indicatorPosition) { + case IndicatorPosition.top: + topStartY = -size.height / 2; + break; + case IndicatorPosition.center: + topStartY = 0; + break; + case IndicatorPosition.bottom: + // startY = size.height / 2; + break; + }*/ + // endregion // if not first, draw top-to-center upper line - if (!isFirst) canvas.drawLine(top, topOfCenter, linePaint); +// if (!isFirst) canvas.drawLine(outboundTop, topOfCenter, linePaint); // if not last, draw center-to-bottom bottom line - if (!isLast) canvas.drawLine(bottomOfCenter, bottom, linePaint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; - } -} + if (!isLast) canvas.drawLine(bottomOfCenter, outboundBottom, testLinePaint); +* */ diff --git a/pubspec.yaml b/pubspec.yaml index eda2aaa..3a88f60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_timeline description: a fully customizable & general timeline widget, based on real-world application references -version: 0.0.4+8 +version: 0.1.1+12 homepage: https://github.com/softmarshmallow/flutter-timeline repository: https://github.com/softmarshmallow/flutter-timeline