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

目录

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 组件属性配置

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5G发牌五周年丨移远通信:全面发力,加快推进5G技术服务社会发展

2024年6月6日&#xff0c;正值中国5G商用牌照发牌五周年。根据移动通信“十年一代”的规律&#xff0c;5G已走过一半征程。在过去的五年时间里&#xff0c;5G技术从萌芽到成熟&#xff0c;深刻改变了工业、农业、医疗及消费端等各个领域的发展脉络。无论是无人机配送、自动驾驶…

自动化执行任务的脚本代码分享!

在当今信息化社会&#xff0c;自动化执行任务的脚本代码已经成为提高工作效率、减少人工错误的重要工具。 无论是数据处理、文件操作&#xff0c;还是网络请求、系统监控&#xff0c;脚本代码都能帮助我们实现自动化操作&#xff0c;从而释放人力&#xff0c;让我们有更多时间…

一种基于小波域的增强独立分量分析方法(MATLAB)

独立分量分析ICA是二十世纪九十年代发展起来的一种多元统计和计算技术&#xff0c;目的是用来分离或提取随机变量、观测数据或信号混合物中具有独立特性的隐藏分量。ICA可以看作是主分量分析PCA和因子分析FA的扩展。与PCA和FA相比&#xff0c;ICA是一种更强有力的技术。当PCA和…

家政服务小程序,提高企业在市场中的竞争力

近几年&#xff0c;人们对家政的需求持续增加&#xff0c;面对小程序的快速发展&#xff0c;互联网家政的模式成为了市场新的发展方向&#xff0c;越来越多的居民也开始在线上预约家政服务。随着当下人们对家政的需求日益提升&#xff0c;线上家政小程序利用各种信息技术&#…

<PLC><汇川><伺服>汇川SV630N伺服驱动使用转矩模式时报错E234.0的一种解决办法

前言 本文是使用汇川SV630N,进行EtherCat通讯,使用转矩模式时,当希望电机反向时电机会报错的一种解决办法。 错误代码 PLC端添加EtherCat轴,并编写控制指令,使用mc轴控指令: 当设定了目标力矩、力矩斜坡以及限制速度后,触发指令块,伺服会驱动电机进行运动。 在电机空…

Ripple:使用Wavelet Approximations来加速FHE的Programmable Bootstraps

1. 引言 University of Delaware和Nillion团队的 Charles Gouert、Mehmet Ugurbil、Dimitris Mouris、Miguel de Vega 和 Nektarios G. Tsoutsos&#xff0c;2024年论文《Ripple: Accelerating Programmable Bootstraps for FHE with Wavelet Approximations》&#xff0c;开源…

vue 文件预览mp4、txt、pptx、xls、xlsx、docx、pdf、html、xml

vue 文件预览 图片、mp4、txt、pptx、xls、xlsx、docx、pdf、html、xml 最近公司要做一个类似电脑文件夹的功能&#xff0c;支持文件夹操作&#xff0c;文件操作,这里就不说文件夹操作了&#xff0c;说说文件预览操作&#xff0c;本人是后端java开发&#xff0c;前端vue&#…

【Qt】Qt QTreeWidget隐藏列名称(横向表头)

1. 效果 未隐藏 隐藏 2. 方法 方法1 ui->treeWidget->header()->hide();方法2 ui->treeWidget->header()->setVisible(false);

猫毛过敏终极解决神器,使用宠物空气净化器享受快乐撸猫~

作为一位经验丰富的宠物主人&#xff0c;与猫咪共度的时光确实充满了乐趣。但是&#xff0c;猫毛和皮屑对某些人来说可能会成为头疼的问题&#xff0c;引发过敏症状&#xff0c;例如打喷嚏、流鼻涕&#xff0c;甚至呼吸急促。这些反应不仅会干扰宠物主人的日常生活&#xff0c;…

数据报表统计实现

目录 一&#xff1a;背景 二&#xff1a;实现过程 一&#xff1a;背景 最近需要开发一个数据统计的功能&#xff0c;主要是按照各种维度统计客户的数据&#xff0c;一般是按照日期来展示数量和变化情况。下面我们来梳理下实现的过程。 二&#xff1a;实现过程 1&#xff1a…

锻炼 精读笔记 01

元数据 [!abstract] 锻炼 书名&#xff1a; 锻炼作者&#xff1a; 丹尼尔利伯曼简介&#xff1a; 我们是为休息而生&#xff0c;还是为跑而生&#xff1f; 跑步会毁了你的膝盖吗? 哪种运动项目蕞适合我&#xff1f; 懒惰是不正常的行为吗&#xff1f; 每晚都需要睡够 8 个小时…

Python 引用数据类型

列表List 定义列表 可以将不同的基本数据类型或者列表装到一个列表里 my_list [1,2,3,4,5] print(my_list) # [1, 2, 3, 4, 5] 直接打印出列表的内容 print(type(my_list)) # <class list>my_list ["1","2","3","4","…

CV Method:超全学习率策略总结

文章目录 前言一、LambdaLR二、StepLR三、MultiStepLR四、ConstantLR五、LinearLR六、PolynomialLR七、ChainedScheduler八、ExponentialLR九、CosineAnnealingLR十、OneCycleLR十一、ReduceLROnPlateau十二、自定义学习率类函数总结 前言 在深度学习中&#xff0c;学习率对模…

数据分析工具|数据分析利器与业务决策的好帮手vividiem Desktop

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、腾讯云优秀创作者、ACDU成员 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注…

如何修复d3dcompiler43.dll丢失问题,这三种方法可轻松解决

在计算机使用过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是“计算机缺失d3dcompiler43.dll”。这个问题可能会影响到计算机的正常运行&#xff0c;让我们无法正常使用某些软件或者游戏。那么&#xff0c;究竟什么是d3dcompiler43.dll&#xff1f;为什…

nginx: [error] invalid PID number ““ in “/run/nginx.pid“

两种问题&#xff0c;我自己碰到的情况 ./nginx -s reload执行命令后报错 nginx: [error] invalid PID number ““ in “/run/nginx.pid“ 第一种情况&#xff0c;pid被注释了 /usr/local/nginx/conf/nginx.conf #user nobody; worker_processes 1;// 可能是这里被注释了…

小海外仓也能用得起的大型海外仓系统:高性价比模式是关键

高效的仓储管理、订单处理效率、标准化财务管理&#xff0c;这些集成在海外仓系统的功能&#xff0c;在以往都是大型海外仓才投资得起的工具。 不过实际情况是&#xff0c;这些标准化的管理模式&#xff0c;恰恰是资金有限的小型海外仓最需要的。为了解决这个问题&#xff0c;…

基于JSP技术的社区生活超市管理系统

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;MyEclipse开发环境、Tomcat服务器 系统展示 首页 管理员功能模块…

“墨者杯”网络安全大赛wp

漏洞利用01 504错误修改为POST提交拿到php源码&#xff0c; 查看逻辑$_POST[roam1] ! $_POST[roam2] && sha1($_POST[roam1]) sha1($_POST[roam2]) 采用数组绕过 roam1[]1&roam2[]2 拿到phpinfo&#xff0c;观察发现 这里的意思是每个php页面都包含这个f14…

微信小程序实现图片预览功能

需求&#xff1a;点击图片实现放大预览 实现&#xff1a;调用微信官方给出的方法 wx.previewImage 注&#xff1a;urls 必须是数组 <image src"{{imgUrl}}" data-src"{{imgUrl}}" bindtap"previewImage" />data: {imgUrl:https://img.yz…