How to Create a Custom Login Screen in Flutter: A Complete Tutorial

How to Create a Custom Login Screen in Flutter: A Complete Tutorial

·

6 min read

Table of contents

No heading

No headings in the article.

In this article, I will introduce how to develop a simple login page using the Flutter framework. I hope this article will be helpful for developers who are interested in learning Flutter.

This interface will be divided into three main parts, including the background, input fields, and hint text. The top-level is a class that inherits from StatefulWidget, representing the main page of the application. In the build method, we define a Scaffold widget as the main body of the application, and set a background color and a child widget Container.

Inside the Container, we define a Stack widget to stack multiple child widgets. In the Stack, we define three child widgets:

  1. A Positioned widget, used to display title that changes depending on whether the keyboard is present or not.

  2. An AnimatedPositioned widget, used to display an image that changes depending on whether the keyboard is present or not.

  3. An AnimatedPositioned widget, used to display a Container that contains an email input field, a password input field, and a login button.

Positioned(
                top: keyboardAppeared == false ? 197 : 96,
                left: 32,
                child: AnimatedCrossFade(firstChild: Text(
                    "Enjoy the app",
                    style: TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 24,
                    color: Colors.white)
                ), secondChild: const Text(
                  "Type the info!",
                  style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 24,
                      color: Colors.white),
                ),crossFadeState: keyboardAppeared == true ? CrossFadeState.showSecond : CrossFadeState.showFirst , duration: const Duration(seconds: 1)),
              ),

This code creates a Positioned widget that displays a text. The display position and content of the text change depending on whether the keyboard is present.

Specifically, there are two different contents for the text: one is "Enjoy the app" and the other is "Type the info!". Both texts have the same font size, weight, and color, which are white and bold with a size of 24.

When the keyboard is not present, the top property of the text is set to 197, and the left property is set to 32. Therefore, the text will appear at the top of the screen, with a certain distance from the left of the screen.

When the keyboard is present, the top property of the text is set to 96, and the left property is still set to 32. Therefore, the text will appear in the center of the screen.

Using the AnimatedCrossFade widget, the text will smoothly switch between the two contents, with an animation time of 1 second. The crossFadeState property of the widget changes depending on whether the keyboard is present. When the keyboard is present, crossFadeState is set to CrossFadeState.showSecond, and the text is displayed as "Type the info!". When the keyboard is not present, crossFadeState is set to CrossFadeState.showFirst, and the text is displayed as "Enjoy the app".

AnimatedPositioned(
                bottom: keyboardAppeared ? 0 :-258 ,
                  right: 0,
                  left: 0,
                  duration: const Duration(milliseconds: 500),
                  // height: 170,
                  child: Image.asset(
                "images/rectangle570.png",
                    scale: 0.5,
                )
              ),

This code defines an AnimatedPositioned widget used to display an image. The value of the bottom property changes depending on whether the keyboard is visible or not. When the keyboard is not visible, the value of bottom is -258, which hides the bottom of the image below the screen, displaying only half of it. When the keyboard is visible, the value of bottom is 0, which displays the entire image. The values of right and left properties are both 0, which means the image will completely fill the parent element (i.e. the screen). The duration property defines the transition time from one bottom value to another, which is 500 milliseconds here. The child property defines the image to be displayed, using Image.asset to load a local image file.

AnimatedPositioned(
                duration: const Duration(milliseconds: 500),
                left: (MediaQuery.of(context).size.width - 335) / 2,
                bottom: keyboardAppeared == false ? 102 : 360.5,
                child: Container(
                  width: 335,
                  height: 290,
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: const BorderRadius.only(
                        topLeft: Radius.circular(8),
                        topRight: Radius.circular(8),
                        bottomLeft: Radius.circular(8),
                        bottomRight: Radius.circular(8)),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.grey.withOpacity(0.5),
                        spreadRadius: 8,
                        blurRadius: 7,
                        offset: const Offset(0, 3), // changes position of shadow
                      ),
                    ],
                  ),
                  child: Column(
                    children: [
                      Padding(
                        padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
                        child: TextFormField(
                          keyboardType: TextInputType.emailAddress,
                          smartDashesType: SmartDashesType.disabled,
                          autocorrect: false,
                          enableSuggestions: false,
                          decoration: InputDecoration(
                            hintText: "Email",
                            hintStyle: const TextStyle(color: Color.fromRGBO(170, 174, 179, 1)),

                            filled: true,
                            fillColor: const Color.fromRGBO(242, 243, 245, 1),
                            border: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(4.0),
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
                        child: TextFormField(
                          // key: UniqueKey(),
                          smartDashesType: SmartDashesType.disabled,
                          autocorrect: false,
                          enableSuggestions: false,
                          obscureText: _hidePassword,
                          decoration: InputDecoration(
                            hintText: "Password",
                            hintStyle: const TextStyle(color: Color.fromRGBO(170, 174, 179, 1)),
                            suffixIcon:  IconButton(icon: _hidePassword == true ? const Icon(CupertinoIcons.eye_slash) : const Icon(CupertinoIcons.eye),onPressed: (){
                              setState(() {
                                _hidePassword = !_hidePassword;
                              });
                            }) ,
                            filled: true,
                            fillColor: const Color.fromRGBO(242, 243, 245, 1),
                            border: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(4.0),
                            ),
                          ),
                        ),
                      ),
                      const Spacer(),
                      Row(
                        children: const [
                          Spacer(),
                          Text("Lost your password?",style: TextStyle(fontWeight: FontWeight.w500,decoration: TextDecoration.underline),),
                          SizedBox(width: 20,)
                        ],
                      ),
                      Padding(
                        padding: const EdgeInsets.all(20.0),
                        child: ElevatedButton(onPressed: (){},
                            child: const SizedBox(width:295,height:50, child: Center(child: Text("GO!"))),
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.black
                          )
                        ),
                      ),


                      // SizedBox(height: 32,)
                    ],
                  ),
                ),
              )

This widget defines a Container with input fields, including two TextFormField widgets for email and password input, and an ElevatedButton as a submission button. At the bottom of the Container, there is also a Row containing the text "Lost your password?" to help users recover forgotten passwords.

Furthermore, the AnimatedPositioned widget adjusts its position dynamically based on whether the keyboard appears or not, and is decorated with a shadow and rounded rectangle shape. The width of the widget is 335 pixels, and the height is 290 pixels. When the keyboard appears, the bottom position of the Container moves upward, and otherwise it moves downward. During this process, the position of the Container transitions smoothly, taking 500 milliseconds to complete.

AnimatedPositioned(
                  bottom: keyboardAppeared ? 302 : 54,
                  width: MediaQuery.of(context).size.width, duration: const Duration(milliseconds: 500),
                  child: Align(
                    alignment: Alignment.bottomCenter,
                    child: Row(

                children: [
                    const Spacer(),
                    const Text("Still no account?"),
                    TextButton(onPressed: (){}, child: const Text("register now!",style: TextStyle(color: Color.fromRGBO(255, 140, 50, 1),decoration: TextDecoration.underline),)),
                    const Spacer()
                ],
              ),
                  )
              )

This is a Flutter code that creates an animated container (AnimatedPositioned) with a Row and some text elements inside it. When the code is executed, it changes the bottom position of the container based on the current keyboard state. If the keyboard is displayed (keyboardAppeared is true), the bottom position of the container will be 302, otherwise, it will be 54. Additionally, the container has a width equal to the current screen width and has an animation duration of 500 milliseconds. The elements inside the container include a centered Row with some Spacer and Text elements, and a TextButton that can perform some action (set to do nothing in this code). The text elements "Still no account?" and "Register now!" are styled with specific properties, including text color and underline effect.

Full code and animation video on GitHub:

Through this article, we have learned how to create a simple login screen using Flutter, including an AnimatedPositioned widget that dynamically adjusts its position, and some commonly used UI elements such as TextFormField and ElevatedButton. Additionally, we have introduced some common Flutter techniques and design principles to help readers develop Flutter applications more effectively. I hope this article is helpful to those who are learning Flutter.