In this flutter tutorial we are going to create Flutter Login page user Interface. This Login Page UI contains Textfields for user inputs (User name and Password) and created a button widget with Material and ink widgets for user event on submitting the user credentials.
We are going to create below login page
![]() |
To implement this page we are going to create below pages
App folder structure will be like below
Let's add code to the respected widgets
wave_widget page
import 'package:flutter/material.dart'; import 'dart:math' as Math; import 'package:flutter_login_wave_anim/ui/widgets/clipper_widget.dart'; class WaveWidget extends StatefulWidget { final Size size; final double yOffset; final Color color; WaveWidget({ required this.size, required this.yOffset, required this.color, }); @override _WaveWidgetState createState() => _WaveWidgetState(); } class _WaveWidgetState extends State<WaveWidget> with TickerProviderStateMixin { late AnimationController animationController; List<Offset> wavePoints = []; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 5000)) ..addListener(() { wavePoints.clear(); final double waveSpeed = animationController.value * 1080; final double fullSphere = animationController.value * Math.pi * 2; final double normalizer = Math.cos(fullSphere); final double waveWidth = Math.pi / 270; final double waveHeight = 20.0; for (int i = 0; i <= widget.size.width.toInt(); ++i) { double calc = Math.sin((waveSpeed - i) * waveWidth); wavePoints.add( Offset( i.toDouble(), //X calc * waveHeight * normalizer + widget.yOffset, //Y ), ); } }); animationController.repeat(); } @override void dispose() { animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: animationController, builder: (context, _) { return ClipPath( clipper: ClipperWidget( waveList: wavePoints, ), child: Container( width: widget.size.width, height: widget.size.height, color: widget.color, ), ); }, ); } } |
textfield_widget
import 'package:flutter/material.dart'; import 'package:flutter_login_wave_anim/globals.dart'; import 'package:flutter_login_wave_anim/ui/models/home_model.dart'; import 'package:provider/provider.dart'; class TextFieldWidget extends StatelessWidget { final String hintText; final IconData prefixIconData; final IconData suffixIconData; TextInputAction? inputAction=TextInputAction.next; final bool obscureText; Function(String value)? onChanged; TextFieldWidget({ required this.hintText, required this.prefixIconData, required this.suffixIconData, required this.obscureText, this.onChanged, this.inputAction }); @override Widget build(BuildContext context) { final model = Provider.of<HomeModel>(context); return TextField( textInputAction: inputAction, onChanged: onChanged, obscureText: obscureText, cursorColor: Global.themeColor, style: TextStyle( color: Global.themeColor, fontSize: 14.0, ), decoration: InputDecoration( labelStyle: TextStyle(color: Global.themeColor), focusColor: Global.themeColor, filled: true, enabledBorder: UnderlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Global.themeColor), ), labelText: hintText, prefixIcon: Icon( prefixIconData, size: 18, color: Global.themeColor, ), suffixIcon: GestureDetector( onTap: () { model.isVisible = !model.isVisible; }, child: Icon( suffixIconData, size: 18, color: Global.themeColor, ), ), ), ); } } |
clipper_widget
import 'package:flutter/material.dart'; class ClipperWidget extends CustomClipper<Path> { final List<Offset> waveList; ClipperWidget({required this.waveList}); @override getClip(Size size) { final Path path = Path(); path.addPolygon(waveList, false); path.lineTo(size.width, size.height); path.lineTo(0.0, size.height); path.close(); return path; } @override bool shouldReclip(CustomClipper oldClipper) => true; } |
button_widget
import 'package:flutter/material.dart'; import 'package:flutter_login_wave_anim/globals.dart'; class ButtonWidget extends StatelessWidget { final String title; final bool hasBorder; ButtonWidget({ required this.title, required this.hasBorder, }); @override Widget build(BuildContext context) { return Material( child: Ink( decoration: BoxDecoration( color: hasBorder ? Global.white : Global.themeColor, borderRadius: BorderRadius.circular(10), border: hasBorder ? Border.all( color: Global.themeColor, width: 1.0, ) : Border.fromBorderSide(BorderSide.none), ), child: InkWell( borderRadius: BorderRadius.circular(10), child: Container( height: 60.0, child: Center( child: Text( title, style: TextStyle( color: hasBorder ? Global.themeColor : Global.white, fontWeight: FontWeight.w600, fontSize: 16.0, ), ), ), ), ), ), ); } } |
home_model
import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import '/globals.dart'; class HomeModel extends ChangeNotifier { get isVisible => _isVisible; bool _isVisible = false; set isVisible(value) { _isVisible = value; notifyListeners(); } get isValid => _isValid; bool _isValid = false; void isValidEmail(String input) { if (input == Global.validEmail.first) { _isValid = true; } else { _isValid = false; } notifyListeners(); } } |
home_view
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_login_wave_anim/globals.dart'; import 'package:flutter_login_wave_anim/ui/widgets/button_widget.dart'; import 'package:flutter_login_wave_anim/ui/widgets/textfield_widget.dart'; import 'package:flutter_login_wave_anim/ui/widgets/wave_widget.dart'; import 'package:provider/provider.dart'; import 'models/home_model.dart'; class HomeView extends StatelessWidget { @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final bool keyboardOpen = MediaQuery.of(context).viewInsets.bottom > 0; final model = Provider.of<HomeModel>(context); return Scaffold( backgroundColor: Global.white, body: Stack( children: <Widget>[ Container( height: size.height - 200, color: Global.themeColor, ), AnimatedPositioned( duration: Duration(milliseconds: 500), curve: Curves.easeOutQuad, top: keyboardOpen ? -size.height / 3.7 : 0.0, child: WaveWidget( size: size, yOffset: size.height / 3.0, color: Global.white, ), ), Padding( padding: const EdgeInsets.only(top: 100.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Login', style: TextStyle( color: Global.white, fontSize: 40.0, fontWeight: FontWeight.w900, ), ), ], ), ), Padding( padding: const EdgeInsets.all(30.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ TextFieldWidget( inputAction:TextInputAction.next, hintText: 'Email', obscureText: false, prefixIconData: Icons.mail_outline, suffixIconData: model.isValid ? Icons.check : Icons.yard, onChanged: (value) { model.isValidEmail(value); }, ), SizedBox( height: 10.0, ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: <Widget>[ TextFieldWidget( inputAction:TextInputAction.done, hintText: 'Password', obscureText: model.isVisible ? false : true, prefixIconData: Icons.lock_outline, suffixIconData: model.isVisible ? Icons.visibility : Icons.visibility_off, ), SizedBox( height: 10.0, ), Text( 'Forgot password?', style: TextStyle( color: Global.themeColor, ), ), ], ), SizedBox( height: 20.0, ), ButtonWidget( title: 'Login', hasBorder: false, ), SizedBox( height: 10.0, ), ButtonWidget( title: 'Sign Up', hasBorder: true, ), ], ), ), ], ), ); } } |
global
import 'dart:ui'; import 'package:flutter/material.dart'; class Global { static const Color white = const Color(0xffffffff); static const Color themeColor = Colors.green; static const List validEmail = ['test@gmail.com']; } |
main
import 'package:flutter/material.dart'; import 'package:flutter_login_wave_anim/ui/home_view.dart'; import 'package:flutter_login_wave_anim/ui/models/home_model.dart'; import 'package:provider/provider.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => HomeModel(), child: MaterialApp( debugShowCheckedModeBanner: false, home: HomeView(), ), ); } } |
Conclusion: In this flutter login page example we added waves animation to background page.
Article Contributed By :
|
|
|
|
1570 Views |