mirror of
https://github.com/flutter/samples.git
synced 2025-11-10 06:48:26 +00:00
[Gallery] Crane focus (#254)
* Add debugLabel to HighlightFocus * Remove decrated property * Add focus to DestinationCards * Add index property to Forms * Move BackLayer stuff to its own file * Add focus * Revert deprecated change * Address feedback
This commit is contained in:
@@ -15,6 +15,7 @@ class HighlightFocus extends StatefulWidget {
|
||||
this.highlightColor,
|
||||
this.borderColor,
|
||||
this.hasFocus = true,
|
||||
this.debugLabel,
|
||||
});
|
||||
|
||||
/// [onPressed] is called when you press space, enter, or numpad-enter
|
||||
@@ -37,6 +38,8 @@ class HighlightFocus extends StatefulWidget {
|
||||
/// Set to false if you want the child to skip focus.
|
||||
final bool hasFocus;
|
||||
|
||||
final String debugLabel;
|
||||
|
||||
@override
|
||||
_HighlightFocusState createState() => _HighlightFocusState();
|
||||
}
|
||||
@@ -67,6 +70,7 @@ class _HighlightFocusState extends State<HighlightFocus> {
|
||||
|
||||
return Focus(
|
||||
canRequestFocus: widget.hasFocus,
|
||||
debugLabel: widget.debugLabel,
|
||||
onFocusChange: (newValue) {
|
||||
setState(() {
|
||||
isFocused = newValue;
|
||||
|
||||
@@ -41,10 +41,10 @@ class _CraneAppState extends State<CraneApp> {
|
||||
home: ApplyTextOptions(
|
||||
child: Backdrop(
|
||||
frontLayer: Container(),
|
||||
backLayer: [
|
||||
FlyForm(),
|
||||
SleepForm(),
|
||||
EatForm(),
|
||||
backLayerItems: [
|
||||
FlyForm(index: 0),
|
||||
SleepForm(index: 1),
|
||||
EatForm(index: 2),
|
||||
],
|
||||
frontTitle: Text('CRANE'),
|
||||
backTitle: Text('MENU'),
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:gallery/data/gallery_options.dart';
|
||||
import 'package:gallery/l10n/gallery_localizations.dart';
|
||||
import 'package:gallery/layout/adaptive.dart';
|
||||
import 'package:gallery/studies/crane/border_tab_indicator.dart';
|
||||
import 'package:gallery/studies/crane/backlayer.dart';
|
||||
import 'package:gallery/studies/crane/colors.dart';
|
||||
import 'package:gallery/studies/crane/item_cards.dart';
|
||||
|
||||
@@ -32,26 +33,29 @@ class _FrontLayer extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = isDisplayDesktop(context);
|
||||
|
||||
return PhysicalShape(
|
||||
elevation: 16,
|
||||
color: cranePrimaryWhite,
|
||||
clipper: ShapeBorderClipper(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(frontLayerBorderRadius),
|
||||
topRight: Radius.circular(frontLayerBorderRadius),
|
||||
return DefaultFocusTraversal(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: PhysicalShape(
|
||||
elevation: 16,
|
||||
color: cranePrimaryWhite,
|
||||
clipper: ShapeBorderClipper(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(frontLayerBorderRadius),
|
||||
topRight: Radius.circular(frontLayerBorderRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ListView(
|
||||
padding: isDesktop
|
||||
? EdgeInsets.symmetric(horizontal: 120, vertical: 22)
|
||||
: EdgeInsets.all(20),
|
||||
children: [
|
||||
Text(title, style: Theme.of(context).textTheme.subtitle),
|
||||
SizedBox(height: 20),
|
||||
ItemCards(index: index),
|
||||
],
|
||||
child: ListView(
|
||||
padding: isDesktop
|
||||
? EdgeInsets.symmetric(horizontal: 120, vertical: 22)
|
||||
: EdgeInsets.all(20),
|
||||
children: [
|
||||
Text(title, style: Theme.of(context).textTheme.subtitle),
|
||||
SizedBox(height: 20),
|
||||
ItemCards(index: index),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -65,17 +69,17 @@ class _FrontLayer extends StatelessWidget {
|
||||
/// front or back layer is showing.
|
||||
class Backdrop extends StatefulWidget {
|
||||
final Widget frontLayer;
|
||||
final List<Widget> backLayer;
|
||||
final List<BackLayerItem> backLayerItems;
|
||||
final Widget frontTitle;
|
||||
final Widget backTitle;
|
||||
|
||||
const Backdrop({
|
||||
@required this.frontLayer,
|
||||
@required this.backLayer,
|
||||
@required this.backLayerItems,
|
||||
@required this.frontTitle,
|
||||
@required this.backTitle,
|
||||
}) : assert(frontLayer != null),
|
||||
assert(backLayer != null),
|
||||
assert(backLayerItems != null),
|
||||
assert(frontTitle != null),
|
||||
assert(backTitle != null);
|
||||
|
||||
@@ -125,61 +129,68 @@ class _BackdropState extends State<Backdrop> with TickerProviderStateMixin {
|
||||
color: cranePurple800,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: Scaffold(
|
||||
backgroundColor: cranePurple800,
|
||||
appBar: AppBar(
|
||||
brightness: Brightness.dark,
|
||||
elevation: 0,
|
||||
titleSpacing: 0,
|
||||
flexibleSpace: CraneAppBar(
|
||||
tabController: _tabController,
|
||||
tabHandler: _handleTabs,
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
BackLayer(
|
||||
child: DefaultFocusTraversal(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: Scaffold(
|
||||
backgroundColor: cranePurple800,
|
||||
appBar: AppBar(
|
||||
brightness: Brightness.dark,
|
||||
elevation: 0,
|
||||
titleSpacing: 0,
|
||||
flexibleSpace: CraneAppBar(
|
||||
tabController: _tabController,
|
||||
backLayers: widget.backLayer,
|
||||
tabHandler: _handleTabs,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: isDesktop
|
||||
? 60 + 20 * textScaleFactor / 2
|
||||
: 175 + 140 * textScaleFactor / 2,
|
||||
),
|
||||
child: TabBarView(
|
||||
physics: isDesktop
|
||||
? NeverScrollableScrollPhysics()
|
||||
: null, // use default TabBarView physics
|
||||
controller: _tabController,
|
||||
children: [
|
||||
SlideTransition(
|
||||
position: _flyLayerOffset,
|
||||
child: _FrontLayer(
|
||||
title: GalleryLocalizations.of(context).craneFlySubhead,
|
||||
index: 0,
|
||||
),
|
||||
),
|
||||
body: FocusScope(
|
||||
child: Stack(
|
||||
children: [
|
||||
BackLayer(
|
||||
tabController: _tabController,
|
||||
backLayerItems: widget.backLayerItems,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: isDesktop
|
||||
? 60 + 20 * textScaleFactor / 2
|
||||
: 175 + 140 * textScaleFactor / 2,
|
||||
),
|
||||
SlideTransition(
|
||||
position: _sleepLayerOffset,
|
||||
child: _FrontLayer(
|
||||
title:
|
||||
GalleryLocalizations.of(context).craneSleepSubhead,
|
||||
index: 1,
|
||||
),
|
||||
child: TabBarView(
|
||||
physics: isDesktop
|
||||
? NeverScrollableScrollPhysics()
|
||||
: null, // use default TabBarView physics
|
||||
controller: _tabController,
|
||||
children: [
|
||||
SlideTransition(
|
||||
position: _flyLayerOffset,
|
||||
child: _FrontLayer(
|
||||
title: GalleryLocalizations.of(context)
|
||||
.craneFlySubhead,
|
||||
index: 0,
|
||||
),
|
||||
),
|
||||
SlideTransition(
|
||||
position: _sleepLayerOffset,
|
||||
child: _FrontLayer(
|
||||
title: GalleryLocalizations.of(context)
|
||||
.craneSleepSubhead,
|
||||
index: 1,
|
||||
),
|
||||
),
|
||||
SlideTransition(
|
||||
position: _eatLayerOffset,
|
||||
child: _FrontLayer(
|
||||
title: GalleryLocalizations.of(context)
|
||||
.craneEatSubhead,
|
||||
index: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SlideTransition(
|
||||
position: _eatLayerOffset,
|
||||
child: _FrontLayer(
|
||||
title: GalleryLocalizations.of(context).craneEatSubhead,
|
||||
index: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -187,33 +198,6 @@ class _BackdropState extends State<Backdrop> with TickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
class BackLayer extends StatefulWidget {
|
||||
final List<Widget> backLayers;
|
||||
final TabController tabController;
|
||||
|
||||
const BackLayer({Key key, this.backLayers, this.tabController})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_BackLayerState createState() => _BackLayerState();
|
||||
}
|
||||
|
||||
class _BackLayerState extends State<BackLayer> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.tabController.addListener(() => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IndexedStack(
|
||||
index: widget.tabController.index,
|
||||
children: widget.backLayers,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CraneAppBar extends StatefulWidget {
|
||||
final Function(int) tabHandler;
|
||||
final TabController tabController;
|
||||
|
||||
49
gallery/gallery/lib/studies/crane/backlayer.dart
Normal file
49
gallery/gallery/lib/studies/crane/backlayer.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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';
|
||||
|
||||
abstract class BackLayerItem extends StatefulWidget {
|
||||
final int index;
|
||||
|
||||
BackLayerItem({Key key, this.index}) : super(key: key);
|
||||
}
|
||||
|
||||
class BackLayer extends StatefulWidget {
|
||||
final List<BackLayerItem> backLayerItems;
|
||||
final TabController tabController;
|
||||
|
||||
const BackLayer({Key key, this.backLayerItems, this.tabController})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_BackLayerState createState() => _BackLayerState();
|
||||
}
|
||||
|
||||
class _BackLayerState extends State<BackLayer> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.tabController.addListener(() => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tabIndex = widget.tabController.index;
|
||||
return DefaultFocusTraversal(
|
||||
policy: WidgetOrderFocusTraversalPolicy(),
|
||||
child: IndexedStack(
|
||||
index: tabIndex,
|
||||
children: [
|
||||
for (BackLayerItem backLayerItem in widget.backLayerItems)
|
||||
Focus(
|
||||
canRequestFocus: backLayerItem.index == tabIndex,
|
||||
debugLabel: 'backLayerItem: $backLayerItem',
|
||||
child: backLayerItem,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:gallery/l10n/gallery_localizations.dart';
|
||||
import 'package:gallery/studies/crane/backlayer.dart';
|
||||
import 'package:gallery/studies/crane/header_form.dart';
|
||||
|
||||
class EatForm extends StatefulWidget {
|
||||
class EatForm extends BackLayerItem {
|
||||
EatForm({int index}) : super(index: index);
|
||||
|
||||
@override
|
||||
_EatFormState createState() => _EatFormState();
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:gallery/l10n/gallery_localizations.dart';
|
||||
import 'package:gallery/studies/crane/backlayer.dart';
|
||||
import 'package:gallery/studies/crane/header_form.dart';
|
||||
|
||||
class FlyForm extends StatefulWidget {
|
||||
class FlyForm extends BackLayerItem {
|
||||
FlyForm({int index}) : super(index: index);
|
||||
|
||||
@override
|
||||
_FlyFormState createState() => _FlyFormState();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:gallery/layout/adaptive.dart';
|
||||
import 'package:gallery/layout/highlight_focus.dart';
|
||||
import 'package:gallery/studies/crane/model/data.dart';
|
||||
import 'package:gallery/studies/crane/model/destination.dart';
|
||||
|
||||
@@ -33,8 +34,13 @@ class _ItemCardsState extends State<ItemCards> {
|
||||
|
||||
return destinations
|
||||
.map(
|
||||
(d) => RepaintBoundary(
|
||||
child: _DestinationCard(destination: d),
|
||||
(d) => HighlightFocus(
|
||||
debugLabel: 'DestinationCard: ${d.destination}',
|
||||
highlightColor: Colors.red.withOpacity(0.5),
|
||||
onPressed: () {},
|
||||
child: RepaintBoundary(
|
||||
child: _DestinationCard(destination: d),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gallery/l10n/gallery_localizations.dart';
|
||||
|
||||
import 'package:gallery/studies/crane/backlayer.dart';
|
||||
import 'package:gallery/studies/crane/header_form.dart';
|
||||
|
||||
class SleepForm extends StatefulWidget {
|
||||
class SleepForm extends BackLayerItem {
|
||||
SleepForm({int index}) : super(index: index);
|
||||
|
||||
@override
|
||||
_SleepFormState createState() => _SleepFormState();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user