Search code examples
flutterlayoutclipperflutter-clippath

How to use ClipPath to get this type of design layout?


I have been scratching my head for a very long time just to get this type of design layout for a TextFormField but failed!

TextFormField

Notice the anti strait lines. My further search for this layout on the internet concludes that I can archive this type of layout using a custom clipper or clip-path. but I am just a beginner to learn flutter and never worked with custom clipper before, I have no idea how to use it. Please help.


Solution

  • I think you can use a CustomPainter :

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(
            scaffoldBackgroundColor: darkBlue,
          ),
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            body: Center(
              child: CustomPaint(
                painter: MyPainter(),
                child: // your TextField with style so we don't see the borders etc...
              ),
            ),
          ),
        );
      }
    }
    
    class MyPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
          // draw your rectangle from points here
      }
    
      // no need to repaint it because your shape is static
      @override
      bool shouldRepaint(Sky oldDelegate) => false;
      @override
      bool shouldRebuildSemantics(Sky oldDelegate) => false;
    }
    

    Here is the link for the basics of CustomPainter : link


    UPDATE :

    Here is the written code with the widget displaying the TextField and of course your custom shape :

    class MainScreen extends StatefulWidget {
      @override
      _MainScreenState createState() => _MainScreenState();
    }
    
    class _MainScreenState extends State<MainScreen> {
      // implementation of your custom active borders
      bool emailActive = false;
      FocusNode? emailNode;
    
      @override
      void initState() {
        super.initState();
        emailNode = FocusScopeNode()
          ..addListener(() {
            setState(() {
              emailActive = emailNode!.hasFocus;
            });
            print(emailActive);
          });
      }
    
      @override
      Widget build(BuildContext context) {
        final Size size = MediaQuery.of(context).size;
        final double width = size.width * 0.8;
        final double height = width * 0.15;
    
        return Scaffold(
          appBar: AppBar(
            title: Text('First screen'),
          ),
          body: Center(
            child: Container(
              height: height,
              width: width,
              child: Stack(
                children: [
                  Center(
                    // your shape
                    child: CustomPaint(
                      painter: _TextFieldPainter(width, height, emailActive),
                    ),
                  ),
                  Container(
                    height: height,
                    width: width,
                    padding: const EdgeInsets.symmetric(horizontal: 10.0),
                    child: Row(
                      children: [
                        Container(
                          width: 35,
                          padding: const EdgeInsets.only(
                              top: 5.0), // so it's aligned with the textfield
                          child: Icon(Icons.email_rounded),
                        ),
                        Container(
                          width: width - 35 - 20, // width - iconSize - padding
                          child: TextField(
                            decoration: InputDecoration(
                              hintText: 'Email id',
                              focusedBorder: InputBorder
                                  .none, // so there is no border below the text field
                            ),
                            focusNode: emailNode,
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class _TextFieldPainter extends CustomPainter {
      final double width;
      final double height;
      final bool active;
    
      _TextFieldPainter(this.width, this.height, this.active);
    
      @override
      void paint(Canvas canvas, Size size) {
        // The starting values so it's centered properly
        final double startX = width / -2;
        final double startY = height / -2;
    
        // Where you set the color etc...
        final Paint paint = Paint()
          ..color = active
              ? Colors.green
              : Colors.black // where you change the color wether it's active or not
          ..strokeWidth = 1.0;
    
        // Drawing the shape
        canvas.drawLine(
            Offset(startX, startY + 5.0), Offset(startX + width, startY), paint);
        canvas.drawLine(Offset(startX + width, startY),
            Offset(startX + width, startY + height), paint);
        canvas.drawLine(Offset(startX + width, startY + height),
            Offset(startX - 5.0, startY + height), paint);
        canvas.drawLine(Offset(startX - 5.0, startY + height),
            Offset(startX, startY + 5.0), paint);
      }
    
      // no need to repaint it here since there is no dynamic values
      @override
      bool shouldRepaint(_TextFieldPainter oldDelegate) => false;
    
      @override
      bool shouldRebuildSemantics(_TextFieldPainter oldDelegate) => false;
    }
    

    You just have to do the same for the password TextField.

    And here is how it looks :

    The requested result