Skip to content

Commit 0300cfa

Browse files
authored
Create SearchAnchor and SearchViewTheme Widget (flutter#123256)
1 parent 9a7387c commit 0300cfa

File tree

14 files changed

+2535
-51
lines changed

14 files changed

+2535
-51
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import 'package:gen_defaults/popup_menu_template.dart';
4646
import 'package:gen_defaults/progress_indicator_template.dart';
4747
import 'package:gen_defaults/radio_template.dart';
4848
import 'package:gen_defaults/search_bar_template.dart';
49+
import 'package:gen_defaults/search_view_template.dart';
4950
import 'package:gen_defaults/segmented_button_template.dart';
5051
import 'package:gen_defaults/slider_template.dart';
5152
import 'package:gen_defaults/snackbar_template.dart';
@@ -177,6 +178,7 @@ Future<void> main(List<String> args) async {
177178
ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile();
178179
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
179180
SearchBarTemplate('SearchBar', '$materialLib/search_anchor.dart', tokens).updateFile();
181+
SearchViewTemplate('SearchView', '$materialLib/search_anchor.dart', tokens).updateFile();
180182
SegmentedButtonTemplate('md.comp.outlined-segmented-button', 'SegmentedButton', '$materialLib/segmented_button.dart', tokens).updateFile();
181183
SnackbarTemplate('md.comp.snackbar', 'Snackbar', '$materialLib/snack_bar.dart', tokens).updateFile();
182184
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'template.dart';
6+
7+
class SearchViewTemplate extends TokenTemplate {
8+
const SearchViewTemplate(super.blockName, super.fileName, super.tokens, {
9+
super.colorSchemePrefix = '_colors.',
10+
super.textThemePrefix = '_textTheme.'
11+
});
12+
13+
@override
14+
String generate() => '''
15+
class _${blockName}DefaultsM3 extends ${blockName}ThemeData {
16+
_${blockName}DefaultsM3(this.context, {required this.isFullScreen});
17+
18+
final BuildContext context;
19+
final bool isFullScreen;
20+
late final ColorScheme _colors = Theme.of(context).colorScheme;
21+
late final TextTheme _textTheme = Theme.of(context).textTheme;
22+
23+
static double fullScreenBarHeight = ${tokens['md.comp.search-view.full-screen.header.container.height']};
24+
25+
@override
26+
Color? get backgroundColor => ${componentColor('md.comp.search-view.container')};
27+
28+
@override
29+
double? get elevation => ${elevation('md.comp.search-view.container')};
30+
31+
@override
32+
Color? get surfaceTintColor => ${colorOrTransparent('md.comp.search-view.container.surface-tint-layer.color')};
33+
34+
// No default side
35+
36+
@override
37+
OutlinedBorder? get shape => isFullScreen
38+
? ${shape('md.comp.search-view.full-screen.container')}
39+
: ${shape('md.comp.search-view.docked.container')};
40+
41+
@override
42+
TextStyle? get headerTextStyle => ${textStyleWithColor('md.comp.search-view.header.input-text')};
43+
44+
@override
45+
TextStyle? get headerHintStyle => ${textStyleWithColor('md.comp.search-view.header.supporting-text')};
46+
47+
@override
48+
BoxConstraints get constraints => const BoxConstraints(minWidth: 360.0, minHeight: 240.0);
49+
50+
@override
51+
Color? get dividerColor => ${componentColor('md.comp.search-view.divider')};
52+
}
53+
''';
54+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for [SearchAnchor.bar].
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const SearchBarApp());
10+
11+
class SearchBarApp extends StatefulWidget {
12+
const SearchBarApp({super.key});
13+
14+
@override
15+
State<SearchBarApp> createState() => _SearchBarAppState();
16+
}
17+
18+
class _SearchBarAppState extends State<SearchBarApp> {
19+
Color? selectedColorSeed;
20+
List<ColorLabel> searchHistory = <ColorLabel>[];
21+
22+
Iterable<Widget> getHistoryList(SearchController controller) {
23+
return searchHistory.map((ColorLabel color) => ListTile(
24+
leading: const Icon(Icons.history),
25+
title: Text(color.label),
26+
trailing: IconButton(icon: const Icon(Icons.call_missed), onPressed: () {
27+
controller.text = color.label;
28+
controller.selection = TextSelection.collapsed(offset: controller.text.length);
29+
}),
30+
));
31+
}
32+
33+
Iterable<Widget> getSuggestions(SearchController controller) {
34+
final String input = controller.value.text;
35+
return ColorLabel.values.where((ColorLabel color) => color.label.contains(input))
36+
.map((ColorLabel filteredColor) =>
37+
ListTile(
38+
leading: CircleAvatar(backgroundColor: filteredColor.color),
39+
title: Text(filteredColor.label),
40+
trailing: IconButton(icon: const Icon(Icons.call_missed), onPressed: () {
41+
controller.text = filteredColor.label;
42+
controller.selection = TextSelection.collapsed(offset: controller.text.length);
43+
}),
44+
onTap: () {
45+
controller.closeView(filteredColor.label);
46+
handleSelection(filteredColor);
47+
},
48+
));
49+
}
50+
51+
void handleSelection(ColorLabel selectedColor) {
52+
setState(() {
53+
selectedColorSeed = selectedColor.color;
54+
if (searchHistory.length >= 5) {
55+
searchHistory.removeLast();
56+
}
57+
searchHistory.insert(0, selectedColor);
58+
});
59+
}
60+
61+
@override
62+
Widget build(BuildContext context) {
63+
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: selectedColorSeed);
64+
final ColorScheme colors = themeData.colorScheme;
65+
66+
return MaterialApp(
67+
theme: themeData,
68+
home: Scaffold(
69+
appBar: AppBar(title: const Text('Search Bar Sample')),
70+
body: Align(
71+
alignment: Alignment.topCenter,
72+
child: Column(
73+
children: <Widget>[
74+
SearchAnchor.bar(
75+
barHintText: 'Search colors',
76+
suggestionsBuilder: (BuildContext context, SearchController controller) {
77+
if (controller.text.isEmpty) {
78+
if (searchHistory.isNotEmpty) {
79+
return getHistoryList(controller);
80+
}
81+
return <Widget>[ Center(child: Text('No search history.', style: TextStyle(color: colors.outline))) ];
82+
}
83+
return getSuggestions(controller);
84+
},
85+
),
86+
cardSize,
87+
Card(color: colors.primary, child: cardSize),
88+
Card(color: colors.onPrimary, child: cardSize),
89+
Card(color: colors.primaryContainer, child: cardSize),
90+
Card(color: colors.onPrimaryContainer, child: cardSize),
91+
Card(color: colors.secondary, child: cardSize),
92+
Card(color: colors.onSecondary, child: cardSize),
93+
],
94+
),
95+
),
96+
),
97+
);
98+
}
99+
}
100+
101+
SizedBox cardSize = const SizedBox(width: 80, height: 30,);
102+
103+
enum ColorLabel {
104+
red('red', Colors.red),
105+
orange('orange', Colors.orange),
106+
yellow('yellow', Colors.yellow),
107+
green('green', Colors.green),
108+
blue('blue', Colors.blue),
109+
indigo('indigo', Colors.indigo),
110+
violet('violet', Color(0xFF8F00FF)),
111+
purple('purple', Colors.purple),
112+
pink('pink', Colors.pink),
113+
silver('silver', Color(0xFF808080)),
114+
gold('gold', Color(0xFFFFD700)),
115+
beige('beige', Color(0xFFF5F5DC)),
116+
brown('brown', Colors.brown),
117+
grey('grey', Colors.grey),
118+
black('black', Colors.black),
119+
white('white', Colors.white);
120+
121+
const ColorLabel(this.label, this.color);
122+
final String label;
123+
final Color color;
124+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for pinned [SearchAnchor] while scrolling.
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() {
10+
runApp(const PinnedSearchBarApp());
11+
}
12+
13+
class PinnedSearchBarApp extends StatefulWidget {
14+
const PinnedSearchBarApp({super.key});
15+
16+
@override
17+
State<PinnedSearchBarApp> createState() => _PinnedSearchBarAppState();
18+
}
19+
20+
class _PinnedSearchBarAppState extends State<PinnedSearchBarApp> {
21+
@override
22+
Widget build(BuildContext context) {
23+
return MaterialApp(
24+
theme: ThemeData(
25+
useMaterial3: true,
26+
colorSchemeSeed: const Color(0xff6750a4)
27+
),
28+
home: Scaffold(
29+
body: SafeArea(
30+
child: CustomScrollView(
31+
slivers: <Widget>[
32+
SliverAppBar(
33+
clipBehavior: Clip.none,
34+
shape: const StadiumBorder(),
35+
scrolledUnderElevation: 0.0,
36+
titleSpacing: 0.0,
37+
backgroundColor: Colors.transparent,
38+
floating: true, // We can also uncomment this line and set `pinned` to true to see a pinned search bar.
39+
title: SearchAnchor.bar(
40+
suggestionsBuilder: (BuildContext context, SearchController controller) {
41+
return List<Widget>.generate(5, (int index) {
42+
return ListTile(
43+
titleAlignment: ListTileTitleAlignment.center,
44+
title: Text('Initial list item $index'),
45+
);
46+
});
47+
}
48+
),
49+
),
50+
// The listed items below are just for filling the screen
51+
// so we can see the scrolling effect.
52+
SliverToBoxAdapter(
53+
child: Padding(
54+
padding: const EdgeInsets.all(20),
55+
child: SizedBox(
56+
height: 100.0,
57+
child: ListView.builder(
58+
scrollDirection: Axis.horizontal,
59+
itemCount: 10,
60+
itemBuilder: (BuildContext context, int index) {
61+
return SizedBox(
62+
width: 100.0,
63+
child: Card(
64+
child: Center(child: Text('Card $index')),
65+
),
66+
);
67+
},
68+
),
69+
),
70+
),
71+
),
72+
SliverToBoxAdapter(
73+
child: Padding(
74+
padding: const EdgeInsets.symmetric(horizontal: 20),
75+
child: Container(
76+
height: 1000,
77+
color: Colors.deepPurple.withOpacity(0.5),
78+
),
79+
),
80+
),
81+
],
82+
),
83+
),
84+
),
85+
);
86+
}
87+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flutter code sample for [SearchAnchor].
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() => runApp(const SearchBarApp());
10+
11+
class SearchBarApp extends StatefulWidget {
12+
const SearchBarApp({super.key});
13+
14+
@override
15+
State<SearchBarApp> createState() => _SearchBarAppState();
16+
}
17+
18+
class _SearchBarAppState extends State<SearchBarApp> {
19+
final SearchController controller = SearchController();
20+
21+
@override
22+
Widget build(BuildContext context) {
23+
final ThemeData themeData = ThemeData(useMaterial3: true);
24+
25+
return MaterialApp(
26+
theme: themeData,
27+
home: Scaffold(
28+
appBar: AppBar(title: const Text('Search Anchor Sample')),
29+
body: Column(
30+
children: <Widget>[
31+
SearchAnchor(
32+
searchController: controller,
33+
builder: (BuildContext context, SearchController controller) {
34+
return IconButton(
35+
icon: const Icon(Icons.search),
36+
onPressed: () {
37+
controller.openView();
38+
},
39+
);
40+
},
41+
suggestionsBuilder: (BuildContext context, SearchController controller) {
42+
return List<ListTile>.generate(5, (int index) {
43+
final String item = 'item $index';
44+
return ListTile(
45+
title: Text(item),
46+
onTap: () {
47+
setState(() {
48+
controller.closeView(item);
49+
});
50+
},
51+
);
52+
});
53+
}
54+
),
55+
Center(
56+
child: controller.text.isEmpty
57+
? const Text('No item selected')
58+
: Text('Selected item: ${controller.value.text}'),
59+
),
60+
],
61+
),
62+
),
63+
);
64+
}
65+
}

packages/flutter/lib/material.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export 'src/material/scrollbar_theme.dart';
154154
export 'src/material/search.dart';
155155
export 'src/material/search_anchor.dart';
156156
export 'src/material/search_bar_theme.dart';
157+
export 'src/material/search_view_theme.dart';
157158
export 'src/material/segmented_button.dart';
158159
export 'src/material/segmented_button_theme.dart';
159160
export 'src/material/selectable_text.dart';

0 commit comments

Comments
 (0)