Mẫu màn hình đăng nhập thiết kế bằng Flutter

Sau đây Team Việt Dev tổng hợp bộ sưu tập mẫu màn hình đăng nhập thiết kế bằng Flutter (Login Screen Samples using Flutter) với giao diện đẹp có thể sẽ phù hợp trong các ứng dụng của bạn.

Thông thường khi xây dựng các ứng dụng thì không thể thiếu màn hình đăng nhập, vậy nội dung chính của bài viết này Team Việt Dev nhằm tổng hợp một số mẫu màn hình đăng nhập thiết kế bằng Flutter với giao diện đẹp hy vọng phù hợp trong các ứng dụng của bạn.

Để tìm hiểu thêm về Flutter bạn có thể thao khảo thêm một số bài viết như khởi tạo ứng dụng Flutter đầu tiên, xây dựng ứng dụng Flutter tính chỉ số BMI, xây dựng ứng dụng Calculator bằng Flutter.

Sau đây là một số mẫu màn hình đăng nhập thiết kế bằng Flutter bạn có thể tham khảo:

Mẫu màn hình đăng nhập thiết kế bằng Flutter

Mẫu màn hình đăng nhập thiết kế bằng Flutter thứ tự từ trái sang phải, bạn có thể lựa chọn tải mã nguồn các mẫu trên với mã bên dưới.

Mã nguồn mẫu số 1:

import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => new _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    final logo = Hero(
      tag: 'hero',
      child: CircleAvatar(
        backgroundColor: Colors.transparent,
        radius: 48.0,
        child: Image.asset('assets/lock.png'),
      ),
    );

    final email = TextFormField(
      keyboardType: TextInputType.emailAddress,
      autofocus: false,
      initialValue: '',
      decoration: InputDecoration(
        hintText: 'Enter your email...',
        contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
        border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
      ),
    );

    final password = TextFormField(
      autofocus: false,
      initialValue: '',
      obscureText: true,
      decoration: InputDecoration(
        hintText: 'Enter your password...',
        contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
        border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
      ),
    );

    final loginButton = new RaisedButton(
      child: const Text('Sign In'),
      textColor: Colors.white,
      color: Theme.of(context).accentColor,
      elevation: 10.0,
      splashColor: Colors.blueGrey,
      onPressed: () {
        // Perform some action
      },
    );

    final forgotLabel = FlatButton(
      child: Text(
        'Forgot password?',
        style: TextStyle(color: Colors.black54),
      ),
      onPressed: () {},
    );

    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: ListView(
          shrinkWrap: true,
          padding: EdgeInsets.only(left: 24.0, right: 24.0),
          children: <Widget>[
            logo,
            SizedBox(height: 45.0),
            email,
            SizedBox(height: 10.0),
            password,
            SizedBox(height: 15.0),
            loginButton,
            forgotLabel
          ],
        ),
      ),
    );
  }
}

Mã nguồn mẫu số 2:

import 'package:flutter/material.dart';

class LoginPage2 extends StatelessWidget {
  static String tag = 'login-page';

  final Color backgroundColor1 = Color(0xFF4aa0d5);
  final Color backgroundColor2 = Color(0xFF4aa0d5);
  final Color highlightColor = Color(0xfff65aa3);
  final Color foregroundColor = Colors.white;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Container(
        decoration: new BoxDecoration(
          gradient: new LinearGradient(
            begin: Alignment.centerLeft,
            end: new Alignment(1.0, 0.0),
            // 10% of the width, so there are ten blinds.
            colors: [this.backgroundColor1, this.backgroundColor2],
            // whitish to gray
            tileMode: TileMode.repeated, // repeats the gradient over the canvas
          ),
        ),
        height: MediaQuery.of(context).size.height,
        child: Column(
          children: <Widget>[
            Container(
              padding: const EdgeInsets.only(top: 100.0, bottom: 10.0),
              child: Center(
                child: new Column(
                  children: <Widget>[
                    Container(
                      height: 100.0,
                      width: 100.0,
                      child: new Hero(
                        tag: 'hero',
                        child: CircleAvatar(
                          backgroundColor: Colors.transparent,
                          radius: 48.0,
                          child: Image.asset('assets/lock.png'),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            new Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.only(left: 40.0, right: 40.0),
              alignment: Alignment.center,
              decoration: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                      color: this.foregroundColor,
                      width: 0.5,
                      style: BorderStyle.solid),
                ),
              ),
              padding: const EdgeInsets.only(left: 0.0, right: 10.0),
              child: new Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  new Padding(
                    padding:
                        EdgeInsets.only(top: 20.0, bottom: 10.0, right: 00.0),
                    child: Icon(
                      Icons.alternate_email,
                      color: this.foregroundColor,
                    ),
                  ),
                  new Expanded(
                    child: TextField(
                      textAlign: TextAlign.left,
                      style: new TextStyle(color: Colors.white),
                      decoration: InputDecoration(
                        border: InputBorder.none,
                        hintText: 'Enter your email...',
                        hintStyle: TextStyle(color: this.foregroundColor),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            new Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 10.0),
              alignment: Alignment.center,
              decoration: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                      color: this.foregroundColor,
                      width: 0.5,
                      style: BorderStyle.solid),
                ),
              ),
              padding: const EdgeInsets.only(left: 0.0, right: 10.0),
              child: new Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  new Padding(
                    padding:
                        EdgeInsets.only(top: 10.0, bottom: 10.0, right: 00.0),
                    child: Icon(
                      Icons.lock_open,
                      color: this.foregroundColor,
                    ),
                  ),
                  new Expanded(
                    child: TextField(
                      obscureText: true,
                      textAlign: TextAlign.left,
                      decoration: InputDecoration(
                        border: InputBorder.none,
                        hintText: 'Enter your password...',
                        hintStyle: TextStyle(color: this.foregroundColor),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            new Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 30.0),
              alignment: Alignment.center,
              child: new Row(
                children: <Widget>[
                  new Expanded(
                    child: new FlatButton(
                      padding: const EdgeInsets.symmetric(
                          vertical: 20.0, horizontal: 20.0),
                      color: Colors.lightBlue,
                      child: Text(
                        "Sign In",
                        style: TextStyle(color: this.foregroundColor),
                      ),
                      onPressed: () {
                        // Perform some action
                      },
                    ),
                  ),
                ],
              ),
            ),
            new Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 10.0),
              alignment: Alignment.center,
              child: new Row(
                children: <Widget>[
                  new Expanded(
                    child: new FlatButton(
                      padding: const EdgeInsets.symmetric(
                          vertical: 20.0, horizontal: 20.0),
                      color: Colors.transparent,
                      onPressed: () => {},
                      child: Text(
                        "Forgot your password?",
                        style: TextStyle(color: this.foregroundColor),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            new Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.only(
                  left: 40.0, right: 40.0, top: 10.0, bottom: 20.0),
              alignment: Alignment.center,
              child: new Row(
                children: <Widget>[
                  new Expanded(
                    child: new FlatButton(
                      padding: const EdgeInsets.symmetric(
                          vertical: 20.0, horizontal: 20.0),
                      color: Colors.transparent,
                      onPressed: () => {},
                      child: Text(
                        "Don't have an account? Sign Up",
                        style: TextStyle(color: this.foregroundColor),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Mã nguồn mẫu số 3:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class LoginPage3 extends StatefulWidget {
LoginPage3({Key key}) : super(key: key);
@override
_LoginPageState createState() => new _LoginPageState();
}
class _LoginPageState extends State<LoginPage3>
with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final FocusNode myFocusNodeEmailLogin = FocusNode();
final FocusNode myFocusNodePasswordLogin = FocusNode();
final FocusNode myFocusNodePassword = FocusNode();
final FocusNode myFocusNodeEmail = FocusNode();
final FocusNode myFocusNodeName = FocusNode();
TextEditingController loginEmailController = new TextEditingController();
TextEditingController loginPasswordController = new TextEditingController();
bool _obscureTextLogin = true;
bool _obscureTextSignup = true;
bool _obscureTextSignupConfirm = true;
TextEditingController signupEmailController = new TextEditingController();
TextEditingController signupNameController = new TextEditingController();
TextEditingController signupPasswordController = new TextEditingController();
TextEditingController signupConfirmPasswordController =
new TextEditingController();
PageController _pageController;
Color left = Colors.black;
Color right = Colors.white;
Color loginGradientStart = const Color(0xFFfbab66);
Color loginGradientEnd = const Color(0xFFf7418c);
@override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
body: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (overscroll) {
overscroll.disallowGlow();
},
child: SingleChildScrollView(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height >= 775.0
? MediaQuery.of(context).size.height
: 775.0,
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: [loginGradientStart, loginGradientEnd],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 1.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 75.0),
child: new Image(
width: 100.0,
height: 100.0,
fit: BoxFit.fill,
image: new AssetImage('assets/lock.png')),
),
Padding(
padding: EdgeInsets.only(top: 20.0),
child: _buildMenuBar(context),
),
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
onPageChanged: (i) {
if (i == 0) {
setState(() {
right = Colors.white;
left = Colors.black;
});
} else if (i == 1) {
setState(() {
right = Colors.black;
left = Colors.white;
});
}
},
children: <Widget>[
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: _buildSignIn(context),
),
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: _buildSignUp(context),
),
],
),
),
],
),
),
),
),
);
}
@override
void dispose() {
myFocusNodePassword.dispose();
myFocusNodeEmail.dispose();
myFocusNodeName.dispose();
_pageController?.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
_pageController = PageController();
}
void showInSnackBar(String value) {
FocusScope.of(context).requestFocus(new FocusNode());
_scaffoldKey.currentState?.removeCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text(
value,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
fontFamily: "WorkSansSemiBold"),
),
backgroundColor: Colors.blue,
duration: Duration(seconds: 3),
));
}
Widget _buildMenuBar(BuildContext context) {
return Container(
width: 300.0,
height: 50.0,
decoration: BoxDecoration(
color: Color(0x552B2B2B),
borderRadius: BorderRadius.all(Radius.circular(25.0)),
),
child: CustomPaint(
painter: TabIndicationPainter(pageController: _pageController),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: _onSignInButtonPress,
child: Text(
"Sign In",
style: TextStyle(
color: left,
fontSize: 16.0,
fontFamily: "WorkSansSemiBold"),
),
),
),
//Container(height: 33.0, width: 1.0, color: Colors.white),
Expanded(
child: FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: _onSignUpButtonPress,
child: Text(
"Sign Up",
style: TextStyle(
color: right,
fontSize: 16.0,
fontFamily: "WorkSansSemiBold"),
),
),
),
],
),
),
);
}
Widget _buildSignIn(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 23.0),
child: Column(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
overflow: Overflow.visible,
children: <Widget>[
Card(
elevation: 2.0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: Container(
width: 300.0,
height: 190.0,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: 20.0, bottom: 20.0, left: 25.0, right: 25.0),
child: TextField(
focusNode: myFocusNodeEmailLogin,
controller: loginEmailController,
keyboardType: TextInputType.emailAddress,
style: TextStyle(
fontFamily: "WorkSansSemiBold",
fontSize: 16.0,
color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(
FontAwesomeIcons.envelope,
color: Colors.black,
size: 22.0,
),
hintText: "Email address...",
hintStyle: TextStyle(
fontFamily: "WorkSansSemiBold", fontSize: 17.0),
),
),
),
Container(
width: 250.0,
height: 1.0,
color: Colors.grey[400],
),
Padding(
padding: EdgeInsets.only(
top: 20.0, bottom: 20.0, left: 25.0, right: 25.0),
child: TextField(
focusNode: myFocusNodePasswordLogin,
controller: loginPasswordController,
obscureText: _obscureTextLogin,
style: TextStyle(
fontFamily: "WorkSansSemiBold",
fontSize: 16.0,
color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(
FontAwesomeIcons.lock,
size: 22.0,
color: Colors.black,
),
hintText: "Password...",
hintStyle: TextStyle(
fontFamily: "WorkSansSemiBold", fontSize: 17.0),
suffixIcon: GestureDetector(
onTap: _toggleLogin,
child: Icon(
FontAwesomeIcons.eye,
size: 15.0,
color: Colors.black,
),
),
),
),
),
],
),
),
),
Container(
margin: EdgeInsets.only(top: 170.0),
decoration: new BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
boxShadow: <BoxShadow>[
BoxShadow(
color: loginGradientStart,
offset: Offset(1.0, 6.0),
blurRadius: 20.0,
),
BoxShadow(
color: loginGradientEnd,
offset: Offset(1.0, 6.0),
blurRadius: 20.0,
),
],
gradient: new LinearGradient(
colors: [loginGradientEnd, loginGradientStart],
begin: const FractionalOffset(0.2, 0.2),
end: const FractionalOffset(1.0, 1.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
child: MaterialButton(
highlightColor: Colors.transparent,
splashColor: loginGradientEnd,
//shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 42.0),
child: Text(
"SIGN IN",
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
fontFamily: "WorkSansBold"),
),
),
onPressed: () => showInSnackBar("Login button pressed")),
),
],
),
Padding(
padding: EdgeInsets.only(top: 10.0),
child: FlatButton(
onPressed: () {},
child: Text(
"Forgot Password?",
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
fontSize: 16.0,
fontFamily: "WorkSansMedium"),
)),
),
Padding(
padding: EdgeInsets.only(top: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: new LinearGradient(
colors: [
Colors.white10,
Colors.white,
],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 1.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
width: 100.0,
height: 1.0,
),
Padding(
padding: EdgeInsets.only(left: 15.0, right: 15.0),
child: Text(
"Or",
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
fontFamily: "WorkSansMedium"),
),
),
Container(
decoration: BoxDecoration(
gradient: new LinearGradient(
colors: [
Colors.white,
Colors.white10,
],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(1.0, 1.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
width: 100.0,
height: 1.0,
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 10.0, right: 40.0),
child: GestureDetector(
onTap: () => showInSnackBar("Facebook button pressed"),
child: Container(
padding: const EdgeInsets.all(15.0),
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: new Icon(
FontAwesomeIcons.facebookF,
color: Color(0xFF0084ff),
),
),
),
),
Padding(
padding: EdgeInsets.only(top: 10.0),
child: GestureDetector(
onTap: () => showInSnackBar("Google button pressed"),
child: Container(
padding: const EdgeInsets.all(15.0),
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: new Icon(
FontAwesomeIcons.google,
color: Color(0xFF0084ff),
),
),
),
),
],
),
],
),
);
}
Widget _buildSignUp(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 23.0),
child: Column(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
overflow: Overflow.visible,
children: <Widget>[
Card(
elevation: 2.0,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: Container(
width: 300.0,
height: 260.0,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: 20.0, bottom: 20.0, left: 25.0, right: 25.0),
child: TextField(
focusNode: myFocusNodeName,
controller: signupNameController,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.words,
style: TextStyle(
fontFamily: "WorkSansSemiBold",
fontSize: 16.0,
color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(
FontAwesomeIcons.user,
color: Colors.black,
),
hintText: "Fullname...",
hintStyle: TextStyle(
fontFamily: "WorkSansSemiBold", fontSize: 16.0),
),
),
),
Container(
width: 250.0,
height: 1.0,
color: Colors.grey[400],
),
Padding(
padding: EdgeInsets.only(
top: 20.0, bottom: 20.0, left: 25.0, right: 25.0),
child: TextField(
focusNode: myFocusNodeEmail,
controller: signupEmailController,
keyboardType: TextInputType.emailAddress,
style: TextStyle(
fontFamily: "WorkSansSemiBold",
fontSize: 16.0,
color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(
FontAwesomeIcons.envelope,
color: Colors.black,
),
hintText: "Email address...",
hintStyle: TextStyle(
fontFamily: "WorkSansSemiBold", fontSize: 16.0),
),
),
),
Container(
width: 250.0,
height: 1.0,
color: Colors.grey[400],
),
Padding(
padding: EdgeInsets.only(
top: 20.0, bottom: 20.0, left: 25.0, right: 25.0),
child: TextField(
focusNode: myFocusNodePassword,
controller: signupPasswordController,
obscureText: _obscureTextSignup,
style: TextStyle(
fontFamily: "WorkSansSemiBold",
fontSize: 16.0,
color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(
FontAwesomeIcons.lock,
color: Colors.black,
),
hintText: "Password...",
hintStyle: TextStyle(
fontFamily: "WorkSansSemiBold", fontSize: 16.0),
suffixIcon: GestureDetector(
onTap: _toggleSignup,
child: Icon(
FontAwesomeIcons.eye,
size: 15.0,
color: Colors.black,
),
),
),
),
),
],
),
),
),
Container(
margin: EdgeInsets.only(top: 240.0),
decoration: new BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
boxShadow: <BoxShadow>[
BoxShadow(
color: loginGradientStart,
offset: Offset(1.0, 6.0),
blurRadius: 20.0,
),
BoxShadow(
color: loginGradientEnd,
offset: Offset(1.0, 6.0),
blurRadius: 20.0,
),
],
gradient: new LinearGradient(
colors: [loginGradientEnd, loginGradientStart],
begin: const FractionalOffset(0.2, 0.2),
end: const FractionalOffset(1.0, 1.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp),
),
child: MaterialButton(
highlightColor: Colors.transparent,
splashColor: loginGradientEnd,
//shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 42.0),
child: Text(
"SIGN UP",
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
fontFamily: "WorkSansBold"),
),
),
onPressed: () => showInSnackBar("SignUp button pressed")),
),
],
),
],
),
);
}
void _onSignInButtonPress() {
_pageController.animateToPage(0,
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
}
void _onSignUpButtonPress() {
_pageController?.animateToPage(1,
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
}
void _toggleLogin() {
setState(() {
_obscureTextLogin = !_obscureTextLogin;
});
}
void _toggleSignup() {
setState(() {
_obscureTextSignup = !_obscureTextSignup;
});
}
void _toggleSignupConfirm() {
setState(() {
_obscureTextSignupConfirm = !_obscureTextSignupConfirm;
});
}
}
class TabIndicationPainter extends CustomPainter {
Paint painter = new Paint();
final double dxTarget = 125.0;
final double dxEntry = 25.0;
final double radius = 21.0;
final double dy = 25.0;
final double pi = 3.14;
final PageController pageController;
TabIndicationPainter({this.pageController}) : super(repaint: pageController) {
painter = new Paint()
..color = Color(0xFFFFFFFF)
..style = PaintingStyle.fill;
}
@override
void paint(Canvas canvas, Size size) {
final pos = pageController.position;
double fullExtent =
(pos.maxScrollExtent - pos.minScrollExtent + pos.viewportDimension);
double pageOffset = pos.extentBefore / fullExtent;
bool left2right = dxEntry < dxTarget;
Offset entry = new Offset(left2right ? dxEntry : dxTarget, dy);
Offset target = new Offset(left2right ? dxTarget : dxEntry, dy);
Path path = new Path();
path.addArc(
new Rect.fromCircle(center: entry, radius: radius), 0.5 * pi, 1 * pi);
path.addRect(
new Rect.fromLTRB(entry.dx, dy - radius, target.dx, dy + radius));
path.addArc(
new Rect.fromCircle(center: target, radius: radius), 1.5 * pi, 1 * pi);
canvas.translate(size.width * pageOffset, 0.0);
canvas.drawShadow(path, Color(0xFFfbab66), 3.0, true);
canvas.drawPath(path, painter);
}
@override
bool shouldRepaint(TabIndicationPainter oldDelegate) => true;
}

Lời kết: Trong thời gian tới Team Việt Dev sẽ tiếp tục chia sẻ thêm nhiều bài viết về xây dựng ứng dụng di động trên Android và iOS bằng Flutter miễn phí đến bạn đọc, các bạn nhớ theo dõi kênh để có được những chia sẻ mới nhất.

(Tác giả: Team Việt Dev)

Bình luận