blob: 8b64419ea896059cc07781b39e2a5b67e4d17536 [file] [log] [blame]
// Copyright 2019-present the Flutter authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:flutter/material.dart';
import 'package:flutter_gallery/demo/rally/tabs/accounts.dart';
import 'package:flutter_gallery/demo/rally/tabs/bills.dart';
import 'package:flutter_gallery/demo/rally/tabs/budgets.dart';
import 'package:flutter_gallery/demo/rally/tabs/overview.dart';
import 'package:flutter_gallery/demo/rally/tabs/settings.dart';
const int tabCount = 5;
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: tabCount, vsync: this)
..addListener(() {
// Set state to make sure that the [_RallyTab] widgets get updated when changing tabs.
setState(() { });
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Theme(
// This theme effectively removes the default visual touch
// feedback for tapping a tab, which is replaced with a custom
// animation.
data: theme.copyWith(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
child: TabBar(
// Setting isScrollable to true prevents the tabs from being
// wrapped in [Expanded] widgets, which allows for more
// flexible sizes and size animations among tabs.
isScrollable: true,
labelPadding: EdgeInsets.zero,
tabs: _buildTabs(theme),
controller: _tabController,
// This hides the tab indicator.
indicatorColor: Colors.transparent,
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: _buildTabViews(),
),
),
],
),
),
);
}
List<Widget> _buildTabs(ThemeData theme) {
return <Widget>[
_RallyTab(theme, Icons.pie_chart, 'OVERVIEW', 0, _tabController),
_RallyTab(theme, Icons.attach_money, 'ACCOUNTS', 1, _tabController),
_RallyTab(theme, Icons.money_off, 'BILLS', 2, _tabController),
_RallyTab(theme, Icons.table_chart, 'BUDGETS', 3, _tabController),
_RallyTab(theme, Icons.settings, 'SETTINGS', 4, _tabController),
];
}
List<Widget> _buildTabViews() {
return <Widget>[
OverviewView(),
AccountsView(),
BillsView(),
BudgetsView(),
SettingsView(),
];
}
}
class _RallyTab extends StatefulWidget {
_RallyTab(
ThemeData theme,
IconData iconData,
String title,
int tabIndex,
TabController tabController,
) : titleText = Text(title, style: theme.textTheme.button),
isExpanded = tabController.index == tabIndex,
icon = Icon(iconData);
final Text titleText;
final Icon icon;
final bool isExpanded;
@override
_RallyTabState createState() => _RallyTabState();
}
class _RallyTabState extends State<_RallyTab>
with SingleTickerProviderStateMixin {
Animation<double> _titleSizeAnimation;
Animation<double> _titleFadeAnimation;
Animation<double> _iconFadeAnimation;
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_titleSizeAnimation = _controller.view;
_titleFadeAnimation = _controller.drive(CurveTween(curve: Curves.easeOut));
_iconFadeAnimation = _controller.drive(Tween<double>(begin: 0.6, end: 1));
if (widget.isExpanded) {
_controller.value = 1;
}
}
@override
void didUpdateWidget(_RallyTab oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isExpanded) {
_controller.forward();
} else {
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
// Calculate the width of each unexpanded tab by counting the number of
// units and dividing it into the screen width. Each unexpanded tab is 1
// unit, and there is always 1 expanded tab which is 1 unit + any extra
// space determined by the multiplier.
final double width = MediaQuery.of(context).size.width;
const double expandedTitleWidthMultiplier = 2;
final double unitWidth = width / (tabCount + expandedTitleWidthMultiplier);
return SizedBox(
height: 56,
child: Row(
children: <Widget>[
FadeTransition(
child: SizedBox(
width: unitWidth,
child: widget.icon,
),
opacity: _iconFadeAnimation,
),
FadeTransition(
child: SizeTransition(
child: SizedBox(
width: unitWidth * expandedTitleWidthMultiplier,
child: Center(child: widget.titleText),
),
axis: Axis.horizontal,
axisAlignment: -1,
sizeFactor: _titleSizeAnimation,
),
opacity: _titleFadeAnimation,
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}