mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 07:18:15 +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,7 +33,9 @@ class _FrontLayer extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isDesktop = isDisplayDesktop(context);
|
final isDesktop = isDisplayDesktop(context);
|
||||||
|
|
||||||
return PhysicalShape(
|
return DefaultFocusTraversal(
|
||||||
|
policy: ReadingOrderTraversalPolicy(),
|
||||||
|
child: PhysicalShape(
|
||||||
elevation: 16,
|
elevation: 16,
|
||||||
color: cranePrimaryWhite,
|
color: cranePrimaryWhite,
|
||||||
clipper: ShapeBorderClipper(
|
clipper: ShapeBorderClipper(
|
||||||
@@ -53,6 +56,7 @@ class _FrontLayer extends StatelessWidget {
|
|||||||
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,6 +129,8 @@ 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: DefaultFocusTraversal(
|
||||||
|
policy: ReadingOrderTraversalPolicy(),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: cranePurple800,
|
backgroundColor: cranePurple800,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -136,11 +142,12 @@ class _BackdropState extends State<Backdrop> with TickerProviderStateMixin {
|
|||||||
tabHandler: _handleTabs,
|
tabHandler: _handleTabs,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: FocusScope(
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
BackLayer(
|
BackLayer(
|
||||||
tabController: _tabController,
|
tabController: _tabController,
|
||||||
backLayers: widget.backLayer,
|
backLayerItems: widget.backLayerItems,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
@@ -157,22 +164,24 @@ class _BackdropState extends State<Backdrop> with TickerProviderStateMixin {
|
|||||||
SlideTransition(
|
SlideTransition(
|
||||||
position: _flyLayerOffset,
|
position: _flyLayerOffset,
|
||||||
child: _FrontLayer(
|
child: _FrontLayer(
|
||||||
title: GalleryLocalizations.of(context).craneFlySubhead,
|
title: GalleryLocalizations.of(context)
|
||||||
|
.craneFlySubhead,
|
||||||
index: 0,
|
index: 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SlideTransition(
|
SlideTransition(
|
||||||
position: _sleepLayerOffset,
|
position: _sleepLayerOffset,
|
||||||
child: _FrontLayer(
|
child: _FrontLayer(
|
||||||
title:
|
title: GalleryLocalizations.of(context)
|
||||||
GalleryLocalizations.of(context).craneSleepSubhead,
|
.craneSleepSubhead,
|
||||||
index: 1,
|
index: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SlideTransition(
|
SlideTransition(
|
||||||
position: _eatLayerOffset,
|
position: _eatLayerOffset,
|
||||||
child: _FrontLayer(
|
child: _FrontLayer(
|
||||||
title: GalleryLocalizations.of(context).craneEatSubhead,
|
title: GalleryLocalizations.of(context)
|
||||||
|
.craneEatSubhead,
|
||||||
index: 2,
|
index: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -183,33 +192,8 @@ 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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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,9 +34,14 @@ class _ItemCardsState extends State<ItemCards> {
|
|||||||
|
|
||||||
return destinations
|
return destinations
|
||||||
.map(
|
.map(
|
||||||
(d) => RepaintBoundary(
|
(d) => HighlightFocus(
|
||||||
|
debugLabel: 'DestinationCard: ${d.destination}',
|
||||||
|
highlightColor: Colors.red.withOpacity(0.5),
|
||||||
|
onPressed: () {},
|
||||||
|
child: RepaintBoundary(
|
||||||
child: _DestinationCard(destination: d),
|
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