Flutter笔记:使用Flutter构建响应式PC客户端/Web页面-案例

news2024/12/23 8:44:28
Flutter笔记
使用Flutter构建响应式PC客户端/Web页面-案例

作者李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
邮箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/134339945


【简介】也不需要什么简介,使用 Flutter 框架完成下面的响应式PC/Web端效果,你会怎么做呢:
在这里插入图片描述
本文就是介绍相关响应式上的一些工具和实践。


1. Flutter框架中的尺寸工具

Flutter 中,你可以使用 MediaQueryLayoutBuilderAspectRatio 等Widget实现 尺寸测量与约束,进而创建响应式UI。例如,你可以使用 MediaQuery 来获取屏幕的尺寸和方向,然后根据这些信息来动态调整组件的布局和样式。你也可以使用 LayoutBuilder 来根据父组件的尺寸来调整子组件的布局。

1.1 MediaQuery

MediaQuery 组件可以获取当前媒体(例如屏幕)的一些属性,如尺寸、方向、亮度等。你可以使用 MediaQuery.of(context) 来获取一个 MediaQueryData 对象,然后通过这个对象来获取媒体的属性。

例如,你可以使用以下代码来获取屏幕的宽度和高度:

double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;

你也可以使用以下代码来判断当前是否为横屏:

bool isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;

1.2 LayoutBuilder

LayoutBuilder 组件可以根据父组件的约束来生成不同的布局。

LayoutBuilder 的构造函数接受一个回调函数,这个回调函数有两个参数:BuildContextBoxConstraints。 其中 BoxConstraints 对象描述了父组件对子组件的约束,如最大/最小宽度和高度。

例如,你可以使用以下代码来创建一个宽度为父Widget一半的子组件:

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    return Container(
      width: constraints.maxWidth / 2,
      child: ...,
    );
  },
)

LayoutBuilderMediaQuery 都是 Flutter 中用于获取和处理布局信息的工具,但它们的用途和工作方式有所不同:

  • MediaQuery 主要用于获取媒体(通常是屏幕)的信息,如尺寸、方向、亮度等。你可以使用 MediaQuery 来创建响应式布局,即根据屏幕的尺寸和方向来调整布局。例如,你可以使用 MediaQuery 来获取屏幕的宽度,然后根据宽度来决定显示一个列布局还是行布局;

  • LayoutBuilder主要用于获取父组件对子组件的约束,如最大/最小宽度和高度。你可以使用 LayoutBuilder 来创建自适应布局,即根据父组件的约束来调整子组件的布局。例如,你可以使用 LayoutBuilder来获取父组件的最大宽度,然后根据最大宽度来决定子组件的宽度。

  • MediaQuery 获取的是一个 静态的快照,它获取的屏幕信息在获取时就已经固定,不会随着屏幕尺寸的变化而变化。如果你需要响应屏幕尺寸的变化,你需要在屏幕尺寸变化时重新获取MediaQuery的信息。相比之下, LayoutBuilder 则是动态的,它会在父组件的约束变化时重新构建。这意味着如果父 组件 的尺寸变化了,LayoutBuilder 会自动重新获取约束并重新构建子组件。

可见,如果你需要创建一个能够响应屏幕尺寸变化的布局,你可能需要使用 LayoutBuilder。如果你只需要获取屏幕的信息,并不需要响应屏幕尺寸的变化,那么 MediaQuery 可能更适合你;反之,则需要使用 LayoutBuilder

1.3 AspectRatio

AspectRatio 用于强制子组件具有特定的宽高比。AspectRatio 的构造函数接受一个 aspectRatio参数,这个参数表示宽度和高度的比例。比如:

AspectRatio(
  aspectRatio: 16 / 9,
  child: ...,
)

2. 实践:构建响应式Header

我们实现响应式Header的基本思路是,根据屏幕的宽度来选择显示不同的顶部导航栏。整体上,依据这个

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

  
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // 使用LayoutBuilder来获取父Widget的尺寸约束
        // 如果最大宽度小于800像素,返回垂直布局的顶部导航栏
        if (constraints.maxWidth < 800) {
          return const VerticalTopNav();
        } 
        // 否则,返回原始的顶部导航栏
        else {
          return const OriginalTopNav();
        }
      },
    );
  }
}

在这个组件中,LayoutBuilder被用来获取父Widget的尺寸约束,然后根据最大宽度来选择显示哪种顶部导航栏。如果最大宽度小于800像素,就显示垂直布局的顶部导航栏;否则,就显示原始的顶部导航栏。这样,无论屏幕的尺寸如何变化,都能保证顶部导航栏的布局适应屏幕尺寸。

接下来,我们该具体实现 VerticalTopNavOriginalTopNav了。

先看 OriginalTopNav 部分:

class OriginalTopNav extends StatelessWidget {
  const OriginalTopNav({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black,
      child: const Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          HoverTextWidget(66),
          HoverTextWidget(66),
          HoverTextWidget(66),
          HoverTextWidget(66),
          Spacer(),
          HoverTextWidget(66),
        ],
      ),
    );
  }
}

其中的文本组件HoverTextWidget代码为:

class HoverTextWidget extends StatefulWidget {
  const HoverTextWidget(this.width, {Key? key}) : super(key: key);
  final double width;

  
  State<HoverTextWidget> createState() => _HoverTextWidgetState();
}

class _HoverTextWidgetState extends State<HoverTextWidget> {
  // 定义一个状态变量,用于记录鼠标是否悬停在该组件上
  bool isHovered = false;

  // 定义一个方法,用于更新isHovered状态
  void onHover(bool hover) {
    setState(() {
      isHovered = hover;
    });
  }

  
  Widget build(BuildContext context) {
    return MouseRegion(
      // 当鼠标进入该组件时,调用onHover方法并传入true
      onEnter: (_) => onHover(true),
      // 当鼠标离开该组件时,调用onHover方法并传入false
      onExit: (_) => onHover(false),
      // 根据isHovered状态来改变鼠标光标,如果isHovered为true,光标为click,否则为basic
      cursor: isHovered ? SystemMouseCursors.click : SystemMouseCursors.basic,
      child: GestureDetector(
        child: Container(
          height: 40,
          alignment: Alignment.center,
          // 根据isHovered状态来改变背景颜色,如果isHovered为true,背景颜色为grey,否则为black
          color: isHovered ? Colors.grey : Colors.black,
          width: widget.width,
          child: Text(
            '链接',
            style: TextStyle(
              // 根据isHovered状态来改变文字颜色,如果isHovered为true,文字颜色为black,否则为white
              color: isHovered ? Colors.black : Colors.white,
            ),
          ),
        ),
      ),
    );
  }
}

在这里插入图片描述

似乎没什么难点需要说明的,那就接着 VelticalTopNav 部分:

class VelticalTopNav extends StatelessWidget {
  const VelticalTopNav({super.key});

  
  Widget build(BuildContext context) {
    // return const LinkedTabGroup(Axis.vertical);
    return ExpansionTile(
      // title: Text(''),
      title: SvgPicture.asset(
        '/assets/svgs/jcstudio-v2-color.svg',
        width: 50, // 设置宽度
        height: 50,
      ),
      leading: const Icon(Icons.menu, color: Colors.amber),
      backgroundColor: Colors.black,
      collapsedBackgroundColor: Colors.black,
      collapsedIconColor: Colors.amber,
      collapsedTextColor: Colors.amber,
      children: [
        ListTile(
          title: const Text(
            '链接',
            style: TextStyle(color: Colors.white),
          ),
          onTap: () {
            // 处理子菜单项点击
          },
        ),
        ListTile(
          title: const Text(
            '链接',
            style: TextStyle(color: Colors.white),
          ),
          onTap: () {
            // 处理子菜单项点击
          },
        ),

        ListTile(
          title: const Text(
            '链接',
            style: TextStyle(color: Colors.white),
          ),
          onTap: () {
            // 处理子菜单项点击
          },
        ),
        ListTile(
          title: const Text(
            '链接',
            style: TextStyle(color: Colors.white),
          ),
          onTap: () {
            // 处理子菜单项点击
          },
        ),
      ],
    );
  }
}

这样就完成了整体效果中的Header效果,它将在后面被我们的响应式页面所调用。

在这里插入图片描述

在这里插入图片描述

4. 实践:完成一个响应式Web骨架

同样的思路,我们可以用以完成页面主体部分。

class ExampleHomePage extends StatelessWidget {
  final String url = '/windows_page_view_2';
  const ExampleHomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
         // 调用上一节写好的 Header(appbar)部分
          const ResponsiveHeaderNavbar(),
          Expanded(
            child: CustomScrollView(
              slivers: [
                SliverAppBar(
                  automaticallyImplyLeading: false,
                  pinned: true,
                  snap: false,
                  floating: false,
                  expandedHeight: 460.0,
                  flexibleSpace: FlexibleSpaceBar(
                    background: Image.asset(
                      'assets/images/it_curtoon_1.png',
                      fit: BoxFit.cover,
                    ),
                    title: const Text('flutter-online.top'),
                  ),
                ),
				// 同样的思路在这里再来一次
                SliverToBoxAdapter(
                  child: LayoutBuilder(
                    builder: (context, constraints) {
                      if (constraints.maxWidth < 800) {
                        // 当屏幕宽度小于800像素时,改为上下布局
                        return const Column(
                          children: [
                            Part1(),
                            Part2(),
                          ],
                        );
                      } else {
                        // 屏幕宽度大于等于800像素时,保持原有布局
                        return Column(
                          children: [
                            Container(
                              padding: const EdgeInsets.only(
                                  left: 100, right: 100, top: 30),
                              child: const Row(
                                children: [
                                  Expanded(
                                    child: Part1(),
                                  ),
                                  SizedBox(
                                    width: 260,
                                    child: Part2(),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        );
                      }
                    },
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

效果如开头我所展示的那样:
在这里插入图片描述

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

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

相关文章

Python基础入门例程53-NP53 前10个偶数(循环语句)

最近的博文&#xff1a; Python基础入门例程52-NP52 累加数与平均值(循环语句)-CSDN博客 Python基础入门例程51-NP51 列表的最大与最小(循环语句)-CSDN博客 Python基础入门例程50-NP50 程序员节&#xff08;循环语句&#xff09;-CSDN博客 目录 最近的博文&#xff1a; 描…

TCSVT(IEEE Transactions on Circuits and Systems for Video Technology)期刊投稿指南

目录 TCSVT 期刊简介 TCSVT 期刊影响因子和分区​ 期刊官方网站 期刊投稿网址 投稿指南网址 稿件格式要求 稿件提交指南 A. 提交稿件 B. 手稿格式 C. 摘要指南 D. 提交图形的指南 E. 页面和彩色图形费用 TCSVT 期刊简介 IEEE Transactions on Circuits and Systems…

RGB颜色空间与BMP格式图片

RGB颜色空间 RGB可以分为两大类&#xff1a;一种是索引形式&#xff0c;一种是像素形式&#xff1a; 索引形式&#xff1a;存储每个像素在调色板中的索引 RGB1&#xff1a;每个像素用1bit表示&#xff0c;调色板中只包含两种颜色&#xff08;黑白&#xff09;RGB4&#xff1a…

恒源云之oss上传数据、云台下载数据

目录 一、本地cmd上传数据二、使用云平台下载数据 一、本地cmd上传数据 需要下载恒源云客户端oss需要先将数据&#xff08;代码、数据集&#xff09;压缩成zip文件。 本地cmd打开oss&#xff0c;测试是否安成功 oss输入oss命令&#xff0c;并正确输入账号密码 oss login在个人…

按键编程 pal库和标准库

按钮的电路设计 电路的搭建 原理与编程 创建了两个变量 用来捕捉按键的状态 先让两个变量都为1 previous和current都为1 &#xff08;按键没按下&#xff09; 然后让current去捕捉按键的状态通过读gpioA的pin0 如果为0就是按键按下 如果为1就是按键没按下 然后赋值给current …

U-Boot 图形化配置及其原理

目录 U-Boot 图形化配置体验menuconfig 图形化配置原理make menuconfig 过程分析Kconfig 语法简介 添加自定义菜单 在前两章中我们知道uboot 可以通过mx6ull_alientek_emmc_defconfig 来配置&#xff0c;或者通过文件mx6ull_alientek_emmc.h 来配置uboot。还有另外一种配置uboo…

理解快速排序

理解快速排序 首先了解以下快速排序 快速排序&#xff08;QuickSort&#xff09;是一种常用的排序算法&#xff0c;属于比较排序算法的一种。它是由英国计算机科学家Tony Hoare于1960年提出的&#xff0c;是一种分而治之&#xff08;divide and conquer&#xff09;的算法。 …

systemd-timesyncd

介绍 systemd-timesyncd 是一个用于跨网络同步系统时钟的守护服务。它实现了一个 SNTP 客户端。与NTP的复杂实现相比&#xff0c;这个服务简单的多&#xff0c;它只专注于从远程服务器查询然后同步到本地时钟。除非你打算为客户端提供 NTP 服务器或者连接本地硬件时钟&#xff…

Keras实现图注意力模型GAT

简介&#xff1a;本文实现了一个GAT图注意力机制的网络层&#xff0c;可以在Keras中像调用Dense网络层、Input网络层一样直接搭积木进行网络组合。 一&#xff0c;基本展示 如下图所示&#xff0c;我们输入邻接矩阵和节点特征矩阵之后&#xff0c;可以直接调用myGraphAttention…

第90步 深度学习图像分割:U-Net建模

基于WIN10的64位系统演示 一、写在前面 从这一期开始&#xff0c;我们杀个回马枪&#xff0c;继续学习深度学习图像分割系列&#xff0c;以为4090上岗了。 图像分割是计算机视觉的一个重要任务&#xff0c;目的是将数字图像分割成多个部分或区域&#xff0c;这些部分通常对应…

大话IEC104 规约

2. iec104 协议的帧结构 iec104 基于TCP/IP 传输&#xff0c;是一个应用层协议&#xff0c; 其帧结构被称为 APDU&#xff0c;APDU 一般由 APCI 和 ASDU组成。 2.1 APDU (Application Protocol Data Unit) APDU 被称为应用协议数据单元&#xff0c;也就是一个iec104 的协议帧…

详解—搜索二叉树

一.二叉搜索树 1.1概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值 它的…

Android修行手册 - 可变参数中星号什么作用(冷知识)

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

No183.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

【MySQL系列】 第一章 · MySQL概述

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Visual Studio 2019 写 Unity 脚本时,烦人又离谱的自动补全!

Visual Studio 2019 写 Unity 脚本时&#xff0c;逆天又离谱的自动补全&#xff01; 血压高升的原因 写脚本的时候&#xff0c;智能提示有哪些函数可以使用&#xff0c;是非常棒的一件事情&#xff0c;有助于游戏开发者编写和检查自己的脚本代码。 但是&#xff01; 我想输入…

Leetcode—2469.温度转换【简单】

2023每日刷题&#xff08;二十六&#xff09; Leetcode—2469.温度转换 实现代码 /*** Note: The returned array must be malloced, assume caller calls free().*/ double* convertTemperature(double celsius, int* returnSize) {double* ans (double *)malloc(sizeof(do…

Leetcode—107.二叉树的层序遍历II【中等】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—107.二叉树的层序遍历II 实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullpt…

Radius是什么意思? 安当加密

Radius是什么意思&#xff1f; RADIUS&#xff08;Remote Authentication Dial In User Service&#xff09;是一种远程用户拨号认证系统&#xff0c;它由RFC 2865和RFC 2866定义&#xff0c;是应用最广泛的AAA&#xff08;Authentication、Authorization、Accounting&#xf…

【编程语言发展史】Python的起源和发展历史

目录 Python的起源 Python的发展历史 Python的生态系统和应用领域 Python的社区和发展模式 Python的未来趋势和挑战 Python是一门广受欢迎的高级编程语言&#xff0c;其起源和发展历史自20世纪末至今&#xff0c;经历了多个版本的迭代和社区的广泛参与。以下是关于Python的…