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

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

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

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

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

相关文章

红黑树,AVLTree树(平衡二叉树)迭代器原理讲解

红黑树&#xff0c;AVLTree树底层实现逻辑都是平衡二叉树&#xff08;AVLTree高度平衡&#xff0c;红黑树以某种规则平衡&#xff09;&#xff0c;但终究不像链表的迭代器那样逻辑简单。 简单叙述以下&#xff0c;二叉树上面迭代器的运行逻辑&#xff0c;根据下面的图&#xff…

leetcode-链表经典题

1.反转单链表 206. 反转链表https://leetcode.cn/problems/reverse-linked-list/这里我们使用创建一个变量cur来遍历原链表&#xff0c;再创建一个新节点newnode&#xff0c;首先使用一个循环来遍历原链表&#xff0c;cur为NULL是循环结束&#xff0c;每次进入循环将cur的下一…

.mallab勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复

导言&#xff1a; .mallab勒索病毒是一种极具威胁性的数字病毒&#xff0c;通过高级加密算法深度侵袭用户文件&#xff0c;迫使受害者支付赎金以获取解密密钥。了解其侵害方式和对抗手段对数字安全至关重要。数据的重要性不容小觑&#xff0c;您可添加我们的技术服务号&#x…

threejs(12)-着色器打造烟雾水云效果

一、自己封装水波纹效果 src/main/main01.js import * as THREE from "three";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import gsap from "gsap"; import * as dat from "dat.gui"; import ver…

【文件IO】

文章目录 File常见方法和属性属性构造方法方法 InputStream方法FileInputStream OutputStream利用 OutputStreamWriter 进行字符写入 总结按字节读取数据按字节写入数据按字符读取数据按字符写入数据 File常见方法和属性 属性 修饰符及类型属性说明static StringpathSeparato…

网络安全(黑客技术)-高效自学

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

思科设备静态路由配置

一、静态路由基本知识 路由器的主要功能就是用来转发IP 数据包以使数据包到达正确的目的主机。可以想象数据包到达路由器就像一辆汽车开到十字路口&#xff0c;路由表就类似路标&#xff0c;列出可能到达的目的地&#xff0c;以及应该选择哪条路到达目的地。 路由器必须要有相应…

Linux - 基础IO(重定向 - 重定向模拟实现 - shell 当中的 重定向)- 下篇

前言 上一篇博客当中&#xff0c;我们对 文件 在操作系统当中是 如何就管理的&#xff0c;这个问题做了 详细描述&#xff0c;本篇博客将基于上篇 博客当中的内容进行 阐述&#xff0c;如有疑问&#xff0c;请参考上篇博客&#xff1a; Linux - 基础IO&#xff08;Linux 当中…

应用层——HTTP协议

文章目录 HTTP协议1.HTTP简介2.认识URL3.urlencode和urldecode4.HTTP协议格式&#xff08;1&#xff09;HTTP请求协议格式&#xff08;2&#xff09;HTTP响应协议格式 5.HTTP的方法6.HTTP的状态码7.HTTP常见的Header8.Cookie和Session HTTP协议 1.HTTP简介 HTTP&#xff08;Hy…

mac安装brew

命令 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"如图 选择下载源&#xff0c;进行安装 安装完成 验证

计算机毕业设计 基于SpringBoot的实训管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【零基础小白也能轻松学会】3DMAX编织建模教程

有没有想过这些木质材料是如何在椅子上相互交织的&#xff1f;复杂吗&#xff1f;也许是也许不是……本教程将指导您一步一步地以任何形式提出自己的复杂编织图案。本教程将重点关注建模部分&#xff0c;并让您从那里开始发挥想象力。 1.首先创建一个新平面&#xff08;长度55&…

C++: 内存管理 (new / delete)

文章目录 一. C/C 内存分布二. C 语言中动态内存管理方式: malloc/calloc/realloc/free三. C内存管理方式1. new / delete 操作内置类型2. new / delete 操作自定义类型 四. operator new 与 operator delete 函数五. new 和 delete 的实现原理1. 内置类型2. 自定义类型 六. 定…

【 第八章】软件设计师 之 计算机软件法律法规

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 备考资料导航 软考好处&#xff1a;软考的…

程序员的护城河:职业发展的关键元素

目录 1. 技术深度与广度 2. 项目经验与实际操作 3. 沟通与团队协作 4. 持续学习与自我更新 5. 社区参与与开源贡献 6. 创新思维与解决问题的能力 7. 职业规划与自我管理 结语 在科技日新月异的今天&#xff0c;程序员的竞争已经不再仅仅依赖于技术水平&#xff0c;而是…

路径总和[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你二叉树的根节点root和一个表示目标和的整数targetSum。判断该树中是否存在 根节点到叶子节点的路径&#xff0c;这条路径上所有节点值相加等于目标和targetSum。如果存在&#xff0c;返回true&#xff1b;否则&#xff0c;返回fa…

基于SpringMVC模式的电器网上订购系统的设计

大家好我是玥沐春风&#xff0c;今天分享一个基于SpringMVC模式的电器网上订购系统的设计&#xff0c;项目源码以及部署相关请联系我&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 本系统利用现在比较广泛的JSP结合后台SpringMybatisAjax编写程序的方式实现的。 在…

【C++入门】构造函数析构函数

目录 前言 1. 类的默认成员函数 2. 构造函数 2.1 什么是构造函数 2.2 构造函数的特性 3. 析构函数 3.1 什么是析构函数 3.2 析构函数的特性 前言 前边我们已经了解了类和对像的基本概念&#xff0c;今天我们将继续深入了解类。类有6个默认成员函数&#xff0c;即使类中什么都…

Golang 字符串处理汇总

1. 统计字符串长度&#xff1a;len(str) len(str) 函数用于统计字符串的长度&#xff0c;按字节进行统计&#xff0c;且该函数属于内置函数也不用导包&#xff0c;直接用就行&#xff0c;示例如下&#xff1a; //统计字符串的长度,按字节进行统计: str : "golang你好&qu…

【数据库开发】DataX开发环境的安装部署(Python、Java)

文章目录 1、简介1.1 DataX简介1.2 DataX功能1.3 支持的数据通道 2、DataX安装配置2.1 DataX2.2 Java2.3 Python 3、DataX Web安装配置3.1 mysql3.2 DataX Web3.2.1 简介3.2.2 架构图3.2.3 依赖环境3.2.4 安装 4、入门使用4.1 DataX自带打印示例测试4.2 DataX生成任务模板文件4…