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
5import 'package:flutter/material.dart';
6
7/// Flutter code sample for [ListenableBuilder].
8
9void main() => runApp(const ListenableBuilderExample());
10
11/// This widget listens for changes in the focus state of the subtree defined by
12/// its [child] widget, changing the border and color of the container it is in
13/// when it has focus.
14///
15/// A [FocusListenerContainer] swaps out the [BorderSide] of a border around the
16/// child widget with [focusedSide], and the background color with
17/// [focusedColor], when a widget that is a descendant of this widget has focus.
18class FocusListenerContainer extends StatefulWidget {
19 const FocusListenerContainer({
20 super.key,
21 this.border,
22 this.padding,
23 this.focusedSide,
24 this.focusedColor = Colors.black12,
25 required this.child,
26 });
27
28 /// This is the border that will be used when not focused, and which defines
29 /// all the attributes except for the [OutlinedBorder.side] when focused.
30 final OutlinedBorder? border;
31
32 /// This is the [BorderSide] that will be used for [border] when the [child]
33 /// subtree is focused.
34 final BorderSide? focusedSide;
35
36 /// This is the [Color] that will be used as the fill color for the background
37 /// of the [child] when a descendant widget is focused.
38 final Color? focusedColor;
39
40 /// The padding around the inside of the container.
41 final EdgeInsetsGeometry? padding;
42
43 /// This is defines the subtree to listen to for focus changes.
44 final Widget child;
45
46 @override
47 State<FocusListenerContainer> createState() => _FocusListenerContainerState();
48}
49
50class _FocusListenerContainerState extends State<FocusListenerContainer> {
51 final FocusNode _focusNode = FocusNode();
52
53 @override
54 void dispose() {
55 _focusNode.dispose();
56 super.dispose();
57 }
58
59 @override
60 Widget build(BuildContext context) {
61 final OutlinedBorder effectiveBorder = widget.border ?? const RoundedRectangleBorder();
62 return ListenableBuilder(
63 listenable: _focusNode,
64 child: Focus(
65 focusNode: _focusNode,
66 skipTraversal: true,
67 canRequestFocus: false,
68 child: widget.child,
69 ),
70 builder: (BuildContext context, Widget? child) {
71 return Container(
72 padding: widget.padding,
73 decoration: ShapeDecoration(
74 color: _focusNode.hasFocus ? widget.focusedColor : null,
75 shape: effectiveBorder.copyWith(side: _focusNode.hasFocus ? widget.focusedSide : null),
76 ),
77 child: child,
78 );
79 },
80 );
81 }
82}
83
84class MyField extends StatefulWidget {
85 const MyField({super.key, required this.label});
86
87 final String label;
88
89 @override
90 State<MyField> createState() => _MyFieldState();
91}
92
93class _MyFieldState extends State<MyField> {
94 final TextEditingController controller = TextEditingController();
95
96 @override
97 Widget build(BuildContext context) {
98 return Row(
99 children: <Widget>[
100 Expanded(child: Text(widget.label)),
101 Expanded(
102 flex: 2,
103 child: TextField(
104 controller: controller,
105 onEditingComplete: () {
106 debugPrint('Field ${widget.label} changed to ${controller.value}');
107 },
108 ),
109 ),
110 ],
111 );
112 }
113}
114
115class ListenableBuilderExample extends StatelessWidget {
116 const ListenableBuilderExample({super.key});
117
118 @override
119 Widget build(BuildContext context) {
120 return MaterialApp(
121 home: Scaffold(
122 appBar: AppBar(title: const Text('ListenableBuilder Example')),
123 body: Center(
124 child: SizedBox(
125 width: 300,
126 child: Padding(
127 padding: const EdgeInsets.all(8.0),
128 child: Column(
129 mainAxisAlignment: MainAxisAlignment.center,
130 children: <Widget>[
131 const Padding(
132 padding: EdgeInsets.only(bottom: 8),
133 child: MyField(label: 'Company'),
134 ),
135 FocusListenerContainer(
136 padding: const EdgeInsets.all(8),
137 border: const RoundedRectangleBorder(
138 side: BorderSide(strokeAlign: BorderSide.strokeAlignOutside),
139 borderRadius: BorderRadius.all(Radius.circular(5)),
140 ),
141 // The border side will get wider when the subtree has focus.
142 focusedSide: const BorderSide(
143 width: 4,
144 strokeAlign: BorderSide.strokeAlignOutside,
145 ),
146 // The container background will change color to this when
147 // the subtree has focus.
148 focusedColor: Colors.blue.shade50,
149 child: const Column(
150 crossAxisAlignment: CrossAxisAlignment.start,
151 children: <Widget>[
152 Text('Owner:'),
153 MyField(label: 'First Name'),
154 MyField(label: 'Last Name'),
155 ],
156 ),
157 ),
158 ],
159 ),
160 ),
161 ),
162 ),
163 ),
164 );
165 }
166}
167