Skip to content

Instantly share code, notes, and snippets.

@stargazing-dino
Created September 9, 2019 23:26
Show Gist options
  • Save stargazing-dino/af5872100739fe66f8afa8f0195cacb1 to your computer and use it in GitHub Desktop.
Save stargazing-dino/af5872100739fe66f8afa8f0195cacb1 to your computer and use it in GitHub Desktop.
Text Button Group
import 'package:flutter/material.dart';
// https://pub.dev/packages/reorderables
class TextButtonGroup extends StatefulWidget {
TextButtonGroup({
Key key,
this.initialIndex = 0,
@required this.titles,
@required this.onTap,
@required this.duration,
this.spacing = 20.0,
this.spacingFraction = 0.3,
this.curve = Curves.easeIn,
this.badgeRadius = 4,
this.badgeColor = Colors.blue,
this.selectedItemColor = Colors.black,
this.unselectedItemColor = Colors.grey,
this.alignment = Alignment.center,
}) : super(key: key);
final int initialIndex;
final List<TextSpan> titles;
final void Function(int selectedIndex) onTap;
final Duration duration;
final double spacing;
final double spacingFraction;
final Curve curve;
final double badgeRadius;
final Color badgeColor;
final Color selectedItemColor;
final Color unselectedItemColor;
final Alignment alignment;
@override
_TextButtonGroupState createState() => _TextButtonGroupState();
}
class _TextButtonGroupState extends State<TextButtonGroup>
with SingleTickerProviderStateMixin {
double width = 0;
double leftWidth = 0;
int currentIndex = 0;
static const double BAR_HEIGHT = 60;
static const double INDICATOR_HEIGHT = 2;
List<double> widths;
@override
void initState() {
currentIndex = widget.initialIndex;
super.initState();
}
_select(int index) {
double total = 0;
for (var i = index; i >= 0; i--) total += widths[i];
leftWidth = total;
currentIndex = index;
widget.onTap(currentIndex);
setState(() {});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final List<TextBox> lastBoxes = widget.titles
.map<TextBox>(
(textSpan) => _calcLastLineEnd(context, constraints, textSpan))
.toList();
widths = lastBoxes
.map<double>(
(lastBox) => (lastBox.right - lastBox.left) + widget.spacing)
.toList();
// TODO: rename to sumWidth
width = widths.fold(0, (p, c) => p + c);
final rightSpacing =
(widget.spacing * widget.spacingFraction - widget.spacing) -
widget.badgeRadius;
final leftPosition = leftWidth == 0 ? widths[0] : leftWidth;
return Stack(
alignment: widget.alignment,
fit: StackFit.loose,
overflow: Overflow.visible,
children: <Widget>[
IntrinsicWidth(
child: Row(
children: widget.titles
.asMap()
.map(
(int index, title) {
return MapEntry(
index,
Container(
margin: EdgeInsets.only(
right: index == widget.titles.length - 1
? widget.spacing * widget.spacingFraction +
widget.badgeRadius
: widget.spacing,
top: widget.badgeRadius,
),
child: GestureDetector(
onTap: () {
_select(index);
},
child: _buildTextWidget(
title,
index,
widths[index],
),
),
),
);
},
)
.values
.toList(),
),
),
// Indicator
AnimatedPositioned(
left: leftPosition + rightSpacing,
top: 0,
duration: widget.duration,
curve: widget.curve,
child: CircleAvatar(
radius: widget.badgeRadius,
backgroundColor: widget.badgeColor,
),
),
],
);
},
);
}
// Calculate the left, top, bottom position of the end of the last text
// line.
TextBox _calcLastLineEnd(
BuildContext context,
BoxConstraints constraints,
TextSpan textWidget,
) {
final richTextWidget = Text.rich(textWidget).build(context) as RichText;
final renderObject = richTextWidget.createRenderObject(context);
renderObject.layout(constraints);
final lastBox = renderObject
.getBoxesForSelection(TextSelection(
baseOffset: 0, extentOffset: textWidget.toPlainText().length))
.last;
return lastBox;
}
Widget _buildTextWidget(TextSpan title, int index, double width) {
bool isSelected = index == currentIndex;
final color =
isSelected ? widget.selectedItemColor : widget.unselectedItemColor;
return Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
AnimatedOpacity(
opacity: isSelected ? 1.0 : 0.5,
duration: widget.duration,
curve: widget.curve,
child: Text(
title.text,
style: title.style == null
? TextStyle(color: color)
: title.style.copyWith(color: color),
),
),
],
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment