Last active
April 4, 2018 05:06
-
-
Save nhancv/18f2f75222da312289a75a176c6f50fc to your computer and use it in GitHub Desktop.
Tab Indicator simple
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/widgets.dart'; | |
import 'package:flutter/animation.dart'; | |
import 'package:flutter/material.dart'; | |
void main() => runApp(new MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return new MaterialApp( | |
title: 'Flutter Demo', | |
theme: new ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: new MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
@override | |
_MyHomePageState createState() => new _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
int fromTabIndex = 0; | |
int toTabIndex = 0; | |
Random r = new Random(); | |
void update() { | |
setState(() { | |
fromTabIndex = toTabIndex; | |
toTabIndex = (r.nextInt(4) + fromTabIndex) % 4; | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return new Scaffold( | |
appBar: new AppBar( | |
title: new Text('Hello'), | |
), | |
body: new TabIndicator( | |
screenSize: MediaQuery.of(context).size, | |
fromIndex: fromTabIndex, | |
toIndex: toTabIndex, | |
), | |
floatingActionButton: new FloatingActionButton( | |
onPressed: update, | |
child: new Icon(Icons.repeat), | |
), | |
); | |
} | |
} | |
class TabIndicator extends StatefulWidget { | |
final Size screenSize; | |
final int fromIndex; | |
final int toIndex; | |
TabIndicator( | |
{Key key, @required this.screenSize, this.fromIndex, this.toIndex}) | |
: super(key: key); | |
@override | |
State<StatefulWidget> createState() { | |
return new _TabIndicatorState(); | |
} | |
} | |
class _TabIndicatorState extends State<TabIndicator> | |
with TickerProviderStateMixin { | |
AnimationController animationController; | |
Animation<double> dxTargetAnim; | |
Animation<double> dxEntryAnim; | |
Size screenSize; | |
double iconSize; | |
double height; | |
double section; | |
double horizontalPadding; | |
@override | |
void initState() { | |
super.initState(); | |
iconSize = 47.0; | |
height = 70.0; | |
screenSize = widget.screenSize; | |
section = screenSize.width / 8; | |
horizontalPadding = section - iconSize / 2; | |
setUpAnimation(widget.fromIndex, widget.toIndex); | |
} | |
@override | |
void dispose() { | |
if (animationController != null) animationController.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didUpdateWidget(TabIndicator oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
if (oldWidget.fromIndex != widget.fromIndex || | |
oldWidget.toIndex != widget.toIndex) { | |
setUpAnimation(widget.fromIndex, widget.toIndex); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return new Container( | |
color: const Color(0xffee613a), | |
child: new CustomPaint( | |
size: new Size( | |
MediaQuery.of(context).size.width, | |
MediaQuery.of(context).size.height, | |
), | |
painter: new _TabIndicationPainter( | |
dxTarget: dxTargetAnim.value, | |
dxEntry: dxEntryAnim.value, | |
dy: height / 2, | |
radius: iconSize / 2, | |
), | |
child: indicatorIcon(), | |
), | |
); | |
} | |
void setUpAnimation(int fromIndex, int toIndex) { | |
animationController = new AnimationController( | |
duration: new Duration(milliseconds: 700), | |
vsync: this, | |
); | |
dxTargetAnim = new Tween<double>( | |
begin: section * (fromIndex * 2 + 1), | |
end: section * (toIndex * 2 + 1)) | |
.animate(intervalCurved(0.0, 1.0)); | |
dxEntryAnim = new Tween<double>( | |
begin: section * (fromIndex * 2 + 1), | |
end: section * (toIndex * 2 + 1)) | |
.animate(intervalCurved(0.5, 1.0)); | |
animationController | |
..addListener(() { | |
setState(() {}); | |
}); | |
if (fromIndex != toIndex) { | |
animationController.forward(); | |
} | |
} | |
CurvedAnimation intervalCurved(begin, end, [curve = Curves.easeInOut]) { | |
return new CurvedAnimation( | |
parent: animationController, | |
curve: new Interval(begin, end, curve: curve), | |
); | |
} | |
Widget indicatorIcon() { | |
return new Container( | |
width: screenSize.width, | |
height: height, | |
padding: new EdgeInsets.symmetric( | |
vertical: height / 2 - iconSize / 2, | |
), | |
child: new Stack( | |
children: <Widget>[ | |
getIcon(0), | |
getIcon(1), | |
getIcon(2), | |
getIcon(3), | |
], | |
), | |
); | |
} | |
Widget getIcon(index) { | |
return new Positioned( | |
left: section * (index * 2 + 1) - iconSize / 2, | |
child: new Container( | |
width: 47.0, | |
height: 47.0, | |
alignment: Alignment.center, | |
child: new Text('x'), | |
), | |
); | |
} | |
} | |
class _TabIndicationPainter extends CustomPainter { | |
Paint painter; | |
final double dxTarget; | |
final double dxEntry; | |
final double radius; | |
final double dy; | |
_TabIndicationPainter( | |
{this.dxTarget = 200.0, | |
this.dxEntry = 50.0, | |
this.radius = 25.0, | |
this.dy = 25.0}) { | |
painter = new Paint() | |
..color = Colors.white | |
..style = PaintingStyle.fill; | |
} | |
@override | |
void paint(Canvas canvas, Size size) { | |
bool left2right = dxEntry < dxTarget; | |
Offset entry = new Offset(left2right ? dxEntry : dxTarget, dy); | |
Offset target = new Offset(left2right ? dxTarget : dxEntry, dy); | |
Path path = new Path(); | |
path.addArc( | |
new Rect.fromCircle(center: entry, radius: radius), 0.5 * pi, 1 * pi); | |
path.addRect( | |
new Rect.fromLTRB(entry.dx, dy - radius, target.dx, dy + radius)); | |
path.addArc( | |
new Rect.fromCircle(center: target, radius: radius), 1.5 * pi, 1 * pi); | |
canvas.drawPath(path, painter); | |
} | |
@override | |
bool shouldRepaint(_TabIndicationPainter oldDelegate) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment