Flutter基础 -- Flutter布局练习(小项目)

目录

[1. Splash 布局(第一页)](#1. Splash 布局(第一页))

[1.1 目标](#1.1 目标)

[1.2 当前效果图](#1.2 当前效果图)

[1.3 创建 Splash 界面](#1.3 创建 Splash 界面)

[1.4 设置 MaterialApp](#1.4 设置 MaterialApp)

[1.5 设置 Splash 背景色](#1.5 设置 Splash 背景色)

[1.6 布局 Splash 界面](#1.6 布局 Splash 界面)

[1.7 总结](#1.7 总结)

[2. Splash 圆角图片](#2. Splash 圆角图片)

[2.1 目标](#2.1 目标)

[2.2 当前效果图](#2.2 当前效果图)

[2.3 蓝湖下载图片](#2.3 蓝湖下载图片)

[2.4 图片导入项目](#2.4 图片导入项目)

[2.5 编写 assets 索引](#2.5 编写 assets 索引)

[2.6 编写标志 Logo](#2.6 编写标志 Logo)

[2.7 总结](#2.7 总结)

[3. Splash 文字](#3. Splash 文字)

[3.1 目标](#3.1 目标)

[3.2 当前效果图](#3.2 当前效果图)

[3.3 蓝湖标注查看](#3.3 蓝湖标注查看)

[3.4 字体下载导入](#3.4 字体下载导入)

[3.5 编写 TextStyle](#3.5 编写 TextStyle)

[3.6 加入间距](#3.6 加入间距)

[3.7 总结](#3.7 总结)

[4. Splash 倒计时](#4. Splash 倒计时)

[4.1 目标](#4.1 目标)

[4.2 当前效果图](#4.2 当前效果图)

[4.3 改成有状态组件](#4.3 改成有状态组件)

[4.4 实现倒计时](#4.4 实现倒计时)

[4.5 重构文字显示函数](#4.5 重构文字显示函数)

[4.6 完整代码](#4.6 完整代码)

[4.7 总结](#4.7 总结)

[1. Welcome 图片尺寸适应(第二页)](#1. Welcome 图片尺寸适应(第二页))

[1.1 目标](#1.1 目标)

[1.2 当前效果图](#1.2 当前效果图)

[1.3 设置全局字体](#1.3 设置全局字体)

[1.4 界面布局、标题](#1.4 界面布局、标题)

[1.5 图片尺寸适应](#1.5 图片尺寸适应)

[1.6 总结](#1.6 总结)

[2. Welcome 导航切换](#2. Welcome 导航切换)

[2.1 目标](#2.1 目标)

[2.2 当前效果图](#2.2 当前效果图)

[2.3 底部按钮](#2.3 底部按钮)

[2.4 导航切换](#2.4 导航切换)

[2.5 总结](#2.5 总结)

[1. login 布局(第三页)](#1. login 布局(第三页))

[1.1 目标](#1.1 目标)

[1.2 布局](#1.2 布局)

[1.3 总结](#1.3 总结)

[2. login 登录表单](#2. login 登录表单)

[2.1 目标](#2.1 目标)

[2.2 当前效果图](#2.2 当前效果图)

[2.3 登录表单](#2.3 登录表单)

[2.4 总结](#2.4 总结)

[3. login 按钮抽取](#3. login 按钮抽取)

[3.1 目标](#3.1 目标)

[3.2 当前效果图](#3.2 当前效果图)

[3.3 按钮组件抽取](#3.3 按钮组件抽取)

[3.4 登录按钮](#3.4 登录按钮)

[3.5 欢迎按钮](#3.5 欢迎按钮)

[3.6 总结](#3.6 总结)


**博主wx:**yuanlai45_csdn **博主qq:**2777137742

后期会创建粉丝群,为同学们提供分享交流平台以及提供官方发送的福利奖品~

1. Splash 布局(第一页)

1.1 目标

  • 查看蓝湖标注
  • 初始项目
  • 创建 splash 界面

github 目标图片在里面

https://github.com/ducafecat/flutter_quickstart_learn

1.2 当前效果图

1.3 创建 Splash 界面

lib/pages/splash.dart

Dart 复制代码
import 'package:flutter/material.dart';

import '../common/index.dart';

class SplashPage extends StatelessWidget {
  const SplashPage({Key? key}) : super(key: key);

  // 主视图
  Widget _buildView(BuildContext context) {
    return Text("splash");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: _buildView(context)),
    );
  }
}

1.4 设置 MaterialApp

lib/main.dart

Dart 复制代码
import 'package:flutter/material.dart';

import 'pages/splash.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Quick Start',

      // 首页
      home: const SplashPage(),

      // 关闭 debug 标签
      debugShowCheckedModeBanner: false,
    );
  }
}

1.5 设置 Splash 背景色

lib/common/app_colors.dart

Dart 复制代码
import 'package:flutter/material.dart';

/// 颜色配置
class AppColors {
  /// splash 背景色
  static const Color backgroundSplash = Color(0xff0274bc);
}

lib/pages/splash.dart

Dart 复制代码
import '../common/index.dart';

...

	@override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.backgroundSplash, // 背景色
      body: _buildView(context),
    );
  }

1.6 布局 Splash 界面

lib/pages/splash.dart

Dart 复制代码
  // 主视图
  Widget _buildView(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 图标
        Container(
          color: Colors.white,
          width: 120,
          height: 120,
        ),
        // 标题
        const Text("Online Market"),
        // 倒计时
        const Text("10"),
      ],
    );
  }

1.7 总结

  • 蓝湖标注平台 布局、尺寸、字体、颜色...

  • 初始项目 pages common 目录创建

  • 配置 MaterialApp.home 首页

  • 配置 Scaffold 脚手架背景色

  • 全局颜色管理 AppColors

  • 布局 Splash 界面

2. Splash 圆角图片

2.1 目标

  • 导入图片资源
  • 使用层叠布局编写 logo

2.2 当前效果图

2.3 蓝湖下载图片

依次选中图片,选中 PNG 格式,IOS 类型,3X 高清,最后下载当前切图

2.4 图片导入项目

放入你的项目 assets/images/3.0x/logo.pn

生成 1x 2x 图片

插件地址 Flutter GetX Generator - 猫哥 - Visual Studio Marketplace

修改 pubspec.yaml

Dart 复制代码
flutter:
  ...
  
  assets:
    - assets/images/

2.5 编写 assets 索引

编写 lib/common/assets.dart, 将 assets/images/files.txt 内容复制进去,这个文件是插件生成的,防止文件太多,手写出错

Dart 复制代码
/// 图片资源
class AssetsImages {
  static const logoPng = 'assets/images/logo.png';
}

lib/pages/splash.dart

Dart 复制代码
  // 图标
  Widget _buildLogo() {
    return Stack(
      alignment: Alignment.center,
      children: [
        // 底部
        Container(
          width: 120,
          height: 120,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(120 / 2),
          ),
        ),
        // 图标
        Image.asset(
          AssetsImages.logoPng,
          width: 84,
          height: 80,
        ),
      ],
    );
  }
Dart 复制代码
  // 主视图
  Widget _buildView(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 图标
        _buildLogo(),
        // 标题
        const Text("Online Market"),
        // 倒计时
        const Text("10"),
      ],
    );
  }

2.7 总结

  • 下载蓝湖切图 选择 png 格式 ios 模式 3x 尺寸
  • 使用猫哥插件生成 1x 2x 图片,创建资源索引管理
  • 使用层叠布局创建 logo
  • 使用函数进一步的管理代码

3. Splash 文字

3.1 目标

  • 导入字体文件
  • 配置文字样式

3.2 当前效果图

3.3 蓝湖标注查看

需要关注的属性有:

  • font-size 字体大小
  • font-family 字体名称
  • font-weight 字体重度
  • color 颜色
  • line-height 行高

3.4 字体下载导入

google 字体下载

https://fonts.google.com/

下载后导入 assets/fonts/

编辑 pubspec.yaml

Dart 复制代码
  fonts:
    - family: Poppins
      fonts:
        - asset: assets/fonts/Poppins-Light.ttf
          weight: 300
        - asset: assets/fonts/Poppins-Regular.ttf
          weight: 400
        - asset: assets/fonts/Poppins-Medium.ttf
          weight: 500
        - asset: assets/fonts/Poppins-Bold.ttf
          weight: 700

3.5 编写 TextStyle

lib/pages/splash.dart

Dart 复制代码
  // 标题
  const Text(
    "Online Market",
    style: TextStyle(
      fontSize: 19,
      fontFamily: "Poppins",
      fontWeight: FontWeight.bold,
      color: Colors.white,
      height: 22 / 19,
    ),
  ),

fontFamily 中写入字体名称 Poppins

height 文本跨度的行高将为 [fontSize] 的倍数并且正好是 fontSize *height 逻辑像素 高。换行的时候才有意义

3.6 加入间距

标题和图标间距 24,和计数器 27

lib/pages/splash.dart

Dart 复制代码
  // 主视图
  Widget _buildView(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 图标
        _buildLogo(),

        const SizedBox(height: 24),

        // 标题
        const Text(
          "Online Market",
          style: TextStyle(
            fontSize: 19,
            fontFamily: "Poppins",
            fontWeight: FontWeight.bold,
            color: Colors.white,
            height: 22 / 19,
          ),
        ),

        const SizedBox(height: 27),

        // 倒计时
        const Text(
          "10",
          style: TextStyle(
            fontSize: 19,
            fontFamily: "Poppins",
            fontWeight: FontWeight.bold,
            color: Colors.white,
            height: 22 / 19,
          ),
        ),
      ],
    );
  }

3.7 总结

  • 导入字体 pubspec 中详细明确 字体名称字体文件字体weight
  • 用不到的字体文件不用方式 assets/fonts 目录中
  • 设置文字样式 fontSizefontFamilyfontWeightcolor
  • 具体值的间距用 SizedBox 来配置

4. Splash 倒计时

4.1 目标

  • 使用有状态组件
  • 倒计时更新组件

4.2 当前效果图

4.3 改成有状态组件

lib/pages/splash.dart

Dart 复制代码
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../common/index.dart';

class SplashPage extends StatefulWidget {
  const SplashPage({Key? key}) : super(key: key);

  @override
  State<SplashPage> createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  // 图标
  Widget _buildLogo() {
    return Stack(
      alignment: Alignment.center,
      children: [
        // 底部
        Container(
          width: 120,
          height: 120,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(120 / 2),
          ),
        ),
        // 图标
        Image.asset(
          AssetsImages.logoPng,
          width: 84,
          height: 80,
        ),
      ],
    );
  }

  // 主视图
  Widget _buildView(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        // 图标
        _buildLogo(),

        const SizedBox(height: 24),

        // 标题
        const Text(
          "Online Market",
          style: TextStyle(
            fontSize: 19,
            fontFamily: "Poppins",
            fontWeight: FontWeight.bold,
            color: Colors.white,
            height: 22 / 19,
          ),
        ),

        const SizedBox(height: 27),

        // 倒计时
        Text(
          "0",
          style: const TextStyle(
            fontSize: 19,
            fontFamily: "Poppins",
            fontWeight: FontWeight.bold,
            color: Colors.white,
            height: 22 / 19,
          ),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.backgroundSplash,
      body: Center(child: _buildView(context)),
    );
  }
}

4.4 实现倒计时

lib/pages/splash.dart

计数 num

Dart 复制代码
  // 计数 num
  final duration = 10;
  int number = 0;

倒计时函数

Dart 复制代码
  // 倒计时
  Future<void> _countdown() async {
    number = duration;
    for (int i = 0; i < duration; i++) {
      await Future.delayed(const Duration(seconds: 1), () {
        if(mounted == ture) {
          setState(() {
            number--;
          });
        }
      });
      // 倒计时结束, 进入 welcome
      if (number == 0) {
        if (kDebugMode) {
          print("倒计时结束");
        }
      }
    }
  }

注意 await async 异步函数的语法

初始执行

Dart 复制代码
  @override
  void initState() {
    super.initState();
    _countdown();
  }

打印显示

Dart 复制代码
  // 主视图
  Widget _buildView(BuildContext context) {
    		...
          
				// 倒计时
        Text(
          number > 0 ? "$number" : "done",
          style: const TextStyle(
            fontSize: 19,
            fontFamily: "Poppins",
            fontWeight: FontWeight.bold,
            color: Colors.white,
            height: 22 / 19,
          ),
        ),

4.5 重构文字显示函数

macos 下是 option + enter , 也可以在组件上 右键 -> 重构...

文字显示函数

Dart 复制代码
  // 文字显示
  Text _buildText(String text) {
    return Text(
      text,
      style: const TextStyle(
        fontSize: 19,
        fontFamily: "Poppins",
        fontWeight: FontWeight.bold,
        color: Colors.white,
        height: 22 / 19,
      ),
    );
  }

主视图代码

Dart 复制代码
  // 主视图
  Widget _buildView(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // logo
          _buildLogo(),
          const SizedBox(height: 24),

          // 标题
          _buildText("Online Market"),
          const SizedBox(height: 27),

          // 计数器
          _buildText("10"),

          // end
        ],
      ),
    );
  }

4.6 完整代码

lib/pages/splash.dart

Dart 复制代码
import 'package:flutter/material.dart';

import '../common/app_colors.dart';
import '../common/assets.dart';

class SplashPage extends StatefulWidget {
  const SplashPage({Key? key}) : super(key: key);

  @override
  State<SplashPage> createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  // 计数变量
  final duration = 3;
  int number = 0;

  // 倒计时函数
  Future<void> _countdown() async {
    number = duration;
    for (var i = 0; i < duration; i++) {
      await Future.delayed(const Duration(seconds: 1), () {
        if (mounted == true) {
          setState(() {
            number--;
          });
        }
      });

      if (number == 0) {
        print("倒计时结束");
      }
    }
  }

  @override
  void initState() {
    super.initState();
    _countdown();
  }

  // logo
  Widget _buildLogo() {
    return Stack(
      alignment: Alignment.center,
      children: [
        // 底部
        Container(
          width: 120,
          height: 120,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(120 / 2),
          ),
        ),

        // 图片
        Image.asset(
          AssetsImages.logoPng,
          width: 84,
          height: 80,
        ),
      ],
    );
  }

  // 文字显示
  Text _buildText(String text) {
    return Text(
      text,
      style: const TextStyle(
        fontSize: 19,
        fontFamily: "Poppins",
        fontWeight: FontWeight.bold,
        color: Colors.white,
        height: 22 / 19,
      ),
    );
  }

  // 主视图
  Widget _buildView(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // logo
          _buildLogo(),
          const SizedBox(height: 24),

          // 标题
          _buildText("Online Market"),
          const SizedBox(height: 27),

          // 计数器
          _buildText(number > 0 ? "$number" : "done"),

          // end
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.backgroundSplash,
      body: _buildView(context),
    );
  }
}

4.7 总结

  • 无状态组件重构成有状态组件
  • 使用 Future.delayed 方式实现倒计时
  • 使用 三目运算符 控制显示

1. Welcome 图片尺寸适应(第二页)

1.1 目标

  • 全局配置样式、字体
  • 图片适配高宽
  • 布局代码练习

1.2 当前效果图

1.3 设置全局字体

lib/main.dart

Dart 复制代码
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...

      // 样式
      theme: ThemeData(
        primarySwatch: Colors.orange,
        fontFamily: "Poppins", // 字体
      ),

字体全局放在 theme fontFamily 属性中

1.4 界面布局、标题

lib/pages/welcome.dart

Dart 复制代码
import 'package:flutter/material.dart';

/// 欢迎页面
class WelcomePage extends StatelessWidget {
  const WelcomePage({Key? key}) : super(key: key);

  // 主视图
  Widget _buildView() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 标题
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 30),
          child: Text(
            "Browse & Order All Products at Any Time",
            textAlign: TextAlign.center,
            style: TextStyle(
              fontSize: 20,
              fontFamily: "Poppins",
              fontWeight: FontWeight.bold,
              color: Color(0xff2B2A2A),
              height: 23 / 20,
            ),
          ),
        ),

        // 图
        Container(),

        // 底部按钮
        Container(),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _buildView(),
    );
  }
}

标题代码抽取函数,整理代码

Dart 复制代码
  // 标题
  Padding _buildTitle() {
    return const Padding(
      padding: EdgeInsets.symmetric(horizontal: 30),
      child: Text(
        "Browse & Order All Products at Any Time",
        textAlign: TextAlign.center,
        style: TextStyle(
          fontSize: 20,
          fontFamily: "Poppins",
          fontWeight: FontWeight.bold,
          color: Color(0xff2B2A2A),
          height: 23 / 20,
        ),
      ),
    );
  }
Dart 复制代码
  // 主视图
  Widget _buildView() {
    return Column(
      children: [
        const SizedBox(height: 100),

        // 标题
        _buildTitle(),
        
        ...

1.5 图片尺寸适应

从蓝湖下载图片导入项目,这里不再重复叙述

lib/pages/welcome.dart

Dart 复制代码
  // 图片
  Image _buildImage() {
    return Image.asset(
      AssetsImages.welcomePng,
      height: 300,
      width: double.infinity,
      fit: BoxFit.none,
      //fit:BoxFit.fitWidth
    );
  }
Dart 复制代码
  // 主视图
  Widget _buildView() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 标题
        _buildTitle(),

        const SizedBox(height: 70),

        // 图
        _buildImage(),

        // 底部按钮
        _buildBtns(),
      ],
    );
  }

1.6 总结

  • 通过 ThemeData.fontFamily 设置全局字体
  • padding: EdgeInsets.symmetric(horizontal: 30) 水平 Padding 距离
  • 图片组件 Image.fit 设置宽高适配
  • 布局第一规则 从上往下

2. Welcome 导航切换

2.1 目标

  • 布局规则 "从上往下、从左往右"
  • 全局按钮颜色样式
  • 布局训练 横向、纵向 混合

2.2 当前效果图

2.3 底部按钮

lib/pages/welcome.dart

Dart 复制代码
  // goto 登录页面
  void onLogin(BuildContext context) {}

	// 底部按钮
  Padding _buildBtns(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24),
      child: Row(
        children: [
          // skip
          TextButton(
            onPressed: () => onLogin(context),
            child: const Text(
              "Skip",
              style: TextStyle(
                fontSize: 15,
                fontWeight: FontWeight.w300,
                color: Color(0xff2B2A2A),
              ),
            ),
          ),

          // 撑开
          const Expanded(
            child: SizedBox(),
          ),

          // Get Started
          Container(
            height: 42,
            width: 140,
            clipBehavior: Clip.antiAlias,
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.all(
                Radius.circular(18),
              ),
            ),
            child: ElevatedButton(
              onPressed: () => onLogin(context),
              style: ButtonStyle(
                elevation: MaterialStateProperty.all(0),
                minimumSize: MaterialStateProperty.all(Size.zero),
              ),
              child: const Text(
                "Get Started",
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w300,
                  color: Colors.white,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
Dart 复制代码
  // 主视图
  Widget _buildView(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 标题
        _buildTitle(),

        const SizedBox(height: 70),

        // 图
        _buildImage(),

        const SizedBox(height: 70),

        // 底部按钮
        _buildBtns(context),
      ],
    );
  }

2.4 导航切换

splash 倒计时结束进入 welcome 界面

Dart 复制代码
  // 倒计时
  Future<void> _countdown() async {
    number = duration;
    for (int i = 0; i < duration; i++) {
      ...
      // 倒计时结束, 进入 welcome
      if (number == 0) {
        Navigator.pushReplacement(context,
            MaterialPageRoute(builder: (context) => const WelcomePage()));
      }
    }
  }

2.5 总结

Dart 复制代码
设计稿布局分析 "从上往下、从左往右"
先写布局代码结构
注意命名 _buildXXX 开头都是私有布局函数
导航 Navigator.pushReplacement 进入新界面并替换当前

1. login 布局(第三页)

1.1 目标

看标注布局界面(千锤百炼就会了)

1.2 布局

lib/pages/login.dart

Dart 复制代码
  // 登录表单
  Widget _buildForm() {
    return Container();
  }
Dart 复制代码
	// 主视图
  Widget _buildView() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 15),
      color: AppColors.backgroundSplash,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // 图标
          Image.asset(
            AssetsImages.logoPng,
            width: 60,
          ),

          const SizedBox(height: 20),

          // 主标
          const Text(
            "Let's Sign You In",
            style: TextStyle(
              fontSize: 20,
              color: Colors.white,
              fontWeight: FontWeight.bold,
            ),
          ),

          const SizedBox(height: 10),

          // 子标
          const Text(
            "Welcome back, you've been missed!",
            style: TextStyle(
              fontSize: 13,
              color: Colors.white,
              fontWeight: FontWeight.w300,
            ),
          ),

          const SizedBox(height: 50),

          // 表单
          _buildForm(),
        ],
      ),
    );
  }
Dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _buildView(),
    );
  }

1.3 总结

  • 记住布局规则 "从上往下、从左往右"
  • 用函数拆分视图结构

2. login 登录表单

2.1 目标

  • 编写表单
  • 有效性检查

2.2 当前效果图

2.3 登录表单

lib/pages/login.dart

Dart 复制代码
  // 账号输入是否有效
  bool isUserNameValid = false;
Dart 复制代码
  // 登录表单
  Widget _buildForm() {
    return Container(
      padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(35),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Username or E-Mail
          const Text(
            "Username or E-Mail",
            style: TextStyle(
              fontSize: 15,
              color: Color(0xff838383),
              fontWeight: FontWeight.w300,
            ),
          ),
          TextField(
            onChanged: (value) {
              bool valid = false;
              if (value.length >= 6) {
                valid = true;
              } else {
                valid = false;
              }

              setState(() {
                isUserNameValid = valid;
              });
            },
            decoration: InputDecoration(
              hintText: "@",
              // labelText: "Username or E-Mail",
              // labelStyle: const TextStyle(
              //   fontSize: 15,
              //   color: Color(0xff838383),
              //   fontWeight: FontWeight.w300,
              // ),
              prefixIcon: Image.asset(
                AssetsImages.iconUserPng,
                width: 23,
                height: 23,
              ),
              suffixIcon: isUserNameValid == true
                  ? const Icon(
                      Icons.done,
                      color: Colors.green,
                    )
                  : null,
            ),
          ),

          // 间距
          const SizedBox(height: 35),

          // Password
          const Text(
            "Password",
            style: TextStyle(
              fontSize: 15,
              color: Color(0xff838383),
              fontWeight: FontWeight.w300,
            ),
          ),
          TextField(
            obscureText: true,
            decoration: InputDecoration(
              hintText: "6 digits",
              // labelText: "Password",
              // labelStyle: const TextStyle(
              //   fontSize: 15,
              //   color: Color(0xff838383),
              //   fontWeight: FontWeight.w300,
              // ),
              prefixIcon: Image.asset(
                AssetsImages.iconLockPng,
                width: 23,
                height: 23,
              ),
              suffixIcon: TextButton(
                onPressed: () {},
                child: const Text(
                  "Forget?",
                  style: TextStyle(
                    fontSize: 15,
                    color: Color(0xff0274bc),
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
            ),
          ),

          // 间距
          const SizedBox(height: 30),

          // Sign In

          // 间距
          const SizedBox(height: 16),

          // Don't have an account?  + Sign Up
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 文字
              const Text(
                "Don't have an account? ",
                style: TextStyle(
                  fontSize: 15,
                  color: Color(0xff171717),
                  fontWeight: FontWeight.w300,
                ),
              ),
              
              // 按钮
			  TextButton(
                onPressed: () {},
                child: const Text(
                  "Sign Up",
                  style: TextStyle(
                    fontSize: 15,
                    color: Color(0xff0274bc),
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
              
            ],
          ),
        ],
      ),
    );
  }

2.4 总结

  • 通过 TextField.decoration 属性进行装饰
  • TextField.obscureText 开启密码

3. login 按钮抽取

3.1 目标

  • 抽取公共按钮组件
  • 修改成纯 ElevatedButton 按钮

3.2 当前效果图

3.3 按钮组件抽取

lib/common/widgets.dart

Dart 复制代码
import 'package:flutter/material.dart';

/// 按钮组件
class ButtonWidget extends StatelessWidget {
  const ButtonWidget({
    Key? key,
    this.height,
    this.widget,
    this.radius,
    this.onPressed,
    this.text,
  }) : super(key: key);

  /// 文字
  final String? text;

  /// 高度
  final double? height;

  /// 宽度
  final double? widget;

  /// 圆角
  final double? radius;

  /// 点击事件
  final void Function()? onPressed;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ButtonStyle(
        // 阴影高度
        elevation: MaterialStateProperty.all(0),
        // 最小尺寸
        minimumSize: MaterialStateProperty.all(
            Size(widget ?? double.infinity, height ?? double.infinity)),
        // 形状 圆角
        shape: MaterialStateProperty.all(
          RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(radius ?? 18),
            ),
          ),
        ),
      ),
      child: Text(
        // 文字
        text ?? "",
        // 文字样式
        style: const TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.w300,
          color: Colors.white,
        ),
      ),
    );
  }
}

3.4 登录按钮

lib/pages/login.dart

Dart 复制代码
  // 登录表单
  Widget _buildForm() {
					...
    			// Sign In
          ButtonWidget(
            text: 'Sign In',
            onPressed: () {},
            height: 60,
            widget: double.infinity,
            radius: 18,
          ),

3.5 欢迎按钮

lib/pages/welcome.dart

Dart 复制代码
  // 底部按钮
  Padding _buildBtns(BuildContext context) {
    ...
    // Get Started
    ButtonWidget(
      text: "Get Started",
      height: 42,
      widget: 140,
      radius: 32,
      onPressed: () => onLogin(context),
    ),
Dart 复制代码
  // goto 登录页面
  void onLogin(BuildContext context) {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => const LoginPage()),
    );
  }

3.6 总结

  • 公共组件类抽取方法
  • ElevatedButton 组件属性配置

创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖

相关推荐
朝阳眯眼40 分钟前
Android 集成OpenCV
android·人工智能·opencv
JerryHe1 小时前
Android 常用文件系统命令
android·文件系统·常用命令·分区修复
非正式程序猿3 小时前
Android 权限申请分享
android
小菜琳5 小时前
Android系统adb shell dumpsys activity processes
android
ChatMoneyAI5 小时前
php简单的单例模式
android·php
小菜琳5 小时前
单例模式在 Android中的应用
android
へんしんへんしん10 小时前
MySQL主从复制与读写分离
android·github
mrathena14 小时前
Windows 11 安装 安卓子系统 (WSA)
android·windows
啊猪是的读来过倒15 小时前
Vue 全局状态管理新宠:Pinia实战指南
前端·vue.js·flutter·pinia·全局状态管理
DaSunWarman15 小时前
自动翻译 android/res/values/strings.xml
android·xml·机器翻译·strings.xml