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

news2024/7/4 5:15:06

目录

1. Splash 布局(第一页)

1.1 目标

1.2 当前效果图

1.3 创建 Splash 界面

1.4 设置 MaterialApp

1.5 设置 Splash 背景色

1.6 布局 Splash 界面

1.7 总结

2. Splash 圆角图片

2.1 目标

2.2 当前效果图

2.3 蓝湖下载图片

2.4 图片导入项目

2.5 编写 assets 索引

2.6 编写标志 Logo

2.7 总结

3. Splash 文字

3.1 目标

3.2 当前效果图

3.3 蓝湖标注查看

3.4 字体下载导入

3.5 编写 TextStyle

3.6 加入间距

3.7 总结

4. Splash 倒计时

4.1 目标

4.2 当前效果图

4.3 改成有状态组件

4.4 实现倒计时

4.5 重构文字显示函数

4.6 完整代码

4.7 总结

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

1.1 目标

1.2 当前效果图

1.3 设置全局字体

1.4 界面布局、标题

1.5 图片尺寸适应

1.6 总结

2. Welcome 导航切换

2.1 目标

2.2 当前效果图

2.3 底部按钮

2.4 导航切换

2.5 总结

1. login 布局(第三页)

1.1 目标

1.2 布局

1.3 总结

2. login 登录表单

2.1 目标

2.2 当前效果图

2.3 登录表单

2.4 总结

3. login 按钮抽取

3.1 目标

3.2 当前效果图

3.3 按钮组件抽取

3.4 登录按钮

3.5 欢迎按钮

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

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

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

import 'package:flutter/material.dart';

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

lib/pages/splash.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

  // 主视图
  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

flutter:
  ...
  
  assets:
    - assets/images/

2.5 编写 assets 索引

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

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

lib/pages/splash.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,
        ),
      ],
    );
  }
  // 主视图
  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

  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

  // 标题
  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

  // 主视图
  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

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

  // 计数 num
  final duration = 10;
  int number = 0;

倒计时函数

  // 倒计时
  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 异步函数的语法

初始执行

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

打印显示

  // 主视图
  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 , 也可以在组件上 右键 -> 重构...

文字显示函数

  // 文字显示
  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("10"),

          // end
        ],
      ),
    );
  }

4.6 完整代码

lib/pages/splash.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

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

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(),
    );
  }
}

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

  // 标题
  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,
        ),
      ),
    );
  }
  // 主视图
  Widget _buildView() {
    return Column(
      children: [
        const SizedBox(height: 100),

        // 标题
        _buildTitle(),
        
        ...

1.5 图片尺寸适应

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

lib/pages/welcome.dart

  // 图片
  Image _buildImage() {
    return Image.asset(
      AssetsImages.welcomePng,
      height: 300,
      width: double.infinity,
      fit: BoxFit.none,
      //fit:BoxFit.fitWidth
    );
  }
  // 主视图
  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

  // 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,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
  // 主视图
  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 界面

  // 倒计时
  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 总结

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

1. login 布局(第三页)

1.1 目标

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

1.2 布局

lib/pages/login.dart

  // 登录表单
  Widget _buildForm() {
    return Container();
  }
	// 主视图
  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(),
        ],
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _buildView(),
    );
  }

1.3 总结

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

2. login 登录表单

2.1 目标

  • 编写表单
  • 有效性检查

2.2 当前效果图

2.3 登录表单

lib/pages/login.dart

  // 账号输入是否有效
  bool isUserNameValid = false;
  // 登录表单
  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

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

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

3.5 欢迎按钮

lib/pages/welcome.dart

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

3.6 总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1794734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT 信号和槽 一对多关联示例,一个信号,多个槽函数响应,一个信号源如何绑定多个槽函数

在窗体里放置一个单行文本编辑控件&#xff08;QLineEdit&#xff09;、一个标签控件&#xff08;QLabel&#xff09;和一个文本浏览控件&#xff08;QTextBrowser&#xff09;&#xff0c;在单行文 本编辑控件里的文本被编辑时&#xff0c;标签控件和文本浏览控件都会同步显示…

OpenMV学习笔记4——二维码识别

一、示例程序 按照下图顺序点击&#xff0c;即可打开官方在IDE中准备好的二维码实例程序&#xff1a; # QRCode Example # # This example shows the power of the OpenMV Cam to detect QR Codes # using lens correction (see the qrcodes_with_lens_corr.py script for hig…

【Vue】项目目录介绍和运行流程

文章目录 一、项目目录介绍二、public/index.html三、src/main.js四、运行流程 一、项目目录介绍 虽然脚手架中的文件有很多&#xff0c;目前咱们只需认识三个文件即可&#xff0c;这三个文件就决定了我们项目的运行 main.js 入口文件App.vue App根组件index.html 模板文件 我…

如何实现vue项目不同屏幕适配(2024最新)

文章目录 1.下载插件&#xff0c;修改px单位为rem单位2.配置vue.config.js(如下图位置所示)3.屏幕自适应4.项目实际使用 1.下载插件&#xff0c;修改px单位为rem单位 npm i postcss-plugin-px2rem2.配置vue.config.js(如下图位置所示) 注意在根目录下&#xff0c;如果没有该文…

【记录贴:分布式系列文章】

分布式系列文章目录 文章目录 分布式系列文章目录前言一、Redisq1.怎么判断是否命中缓存1. MySQL数据库如何检查询查缓存是否命中链接2.如何判断redis是否命中缓存链接 q2.Redis缓存穿透、雪崩、击穿以及分布式锁和本地锁 二、分布式q1.分布式订单号生成策略q2.接口幂等性,防止…

Crosslink-NX器件应用连载(9): USB3.0相机

作者&#xff1a;Hello&#xff0c;Panda 大家晚上好&#xff0c;很久没有分享设计案例了&#xff0c;实在是太忙了&#xff0c;精力十分有限&#xff0c;今天分享一个CrosslinkNX系列器件用作USB3.0相机的案例。其实就是分享一下使用CrosslinkNX器件设计USB3.0相机主要有两种…

腾讯云 TDMQ for Apache Pulsar 多地区高可用容灾实践

作者介绍 林宇强 腾讯云高级工程师 专注于消息队列、API网关、微服务、数据同步等 PaaS 领域。有多年的开发和维护经验&#xff0c;目前在腾讯云从事 TDMQ Pulsar 商业化产品方向的研发工作。 导语 本文将从四个维度&#xff0c;深入剖析 Pulsar 在多可用区高可用领域的容…

C++ 并发编程指南(5)线程状态及切换

文章目录 一、多线程状态及切换1、线程状态2、状态切换 前言&#xff1a; C中的线程状态及切换是操作系统和C线程库&#xff08;如POSIX线程或C11及之后的<thread>库&#xff09;共同管理的。线程的状态和切换是多线程编程中的重要概念&#xff0c;下面将简要介绍C线程的…

105.网络游戏逆向分析与漏洞攻防-装备系统数据分析-处理装备与技能数据的创建

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

SpringBoot+Vue校园管理系统(前后端分离)

技术栈 JavaSpringBootMavenMyBatisMySQLVueElement-UIShiro 系统角色 管理员用户院系管理员 系统功能截图

Simulink建立4WIS线性二自由度参考模型

4WIS线性二自由度参考模型 基于前轮转向做了小改动&#xff0c;难度不大&#xff0c;相当于两个微分方程加了两项 Simulink向CarSim中输入四个车轮的转角 有一点注意&#xff0c;四轮转向&#xff0c;前后轴车轮转角不应相等&#xff0c;否则动画会很滑稽 同侧车轮转向角的大小…

:长亭雷池社区版动态防护体验测评

序 长亭雷池在最近发布了动态防护功能&#xff0c;据说可以动态加密保护网页前端代码和阻止爬虫行为、阻止漏洞扫描行为等。今天就来体验测试一下 WAF 是什么 WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF …

基于STM32开发的智能建筑能耗管理系统

目录 引言环境准备智能建筑能耗管理系统基础代码实现&#xff1a;实现智能建筑能耗管理系统 4.1 能耗传感器数据读取4.2 电器设备控制4.3 实时数据监控与分析4.4 用户界面与数据可视化应用场景&#xff1a;能耗管理与优化问题解决方案与优化收尾与总结 1. 引言 随着智能建筑…

nc解决自定义参照字段前台保存后只显示主键的问题

nc解决自定义参照字段前台保存后只显示主键的问题 自定义参照类VoucherRefModel.java package nc.ui.jych.ref;import nc.ui.bd.ref.AbstractRefModel;/*** desc 凭证号参照* author hanh**/ public class VoucherRefModel extends AbstractRefModel {Overridepublic String[…

安全U盘和普通U盘有什么区别?

安全U盘&#xff08;也称为加密U盘或安全闪存驱动器&#xff09;与普通U盘肯定是有一些区别的&#xff0c;从字面意思上来看&#xff0c;就能看出&#xff0c;安全U盘是能够保护文件数据安全性的&#xff0c;普通U盘没这一些功能的&#xff0c;可随意拷贝文件&#xff0c;不防盗…

使用最小花费爬楼梯 | 动态规划

1.使用最小花费爬楼梯 题目连接&#xff1a;746. 使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开…

K210视觉识别模块学习笔记3:内存卡写入拍摄图片_LED三色灯的操作_按键操作_定时器的配置使用

今日开始学习K210视觉识别模块: LED三色灯的操作_按键操作_定时器的配置使用_内存卡写入拍摄图片 亚博智能的K210视觉识别模块...... 固件库版本: canmv_yahboom_v2.1.1.bin 本文最终目的是编写一个按键拍照的例程序&#xff1a; 为以后的专用场景的模型训练做准备&#xf…

2024050402-重学 Java 设计模式《实战责任链模式》

重学 Java 设计模式&#xff1a;实战责任链模式「模拟618电商大促期间&#xff0c;项目上线流程多级负责人审批场景」 一、前言 场地和场景的重要性 射击&#x1f3f9;需要去靶场学习、滑雪&#x1f3c2;需要去雪场体验、开车&#x1f697;需要能上路实践&#xff0c;而编程…

【python】OpenCV—Bitplane

学习来自&#xff1a; 位平面分割&#xff08;Bit-Plane Slicing&#xff09;使用OpenCVPython进行图像处理的初学者指南 位平面 位平面&#xff08;bitplane&#xff09;是一个在计算机科学中用于描述图像数据的概念&#xff0c;具体定义如下&#xff1a; 【定义】&#x…

vue3之拆若依--记实现后台管理首页(左侧菜单栏、头部信息区域...)

效果图 前期准备 启动若依在本地 启动若依后台,跑在自己本地: 这里对于如何下载若依相关的前后端代码请参考若依官网:RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-…