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

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

Flutter笔记使用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);
  @override
  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});
  @override
  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;
  @override
  State<HoverTextWidget> createState() => _HoverTextWidgetState();
}
class _HoverTextWidgetState extends State<HoverTextWidget> {
  // 定义一个状态变量,用于记录鼠标是否悬停在该组件上
  bool isHovered = false;
  // 定义一个方法,用于更新isHovered状态
  void onHover(bool hover) {
    setState(() {
      isHovered = hover;
    });
  }
  @override
  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});
  @override
  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});
  @override
  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(),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        );
                      }
                    },
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

效果如开头我所展示的那样:

目录
相关文章
|
Java 数据库 网络架构
菜鸟之路Day36一一Web开发综合案例(部门管理)
本文详细记录了基于Spring Boot的Web开发综合案例——部门管理功能的实现过程。从环境搭建到功能开发,涵盖数据库表设计、Spring Boot项目创建、依赖引入、配置文件设置以及Mapper、Service、Controller的基础结构构建。文章重点讲解了查询、删除、新增和修改部门信息的业务逻辑实现,遵循RESTful规范设计接口,并通过统一响应结果类`Result`优化前后端交互体验。借助Spring的IoC容器管理与MyBatis的SQL映射,实现了高效的数据操作与业务处理,最终完成部门管理的全功能开发。
416 12
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
1040 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
移动开发 开发者 HTML5
构建响应式Web界面:Flexbox与Grid的实战应用
【10月更文挑战第22天】随着互联网的普及,用户对Web界面的要求越来越高,不仅需要美观,还要具备良好的响应性和兼容性。为了满足这些需求,Web开发者需要掌握一些高级的布局技术。Flexbox和Grid是现代Web布局的两大法宝,它们分别由CSS3和HTML5引入,能够帮助开发者构建出更加灵活和易于维护的响应式Web界面。本文将深入探讨Flexbox和Grid的实战应用,并通过具体实例来展示它们在构建响应式Web界面中的强大能力。
308 3
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
545 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
XML SQL 前端开发
菜鸟之路Day37一一Web开发综合案例(员工管理)
本文介绍了基于Web开发的员工管理综合案例,涵盖分页查询、条件分页查询、删除员工和新增员工四大功能模块。通过前后端交互,前端传递参数(如页码、每页记录数、查询条件等),后端使用MyBatis与PageHelper插件处理数据查询与操作。代码结构清晰,包括Controller层接收请求、Service层业务逻辑处理以及Mapper层数据访问,并结合XML动态SQL实现灵活的条件查询。此外,新增与删除功能分别通过POST与DELETE请求完成,确保系统功能完整且高效。
336 7
|
12月前
|
存储 前端开发 Java
菜鸟之路Day38一一Web开发综合案例(三)
本文介绍了Web开发中的文件上传与员工信息修改的综合案例,涵盖前端到后端的完整流程。重点讲解了阿里云OSS的集成,包括Bucket创建、密钥获取及SDK使用,并通过Spring Boot实现文件上传功能。同时,详细描述了员工信息查询与修改的操作逻辑,涉及Controller、Service和Mapper层代码实现。最后探讨了配置文件的优化,对比@Value与@ConfigurationProperties注解,展示了如何通过实体类批量注入配置参数,提升代码可维护性与灵活性。
302 1
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
534 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
Web App开发 前端开发 JavaScript
鸿蒙5开发宝藏案例分享---Web适配一多开发实践
这是一份实用的鸿蒙Web多设备适配开发指南,针对开发者在不同屏幕尺寸下的布局难题提供了解决方案。文章通过三大法宝(相对单位、媒体查询和窗口监听)详细介绍如何实现智能适配,并提供了多个实战案例,如宫格布局、对话框变形和自适应轮播图等。此外,还分享了调试技巧及工具推荐,帮助开发者快速上手并优化性能。最后鼓励读者实践探索,并提示更多官方资源等待发现。
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
707 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
安全 Linux 开发工具
零基础构建开源项目OpenIM桌面应用和pc web- Electron篇
OpenIM 为开发者提供开源即时通讯 SDK,作为 Twilio、Sendbird 等云服务的替代方案。借助 OpenIM,开发者可以构建安全可靠的即时通讯应用,如 WeChat、Zoom、Slack 等。 本仓库基于开源版 OpenIM SDK 开发,提供了一款基于 Electron 的即时通讯应用。您可以使用此应用程序作为 OpenIM SDK 的参考实现。本项目同时引用了 @openim/electron-client-sdk 和 @openim/wasm-client-sdk,分别为 Electron 版本和 Web 版本的 SDK,可以同时构建 PC Web 程序和桌面应用(Wi
1438 2