1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'tabs.dart'; |
6 | library; |
7 | |
8 | import 'package:flutter/widgets.dart'; |
9 | |
10 | import 'colors.dart'; |
11 | |
12 | /// Used with [TabBar.indicator] to draw a horizontal line below the |
13 | /// selected tab. |
14 | /// |
15 | /// The selected tab underline is inset from the tab's boundary by [insets]. |
16 | /// The [borderSide] defines the line's color and weight. |
17 | /// |
18 | /// The [TabBar.indicatorSize] property can be used to define the indicator's |
19 | /// bounds in terms of its (centered) widget with [TabBarIndicatorSize.label], |
20 | /// or the entire tab with [TabBarIndicatorSize.tab]. |
21 | class UnderlineTabIndicator extends Decoration { |
22 | /// Create an underline style selected tab indicator. |
23 | const UnderlineTabIndicator({ |
24 | this.borderRadius, |
25 | this.borderSide = const BorderSide(width: 2.0, color: Colors.white), |
26 | this.insets = EdgeInsets.zero, |
27 | }); |
28 | |
29 | /// The radius of the indicator's corners. |
30 | /// |
31 | /// If this value is non-null, rounded rectangular tab indicator is |
32 | /// drawn, otherwise rectangular tab indicator is drawn. |
33 | final BorderRadius? borderRadius; |
34 | |
35 | /// The color and weight of the horizontal line drawn below the selected tab. |
36 | final BorderSide borderSide; |
37 | |
38 | /// Locates the selected tab's underline relative to the tab's boundary. |
39 | /// |
40 | /// The [TabBar.indicatorSize] property can be used to define the tab |
41 | /// indicator's bounds in terms of its (centered) tab widget with |
42 | /// [TabBarIndicatorSize.label], or the entire tab with |
43 | /// [TabBarIndicatorSize.tab]. |
44 | final EdgeInsetsGeometry insets; |
45 | |
46 | @override |
47 | Decoration? lerpFrom(Decoration? a, double t) { |
48 | if (a is UnderlineTabIndicator) { |
49 | return UnderlineTabIndicator( |
50 | borderSide: BorderSide.lerp(a.borderSide, borderSide, t), |
51 | insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!, |
52 | ); |
53 | } |
54 | return super.lerpFrom(a, t); |
55 | } |
56 | |
57 | @override |
58 | Decoration? lerpTo(Decoration? b, double t) { |
59 | if (b is UnderlineTabIndicator) { |
60 | return UnderlineTabIndicator( |
61 | borderSide: BorderSide.lerp(borderSide, b.borderSide, t), |
62 | insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!, |
63 | ); |
64 | } |
65 | return super.lerpTo(b, t); |
66 | } |
67 | |
68 | @override |
69 | BoxPainter createBoxPainter([ VoidCallback? onChanged ]) { |
70 | return _UnderlinePainter(this, borderRadius, onChanged); |
71 | } |
72 | |
73 | Rect _indicatorRectFor(Rect rect, TextDirection textDirection) { |
74 | final Rect indicator = insets.resolve(textDirection).deflateRect(rect); |
75 | return Rect.fromLTWH( |
76 | indicator.left, |
77 | indicator.bottom - borderSide.width, |
78 | indicator.width, |
79 | borderSide.width, |
80 | ); |
81 | } |
82 | |
83 | @override |
84 | Path getClipPath(Rect rect, TextDirection textDirection) { |
85 | if (borderRadius != null) { |
86 | return Path()..addRRect( |
87 | borderRadius!.toRRect(_indicatorRectFor(rect, textDirection)) |
88 | ); |
89 | } |
90 | return Path()..addRect(_indicatorRectFor(rect, textDirection)); |
91 | } |
92 | } |
93 | |
94 | class _UnderlinePainter extends BoxPainter { |
95 | _UnderlinePainter( |
96 | this.decoration, |
97 | this.borderRadius, |
98 | super.onChanged, |
99 | ); |
100 | |
101 | final UnderlineTabIndicator decoration; |
102 | final BorderRadius? borderRadius; |
103 | |
104 | @override |
105 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { |
106 | assert(configuration.size != null); |
107 | final Rect rect = offset & configuration.size!; |
108 | final TextDirection textDirection = configuration.textDirection!; |
109 | final Paint paint; |
110 | if (borderRadius != null) { |
111 | paint = Paint()..color = decoration.borderSide.color; |
112 | final Rect indicator = decoration._indicatorRectFor(rect, textDirection); |
113 | final RRect rrect = RRect.fromRectAndCorners( |
114 | indicator, |
115 | topLeft: borderRadius!.topLeft, |
116 | topRight: borderRadius!.topRight, |
117 | bottomRight: borderRadius!.bottomRight, |
118 | bottomLeft: borderRadius!.bottomLeft, |
119 | ); |
120 | canvas.drawRRect(rrect, paint); |
121 | } else { |
122 | paint = decoration.borderSide.toPaint()..strokeCap = StrokeCap.square; |
123 | final Rect indicator = decoration._indicatorRectFor(rect, textDirection) |
124 | .deflate(decoration.borderSide.width / 2.0); |
125 | canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint); |
126 | } |
127 | } |
128 | } |
129 | |