Added an example for IndexedStack widget (#105318)
* Added an example for IndexedStack * Added tests for the IndexedStack example * Fixed type issue for onSubmitted callback functions * Fixed documentation and moved files to their appropriate places * Fixed documentation and moved files to their appropriate places * Moved test files to their appropriate places * Moved test files to their appropriate places * Fixed file path in documentation * Remove trailing space * Formatting changes * Remove extra line * Further formatting changes * Further formatting changes * fix comma and inline Co-authored-by: Greg Spencer <gspencergoog@users.noreply.github.com> * Formatting * indentation and formatting * Formatting * Formatting * Formatting * Removed duplicate chevron * better wording on documentation Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com> * Added testing for state preservation Co-authored-by: Greg Spencer <gspencergoog@users.noreply.github.com> Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com>
This commit is contained in:
parent
38df107b8c
commit
082560313b
144
examples/api/lib/widgets/basic/indexed_stack.0.dart
Normal file
144
examples/api/lib/widgets/basic/indexed_stack.0.dart
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flutter code sample for IndexedStack.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void main() => runApp(const MyApp());
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
static const String _title = 'Flutter Code Sample';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: _title,
|
||||||
|
home: Scaffold(
|
||||||
|
appBar: AppBar(title: const Text(_title)),
|
||||||
|
body: const MyStatefulWidget(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyStatefulWidget extends StatefulWidget {
|
||||||
|
const MyStatefulWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
|
||||||
|
List<String> names = <String>['Dash', 'John', 'Mary'];
|
||||||
|
int index = 0;
|
||||||
|
final TextEditingController fieldText = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
width: 300,
|
||||||
|
child: TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
hintText: 'Enter the name for a person to track',
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) {
|
||||||
|
setState(() {
|
||||||
|
names.add(value);
|
||||||
|
});
|
||||||
|
fieldText.clear();
|
||||||
|
},
|
||||||
|
controller: fieldText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 50),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
if (index == 0) {
|
||||||
|
index = names.length - 1;
|
||||||
|
} else {
|
||||||
|
index -= 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(key: Key('gesture1'), Icons.chevron_left),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
IndexedStack(
|
||||||
|
index: index,
|
||||||
|
children: <Widget>[
|
||||||
|
for (String name in names) PersonTracker(name: name)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
if (index == names.length - 1) {
|
||||||
|
index = 0;
|
||||||
|
} else {
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(key: Key('gesture2'), Icons.chevron_right),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PersonTracker extends StatefulWidget {
|
||||||
|
const PersonTracker({super.key, required this.name});
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
State<PersonTracker> createState() => _PersonTrackerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonTrackerState extends State<PersonTracker> {
|
||||||
|
int counter = 0;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
key: Key(widget.name),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromARGB(255, 239, 248, 255),
|
||||||
|
border: Border.all(color: const Color.fromARGB(255, 54, 60, 244)),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Name: ${widget.name}'),
|
||||||
|
Text('Score: $counter'),
|
||||||
|
TextButton.icon(
|
||||||
|
key: Key('increment${widget.name}'),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
counter += 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
label: const Text('Increment'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
93
examples/api/test/widgets/basic/indexed_stack.0_test.dart
Normal file
93
examples/api/test/widgets/basic/indexed_stack.0_test.dart
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. 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:flutter_api_samples/widgets/basic/indexed_stack.0.dart' as example;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('has correct forward rendering mechanism', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(const example.MyApp());
|
||||||
|
|
||||||
|
final Finder gesture2 = find.byKey(const Key('gesture2'));
|
||||||
|
final Element containerFinder = find.byKey(const Key('Dash')).evaluate().first;
|
||||||
|
expect(containerFinder.renderObject!.debugNeedsPaint, false);
|
||||||
|
final Element containerFinder1 = find.byKey(const Key('John')).evaluate().first;
|
||||||
|
expect(containerFinder1.renderObject!.debugNeedsPaint, true);
|
||||||
|
final Element containerFinder2 = find.byKey(const Key('Mary')).evaluate().first;
|
||||||
|
expect(containerFinder2.renderObject!.debugNeedsPaint, true);
|
||||||
|
|
||||||
|
await tester.tap(gesture2);
|
||||||
|
await tester.pump();
|
||||||
|
expect(containerFinder.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder1.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder2.renderObject!.debugNeedsPaint, true);
|
||||||
|
|
||||||
|
await tester.tap(gesture2);
|
||||||
|
await tester.pump();
|
||||||
|
expect(containerFinder.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder1.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder2.renderObject!.debugNeedsPaint, false);
|
||||||
|
});
|
||||||
|
testWidgets('has correct backward rendering mechanism', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(const example.MyApp());
|
||||||
|
|
||||||
|
final Finder gesture1 = find.byKey(const Key('gesture1'));
|
||||||
|
final Element containerFinder = find.byKey(const Key('Dash')).evaluate().first;
|
||||||
|
final Element containerFinder1 = find.byKey(const Key('John')).evaluate().first;
|
||||||
|
final Element containerFinder2 = find.byKey(const Key('Mary')).evaluate().first;
|
||||||
|
|
||||||
|
await tester.tap(gesture1);
|
||||||
|
await tester.pump();
|
||||||
|
expect(containerFinder.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder1.renderObject!.debugNeedsPaint, true);
|
||||||
|
expect(containerFinder2.renderObject!.debugNeedsPaint, false);
|
||||||
|
|
||||||
|
await tester.tap(gesture1);
|
||||||
|
await tester.pump();
|
||||||
|
expect(containerFinder.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder1.renderObject!.debugNeedsPaint, false);
|
||||||
|
expect(containerFinder2.renderObject!.debugNeedsPaint, false);
|
||||||
|
});
|
||||||
|
testWidgets('has correct element addition handling', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(const example.MyApp());
|
||||||
|
|
||||||
|
expect(find.byType(example.PersonTracker), findsNWidgets(3));
|
||||||
|
final Finder textField = find.byType(TextField);
|
||||||
|
await tester.enterText(textField, 'hello');
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.byType(example.PersonTracker), findsNWidgets(4));
|
||||||
|
|
||||||
|
await tester.enterText(textField, 'hello1');
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.byType(example.PersonTracker), findsNWidgets(5));
|
||||||
|
});
|
||||||
|
testWidgets('has state preservation', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(const example.MyApp());
|
||||||
|
|
||||||
|
final Finder gesture1 = find.byKey(const Key('gesture1'));
|
||||||
|
final Finder gesture2 = find.byKey(const Key('gesture2'));
|
||||||
|
final Finder containerFinder = find.byKey(const Key('Dash'));
|
||||||
|
final Finder incrementFinder = find.byKey(const Key('incrementDash'));
|
||||||
|
Finder counterFinder(int score) {
|
||||||
|
return find.descendant(of: containerFinder, matching: find.text('Score: $score'));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(counterFinder(0), findsOneWidget);
|
||||||
|
await tester.tap(incrementFinder);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(counterFinder(1), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(gesture2);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.tap(gesture1);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(counterFinder(1), findsOneWidget);
|
||||||
|
expect(counterFinder(0), findsNothing);
|
||||||
|
});
|
||||||
|
}
|
@ -3846,6 +3846,13 @@ class Stack extends MultiChildRenderObjectWidget {
|
|||||||
///
|
///
|
||||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=_O0PPD1Xfbk}
|
/// {@youtube 560 315 https://www.youtube.com/watch?v=_O0PPD1Xfbk}
|
||||||
///
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// This example shows a [IndexedStack] widget being used to lay out one card
|
||||||
|
/// at a time from a series of cards, each keeping their respective states.
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/widgets/basic/indexed_stack.0.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [Stack], for more details about stacks.
|
/// * [Stack], for more details about stacks.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user