Focus with Border - wurzelsand/flutter-memos GitHub Wiki
A desktop app is to be created. The tab keys are used to switch the focus between the three containers. With return or space the container can be activated, i.e. its number appears below as text.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: ExampleWidget(),
)
);
}
}
class ExampleWidget extends StatefulWidget {
const ExampleWidget({super.key});
@override
State createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
String activated = '';
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FocusBorder(
child: Container(
color: Colors.red,
width: 300,
height: 100,
child: const Text('Container 1')
),
onActivated: () => setState(() => activated = '1')
),
FocusBorder(
child: Container(
color: Colors.green,
width: 300,
height: 100,
child: const Text('Container 2')
),
onActivated: () => setState(() => activated = '2')
),
FocusBorder(
child: Container(
color: Colors.blue,
width: 300,
height: 100,
child: const Text('Container 3')
),
onActivated: () => setState(() => activated = '3')
),
Text('Activated: $activated'),
],
),
);
}
}
class FocusBorder extends StatefulWidget {
const FocusBorder({required this.child, required this.onActivated, super.key});
final Widget child;
final VoidCallback onActivated;
@override
State<StatefulWidget> createState() => _FocusBorderState();
}
class _FocusBorderState extends State<FocusBorder> {
bool _hasFocus = false;
@override
Widget build(BuildContext context) {
return FocusableActionDetector(
onFocusChange: (focus) {
setState(() {
_hasFocus = focus;
});
},
actions: <Type, Action<Intent>>{
ActivateIntent: CallbackAction<Intent>(onInvoke:
(intent) => widget.onActivated()),
},
child: Stack(
clipBehavior: Clip.none,
children: [
widget.child,
if (_hasFocus) _justBorders()
],
),
);
}
}
Positioned _justBorders({Color color = Colors.black}) {
return Positioned(
top: 0,
left: 0,
bottom: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: color),
left: BorderSide(color: color),
bottom: BorderSide(color: color),
right: BorderSide(color: color)
),
),
),
);
}
-
FocusableActionDetector
only registers focus and activation by keyboard. With mouse clicks or touches the widgets cannot be activated this way. But you can add this functionality with aGestureDetector
andFocusNode
:class _FocusBorderState extends State<FocusBorder> { bool _hasFocus = false; late FocusNode focusNode; @override void initState() { super.initState(); focusNode = FocusNode(); } @override void dispose() { focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return FocusableActionDetector( focusNode: focusNode, onFocusChange: (hasFocus) => setState(() => _hasFocus = hasFocus), actions: <Type, Action<Intent>>{ ActivateIntent: CallbackAction<ActivateIntent>( onInvoke: (intent) => widget.onActivated(), ), }, child: Builder( builder: (context) { // context of FocusableActionDetector return GestureDetector( onTap: () { Actions.invoke(context, const ActivateIntent()); focusNode.requestFocus(); }, child: Stack( clipBehavior: Clip.none, children: [widget.child, if (_hasFocus) _justBorders()], ), ); }, ), ); } }