Search code examples
flutterdartuser-interfaceui-design

How to add highlight overlay around the widget upon click?


I'm working on simple design system using Flutter. I want to highlight a widget upon selection (click), as you can see in the below images button get highlighted upon click. It gets handle and border.

Challenging part: I don't want layout getting changed as additional space taken by handle and border upon click. I want widget, handle and border are overlaid, so that it wouldn't shift the position of other neighbouring widgets.

before selection

And after selection

enter image description here


Solution

  • You could also use a Stack with the overlay bleeding out of the Stack thanks to a clipBehavior of Clip.none.

    enter image description here enter image description here

    Full code

    Just copy paste it in a DartPad to see it in action.

    import 'package:flutter/material.dart';
    
    const kcPrimary = Color(0xFF001989);
    const kcSecondary = Color(0xFF239689);
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.light(),
          debugShowCheckedModeBanner: false,
          home: const HomePage(),
        );
      }
    }
    
    class HomePage extends StatelessWidget {
      const HomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: const [
                  Text('Blablablablabla'),
                  Text('Blablablablabla'),
                  Text('Blablablablabla'),
                  PlayButton(),
                  Text('Blablablablabla'),
                  Text('Blablablablabla'),
                  Text('Blablablablabla'),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class PlayButton extends StatefulWidget {
      const PlayButton({Key? key}) : super(key: key);
    
      @override
      State<PlayButton> createState() => _PlayButtonState();
    }
    
    class _PlayButtonState extends State<PlayButton> {
      bool clicked = false;
      @override
      Widget build(BuildContext context) {
        return Stack(
          clipBehavior: Clip.none,
          children: [
            InkWell(
              onTap: () => setState(() => clicked = !clicked),
              child: _mainButton,
            ),
            if (clicked) ...[
              Positioned.fill(
                child: IgnorePointer(
                  child: _overlayBorder,
                ),
              ),
              Positioned(
                top: -20.0,
                left: 0,
                child: _overlayTitle,
              ),
              Positioned(top: 0, right: 0, child: _corner),
              Positioned(bottom: 0, right: 0, child: _corner),
              Positioned(bottom: 0, left: 0, child: _corner),
              Positioned(top: 0, left: 0, child: _corner),
            ],
          ],
        );
      }
    
      Widget get _mainButton => Container(
            width: 80.0,
            height: 40.0,
            decoration: BoxDecoration(
                border: Border.all(
                  color: kcPrimary,
                  width: 3.0,
                ),
                borderRadius: const BorderRadius.all(Radius.circular(12))),
            child: Center(
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: const [
                  Icon(Icons.play_arrow),
                  Text('Play'),
                ],
              ),
            ),
          );
    
      Widget get _overlayBorder => Container(
            decoration: BoxDecoration(
              border: Border.all(
                color: kcSecondary,
                width: 3.0,
              ),
            ),
          );
    
      Widget get _corner => Container(width: 10, height: 10, color: kcSecondary);
    
      Widget get _overlayTitle => Container(
            height: 20.0,
            width: 48.0,
            color: kcSecondary,
            alignment: Alignment.center,
            child: const Text(
              'Button',
              style: TextStyle(
                color: Colors.white,
                fontSize: 10,
                fontWeight: FontWeight.bold,
              ),
            ),
          );
    }