Search code examples
flutter

How do I create an L-shaped button in Flutter?


I want make 2 (elevated) buttons, one (backwards) L-shaped, and one a regular rounded text, like this:

L shaped blue button with square red button nestled in the empty space

So far I can think of 3 approaches, but not sure how to implement any of them:

  1. Use clip to clip out the inside of the L button, but I’m not sure how to get the rounded corners on the upper left portions correct; also I’m not sure how to ensure that the hit testing works right so the square button can actually be pressed.
  2. Build the L button from some set of components, say two rounded texts, one horizontal the other vertical, using Stack or something to make them into one Widget, but then how do I make that Stack act like a single button?
  3. Do some kind of CustomPaint, which seems hard to get all the curves right, especially as the buttons need to be dynamically sized, and then also I’d have to re-implement the logic of ElevatedButton to make it pressable, focusable, etc.

How would you lay out two buttons like this?


Solution

  • You can combine Stack and ShapeBorder for L button.

    class LShapeBorder extends OutlinedBorder {
      @override
      OutlinedBorder copyWith({BorderSide? side}) => this;
    
      @override
      ui.Path getInnerPath(ui.Rect rect, {ui.TextDirection? textDirection}) =>
          getOuterPath(rect, textDirection: textDirection);
    
      @override
      ui.Path getOuterPath(ui.Rect rect, {ui.TextDirection? textDirection}) {
        // L shape
        final Path path = Path();
    
        final btnW = rect.width / 2 - 10;
        // path.moveTo(rect.right - btnW, rect.top);
        path.addRRect(
          RRect.fromRectAndRadius(
            Rect.fromLTRB(rect.right - btnW, rect.top, rect.right, rect.bottom),
            const Radius.circular(8),
          ),
        );
    
        path.addRRect(
          RRect.fromRectAndRadius(
            Rect.fromLTRB(rect.left, rect.bottom - btnW, rect.right, rect.bottom),
            const Radius.circular(8),
          ),
        );
    
        path.close();
        return path;
      }
    
      @override
      void paint(ui.Canvas canvas, ui.Rect rect,
          {ui.TextDirection? textDirection}) {}
    
      @override
      ShapeBorder scale(double t) => this;
    }
    

    And usecase

    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: SizedBox(
                height: 200,
                width: 200,
                child: Stack(
                  children: [
                    Align(
                      alignment: Alignment.topLeft,
                      child: ElevatedButton(
                        style: ElevatedButton.styleFrom(
                            fixedSize: Size.square(100),
                            backgroundColor: Colors.red,
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(8),
                            )),
                        onPressed: () {
                          debugPrint('1');
                        },
                        child: SizedBox(),
                      ),
                    ),
                    Positioned.fill(
                      child: ElevatedButton(
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.blue,
                          shape: LShapeBorder(),
                        ),
                        onPressed: () {
                          debugPrint('2');
                        },
                        child: SizedBox(),
                      ),
                    )
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    

    enter image description here