Skip to content

Commit 7b0f83c

Browse files
Merge pull request #7 from softmarshmallow/feature/line-painter-redesign
Feature/line painter redesign
2 parents 0bb4bad + 772a0cb commit 7b0f83c

File tree

7 files changed

+196
-58
lines changed

7 files changed

+196
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ Timeline(
99
altOffset: Offset(0, -24),
1010
// ...
1111
);
12-
```
12+
```
13+
## [0.1.1+12] - supports anchor & offset for timeline & indicator

example/lib/screen/plain_timeline_demo.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ class _PlainTimelineDemoScreenState extends State<PlainTimelineDemoScreen> {
8080

8181
TimelineEventDisplay get plainEventDisplay {
8282
return TimelineEventDisplay(
83+
anchor: IndicatorPosition.top,
84+
indicatorOffset: Offset(0, 24),
8385
child: TimelineEventCard(
84-
title: Text("just \n\n\n\n now"),
86+
title: Text("multi\nline\ntitle\nawesome!"),
8587
content: Text("someone commented on your timeline ${DateTime.now()}"),
8688
),
8789
indicator: randomIndicator);
@@ -91,11 +93,12 @@ class _PlainTimelineDemoScreenState extends State<PlainTimelineDemoScreen> {
9193

9294
Widget _buildTimeline() {
9395
return TimelineTheme(
94-
data: TimelineThemeData(lineColor: Colors.blueAccent, itemGap: 180),
96+
data: TimelineThemeData(
97+
lineColor: Colors.blueAccent, itemGap: 100, lineGap: 0),
9598
child: Timeline(
96-
indicatorPosition: IndicatorPosition.top,
97-
altOffset: Offset(0, -24),
99+
anchor: IndicatorPosition.center,
98100
indicatorSize: 56,
101+
altOffset: Offset(10, 40),
99102
events: events,
100103
));
101104
}

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ packages:
7373
path: ".."
7474
relative: true
7575
source: path
76-
version: "0.0.4+6"
76+
version: "0.0.4+11"
7777
matcher:
7878
dependency: transitive
7979
description:

lib/event_item.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class TimelineEventDisplay {
88
this.indicator,
99
this.indicatorSize,
1010
this.forceLineDrawing = false,
11-
this.indicatorPosition,
11+
this.anchor,
12+
this.indicatorOffset = const Offset(0, 0),
1213
});
1314

1415
final Widget child;
@@ -20,8 +21,9 @@ class TimelineEventDisplay {
2021
/// enables indicator line drawing even no indicator is passed.
2122
final bool forceLineDrawing;
2223

23-
/// [indicatorPosition] overrides the default IndicatorPosition
24-
final IndicatorPosition indicatorPosition;
24+
/// [anchor] overrides the default IndicatorPosition
25+
final IndicatorPosition anchor;
26+
final Offset indicatorOffset;
2527

2628
bool get hasIndicator {
2729
return indicator != null;

lib/indicator_position.dart

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
1-
enum IndicatorPosition{
2-
top,
3-
center,
4-
bottom
5-
}
1+
import 'package:flutter/widgets.dart';
2+
3+
enum IndicatorPosition { top, center, bottom }
4+
5+
extension Mapper on IndicatorPosition {
6+
Alignment get asAlignment {
7+
switch (this) {
8+
case IndicatorPosition.top:
9+
return Alignment.topCenter;
10+
break;
11+
case IndicatorPosition.center:
12+
return Alignment.center;
13+
break;
14+
case IndicatorPosition.bottom:
15+
return Alignment.bottomCenter;
16+
break;
17+
}
18+
return Alignment.center;
19+
}
20+
}

lib/timeline.dart

Lines changed: 160 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Timeline extends StatelessWidget {
2020
// item gap will be ignored when custom separatorBuilder is provided
2121
this.separatorBuilder,
2222
this.altOffset = const Offset(0, 0),
23-
this.indicatorPosition = IndicatorPosition.center})
23+
this.anchor = IndicatorPosition.center})
2424
: itemCount = events.length;
2525

2626
final Offset altOffset;
@@ -35,8 +35,8 @@ class Timeline extends StatelessWidget {
3535
final bool primary;
3636
final bool reverse;
3737

38-
/// [indicatorPosition] describes where the indicator drawing should start. use it with alt offset
39-
final IndicatorPosition indicatorPosition;
38+
/// [anchor] describes where the indicator drawing should start. use it with alt offset
39+
final IndicatorPosition anchor;
4040

4141
final IndexedWidgetBuilder separatorBuilder;
4242

@@ -98,11 +98,13 @@ class Timeline extends StatelessWidget {
9898
return event.hasIndicator;
9999
}
100100

101-
Widget buildWrappedIndicator(Widget child, {double width, double height}) {
101+
Widget buildWrappedIndicator(Widget child,
102+
{double width, double height, Offset indicatorOffset}) {
103+
final offset = altOffset + indicatorOffset;
102104
return Container(
103105
width: width,
104106
height: height,
105-
transform: Matrix4.translationValues(altOffset.dx, altOffset.dy, 0.0),
107+
transform: Matrix4.translationValues(offset.dx, offset.dy, 0.0),
106108
child: child,
107109
);
108110
}
@@ -114,11 +116,11 @@ class Timeline extends StatelessWidget {
114116
bool nextHasIndicator,
115117
TimelineEventDisplay event,
116118
TimelineThemeData theme}) {
117-
var overrideIndicatorSize =
119+
final overrideIndicatorSize =
118120
event.indicatorSize != null ? event.indicatorSize : indicatorSize;
119-
var overrideIndicatorPosition = event.indicatorPosition != null
120-
? event.indicatorPosition
121-
: indicatorPosition;
121+
final overrideIndicatorPosition =
122+
event.anchor != null ? event.anchor : anchor;
123+
final indicatorOffset = event.indicatorOffset;
122124

123125
var line = CustomPaint(
124126
painter: _LineIndicatorPainter(
@@ -137,6 +139,7 @@ class Timeline extends StatelessWidget {
137139
prevHasIndicator: prevHasIndicator,
138140
nextHasIndicator: nextHasIndicator,
139141
indicatorPosition: overrideIndicatorPosition,
142+
indicatorOffset: indicatorOffset,
140143
),
141144
child: SizedBox(height: double.infinity, width: indicatorSize),
142145
);
@@ -145,9 +148,10 @@ class Timeline extends StatelessWidget {
145148
line,
146149
Positioned.fill(
147150
child: Align(
148-
alignment: Alignment.center,
151+
alignment: overrideIndicatorPosition.asAlignment,
149152
child: buildWrappedIndicator(
150153
event.indicator,
154+
indicatorOffset: indicatorOffset,
151155
width: overrideIndicatorSize,
152156
height: overrideIndicatorSize,
153157
)),
@@ -173,6 +177,7 @@ class _LineIndicatorPainter extends CustomPainter {
173177
@required this.nextHasIndicator,
174178
@required this.prevHasIndicator,
175179
@required this.itemGap,
180+
@required this.indicatorOffset,
176181
@required this.indicatorPosition})
177182
: linePaint = Paint()
178183
..color = lineColor
@@ -183,6 +188,7 @@ class _LineIndicatorPainter extends CustomPainter {
183188
final Offset altOffset;
184189
final bool hideDefaultIndicator;
185190
final double indicatorSize;
191+
final Offset indicatorOffset;
186192
final double maxIndicatorSize;
187193
final double lineGap;
188194
final StrokeCap strokeCap;
@@ -198,39 +204,137 @@ class _LineIndicatorPainter extends CustomPainter {
198204
final IndicatorPosition indicatorPosition;
199205

200206
double get altX {
201-
return altOffset.dx;
207+
return altOffset.dx + indicatorOffset.dx;
202208
}
203209

204210
double get altY {
205-
return altOffset.dy;
211+
return altOffset.dy + indicatorOffset.dy;
206212
}
207213

208214
@override
209215
void paint(Canvas canvas, Size size) {
210-
final indicatorRadius = indicatorSize / 2;
211-
final maxIndicatorRadius = maxIndicatorSize / 2;
212-
final indicatorMargin = indicatorRadius + lineGap;
213-
final safeItemGap = (indicatorRadius) + itemGap;
214-
double topStartY = 0.0;
216+
// indicator's radius
217+
final radius = indicatorSize / 2;
218+
final height = size.height;
219+
final halfHeight = height / 2;
220+
final double halfItemGap = itemGap / 2;
215221

216-
// region calculate starting point
217-
/*
222+
// initial start point
223+
// works well
224+
Offset indicatorCenterStartPoint;
218225
switch (indicatorPosition) {
219226
case IndicatorPosition.top:
220-
topStartY = -size.height / 2;
227+
indicatorCenterStartPoint = size.topCenter(Offset(0, radius));
221228
break;
222229
case IndicatorPosition.center:
223-
topStartY = 0;
230+
indicatorCenterStartPoint = size.center(Offset.zero);
224231
break;
225232
case IndicatorPosition.bottom:
226-
// startY = size.height / 2;
233+
indicatorCenterStartPoint = size.bottomCenter(Offset(0, -radius));
227234
break;
228-
}*/
229-
// endregion
235+
}
236+
237+
// alt start point
238+
Offset indicatorCenter = indicatorCenterStartPoint.translate(altX, altY);
239+
240+
// region upper line
241+
if (!isFirst) {
242+
double additionalGap = 0;
243+
if (!prevHasIndicator) additionalGap = halfItemGap;
244+
final additionalTop = getAdditionalY(height, mode: "upper");
245+
246+
// works well
247+
Offset topStart = indicatorCenter.translate(
248+
0,
249+
// the altY + radius is the default start point.
250+
// adding half item gap is also by default.
251+
// the below two items does not get affected by the indicator position
252+
-(((altY + radius) + halfItemGap) //
253+
+
254+
(additionalGap) +
255+
(additionalTop) //
256+
));
257+
258+
// works well
259+
Offset topEnd = indicatorCenter.translate(0, -radius - lineGap);
260+
261+
// draw upper line
262+
if (!isFirst) canvas.drawLine(topStart, topEnd, linePaint);
263+
}
264+
// endregion upper line
265+
266+
// endregion downer line
267+
if (!isLast) {
268+
double additionalGap = 0;
269+
if (!nextHasIndicator) additionalGap = halfItemGap;
270+
271+
final additionalBottom = getAdditionalY(height, mode: "downer");
272+
273+
// works well
274+
Offset bottomEnd = indicatorCenter.translate(
275+
0,
276+
(radius + halfItemGap - altY) +
277+
(additionalGap) //
278+
+
279+
(additionalBottom) //
280+
);
281+
282+
// works well
283+
Offset bottomStart = indicatorCenter.translate(0, radius + lineGap);
284+
if (!isLast) canvas.drawLine(bottomStart, bottomEnd, linePaint);
285+
}
286+
// endregion downer line
287+
}
288+
289+
double getAdditionalY(double height, {@required String mode}) {
290+
double add = 0;
291+
// the additional size should be
292+
if (mode == "upper") {
293+
switch (indicatorPosition) {
294+
case IndicatorPosition.top:
295+
add = 0;
296+
break;
297+
case IndicatorPosition.center:
298+
add = (height - indicatorSize) / 2;
299+
break;
300+
case IndicatorPosition.bottom:
301+
add = height - indicatorSize;
302+
break;
303+
}
304+
return add;
305+
}
306+
307+
if (mode == "downer") {
308+
switch (indicatorPosition) {
309+
case IndicatorPosition.top:
310+
add = height - indicatorSize;
311+
break;
312+
case IndicatorPosition.center:
313+
add = (height - indicatorSize) / 2;
314+
break;
315+
case IndicatorPosition.bottom:
316+
add = 0;
317+
break;
318+
}
319+
return add;
320+
}
321+
322+
throw FlutterError("$mode is not a supported mode");
323+
}
324+
325+
@override
326+
bool shouldRepaint(CustomPainter oldDelegate) {
327+
return true;
328+
}
329+
}
330+
331+
// painter v1
332+
/*
230333
231334
// region override top, bottom calculator for filling empty space between events
232335
double overrideOffsetYForTop = altY;
233336
double overrideOffsetYForBottom = altY;
337+
234338
if (!prevHasIndicator) {
235339
overrideOffsetYForTop = 0.0;
236340
}
@@ -239,26 +343,39 @@ class _LineIndicatorPainter extends CustomPainter {
239343
}
240344
// endregion
241345
242-
final top = size.topLeft(Offset(maxIndicatorRadius + altX,
243-
topStartY - safeItemGap + overrideOffsetYForTop));
244-
final topOfCenter = size.centerLeft(
245-
Offset(maxIndicatorRadius + altX, -indicatorMargin + altY),
246-
);
346+
final inboundTop = size.topCenter(Offset.zero);
347+
final outboundTop = inboundTop.translate(
348+
altX, topStartY - safeItemGap + overrideOffsetYForTop);
349+
// final outboundTop = size.topLeft(Offset(maxIndicatorRadius + altX,
350+
// topStartY - safeItemGap + overrideOffsetYForTop));
247351
248-
final bottom = size.bottomLeft(Offset(maxIndicatorRadius + altX,
249-
0.0 + safeItemGap + overrideOffsetYForBottom));
250-
final bottomOfCenter = size.centerLeft(
251-
Offset(maxIndicatorRadius + altX, indicatorMargin + altY),
252-
);
352+
// region center
353+
// FIXME
354+
final center = size.center(Offset.zero);
355+
final topOfCenter = center.translate(altX, -indicatorMargin + altY);
356+
final bottomOfCenter = center.translate(altX, indicatorMargin + altY);
357+
// endregion
358+
final inboundBottom = size.bottomCenter(Offset.zero);
359+
final outboundBottom =
360+
inboundBottom.translate(altX, safeItemGap + overrideOffsetYForBottom);
361+
362+
// region calculate starting point
363+
/*
364+
switch (indicatorPosition) {
365+
case IndicatorPosition.top:
366+
topStartY = -size.height / 2;
367+
break;
368+
case IndicatorPosition.center:
369+
topStartY = 0;
370+
break;
371+
case IndicatorPosition.bottom:
372+
// startY = size.height / 2;
373+
break;
374+
}*/
375+
// endregion
253376
254377
// if not first, draw top-to-center upper line
255-
if (!isFirst) canvas.drawLine(top, topOfCenter, linePaint);
378+
// if (!isFirst) canvas.drawLine(outboundTop, topOfCenter, linePaint);
256379
// if not last, draw center-to-bottom bottom line
257-
if (!isLast) canvas.drawLine(bottomOfCenter, bottom, linePaint);
258-
}
259-
260-
@override
261-
bool shouldRepaint(CustomPainter oldDelegate) {
262-
return true;
263-
}
264-
}
380+
if (!isLast) canvas.drawLine(bottomOfCenter, outboundBottom, testLinePaint);
381+
* */

0 commit comments

Comments
 (0)