Focus with Border - wurzelsand/flutter-memos GitHub Wiki

Focus with Border

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.

Ausführung

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)
        ),
      ),
    ),
  );
}

Anmerkungen

  • 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 a GestureDetector and FocusNode:

    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()],
                ),
              );
            },
          ),
        );
      }
    }
⚠️ **GitHub.com Fallback** ⚠️