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