mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 15:28:44 +00:00
[Gallery] Fix directory structure (#312)
This commit is contained in:
368
gallery/lib/studies/rally/home.dart
Normal file
368
gallery/lib/studies/rally/home.dart
Normal file
@@ -0,0 +1,368 @@
|
||||
// Copyright 2019 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gallery/data/gallery_options.dart';
|
||||
import 'package:gallery/l10n/gallery_localizations.dart';
|
||||
import 'package:gallery/layout/adaptive.dart';
|
||||
import 'package:gallery/layout/text_scale.dart';
|
||||
import 'package:gallery/pages/home.dart';
|
||||
import 'package:gallery/layout/focus_traversal_policy.dart';
|
||||
import 'package:gallery/studies/rally/tabs/accounts.dart';
|
||||
import 'package:gallery/studies/rally/tabs/bills.dart';
|
||||
import 'package:gallery/studies/rally/tabs/budgets.dart';
|
||||
import 'package:gallery/studies/rally/tabs/overview.dart';
|
||||
import 'package:gallery/studies/rally/tabs/settings.dart';
|
||||
|
||||
const int tabCount = 5;
|
||||
const int turnsToRotateRight = 1;
|
||||
const int turnsToRotateLeft = 3;
|
||||
|
||||
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);
|
||||
final isDesktop = isDisplayDesktop(context);
|
||||
Widget tabBarView;
|
||||
if (isDesktop) {
|
||||
final isTextDirectionRtl =
|
||||
GalleryOptions.of(context).textDirection() == TextDirection.rtl;
|
||||
final verticalRotation =
|
||||
isTextDirectionRtl ? turnsToRotateLeft : turnsToRotateRight;
|
||||
final revertVerticalRotation =
|
||||
isTextDirectionRtl ? turnsToRotateRight : turnsToRotateLeft;
|
||||
tabBarView = Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 150 + 50 * (cappedTextScale(context) - 1),
|
||||
alignment: Alignment.topCenter,
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
ExcludeSemantics(
|
||||
child: SizedBox(
|
||||
height: 80,
|
||||
child: Image.asset(
|
||||
'logo.png',
|
||||
package: 'rally_assets',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Rotate the tab bar, so the animation is vertical for desktops.
|
||||
RotatedBox(
|
||||
quarterTurns: verticalRotation,
|
||||
child: _RallyTabBar(
|
||||
tabs: _buildTabs(
|
||||
context: context, theme: theme, isVertical: true)
|
||||
.map(
|
||||
(widget) {
|
||||
// Revert the rotation on the tabs.
|
||||
return RotatedBox(
|
||||
quarterTurns: revertVerticalRotation,
|
||||
child: widget,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
tabController: _tabController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
// Rotate the tab views so we can swipe up and down.
|
||||
child: RotatedBox(
|
||||
quarterTurns: verticalRotation,
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: _buildTabViews().map(
|
||||
(widget) {
|
||||
// Revert the rotation on the tab views.
|
||||
return RotatedBox(
|
||||
quarterTurns: revertVerticalRotation,
|
||||
child: widget,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
tabBarView = Column(
|
||||
children: [
|
||||
_RallyTabBar(
|
||||
tabs: _buildTabs(context: context, theme: theme),
|
||||
tabController: _tabController,
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: _buildTabViews(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
final backButtonFocusNode =
|
||||
InheritedFocusNodes.of(context).backButtonFocusNode;
|
||||
|
||||
return DefaultFocusTraversal(
|
||||
policy: EdgeChildrenFocusTraversalPolicy(
|
||||
firstFocusNodeOutsideScope: backButtonFocusNode,
|
||||
lastFocusNodeOutsideScope: backButtonFocusNode,
|
||||
focusScope: FocusScope.of(context),
|
||||
),
|
||||
child: ApplyTextOptions(
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
// For desktop layout we do not want to have SafeArea at the top and
|
||||
// bottom to display 100% height content on the accounts view.
|
||||
top: !isDesktop,
|
||||
bottom: !isDesktop,
|
||||
child: 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: tabBarView,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildTabs(
|
||||
{BuildContext context, ThemeData theme, bool isVertical = false}) {
|
||||
return [
|
||||
_RallyTab(
|
||||
theme: theme,
|
||||
iconData: Icons.pie_chart,
|
||||
title: GalleryLocalizations.of(context).rallyTitleOverview,
|
||||
tabIndex: 0,
|
||||
tabController: _tabController,
|
||||
isVertical: isVertical,
|
||||
),
|
||||
_RallyTab(
|
||||
theme: theme,
|
||||
iconData: Icons.attach_money,
|
||||
title: GalleryLocalizations.of(context).rallyTitleAccounts,
|
||||
tabIndex: 1,
|
||||
tabController: _tabController,
|
||||
isVertical: isVertical,
|
||||
),
|
||||
_RallyTab(
|
||||
theme: theme,
|
||||
iconData: Icons.money_off,
|
||||
title: GalleryLocalizations.of(context).rallyTitleBills,
|
||||
tabIndex: 2,
|
||||
tabController: _tabController,
|
||||
isVertical: isVertical,
|
||||
),
|
||||
_RallyTab(
|
||||
theme: theme,
|
||||
iconData: Icons.table_chart,
|
||||
title: GalleryLocalizations.of(context).rallyTitleBudgets,
|
||||
tabIndex: 3,
|
||||
tabController: _tabController,
|
||||
isVertical: isVertical,
|
||||
),
|
||||
_RallyTab(
|
||||
theme: theme,
|
||||
iconData: Icons.settings,
|
||||
title: GalleryLocalizations.of(context).rallyTitleSettings,
|
||||
tabIndex: 4,
|
||||
tabController: _tabController,
|
||||
isVertical: isVertical,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildTabViews() {
|
||||
return [
|
||||
OverviewView(),
|
||||
AccountsView(),
|
||||
BillsView(),
|
||||
BudgetsView(),
|
||||
SettingsView(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _RallyTabBar extends StatelessWidget {
|
||||
const _RallyTabBar({Key key, this.tabs, this.tabController})
|
||||
: super(key: key);
|
||||
|
||||
final List<Widget> tabs;
|
||||
final TabController tabController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return 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: tabs,
|
||||
controller: tabController,
|
||||
// This hides the tab indicator.
|
||||
indicatorColor: Colors.transparent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RallyTab extends StatefulWidget {
|
||||
_RallyTab({
|
||||
ThemeData theme,
|
||||
IconData iconData,
|
||||
String title,
|
||||
int tabIndex,
|
||||
TabController tabController,
|
||||
this.isVertical,
|
||||
}) : titleText = Text(title, style: theme.textTheme.button),
|
||||
isExpanded = tabController.index == tabIndex,
|
||||
icon = Icon(iconData, semanticLabel: title);
|
||||
|
||||
final Text titleText;
|
||||
final Icon icon;
|
||||
final bool isExpanded;
|
||||
final bool isVertical;
|
||||
|
||||
@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) {
|
||||
if (widget.isVertical) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
FadeTransition(
|
||||
child: widget.icon,
|
||||
opacity: _iconFadeAnimation,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FadeTransition(
|
||||
child: SizeTransition(
|
||||
child: Center(child: ExcludeSemantics(child: widget.titleText)),
|
||||
axis: Axis.vertical,
|
||||
axisAlignment: -1,
|
||||
sizeFactor: _titleSizeAnimation,
|
||||
),
|
||||
opacity: _titleFadeAnimation,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 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 width = MediaQuery.of(context).size.width;
|
||||
const expandedTitleWidthMultiplier = 2;
|
||||
final unitWidth = width / (tabCount + expandedTitleWidthMultiplier);
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: 56),
|
||||
child: Row(
|
||||
children: [
|
||||
FadeTransition(
|
||||
child: SizedBox(
|
||||
width: unitWidth,
|
||||
child: widget.icon,
|
||||
),
|
||||
opacity: _iconFadeAnimation,
|
||||
),
|
||||
FadeTransition(
|
||||
child: SizeTransition(
|
||||
child: SizedBox(
|
||||
width: unitWidth * expandedTitleWidthMultiplier,
|
||||
child: Center(
|
||||
child: ExcludeSemantics(child: widget.titleText),
|
||||
),
|
||||
),
|
||||
axis: Axis.horizontal,
|
||||
axisAlignment: -1,
|
||||
sizeFactor: _titleSizeAnimation,
|
||||
),
|
||||
opacity: _titleFadeAnimation,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user