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

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

目录
相关文章
|
1天前
|
开发框架 缓存 前端开发
利用Visual Basic构建高效的ASP.NET Web应用
【4月更文挑战第27天】本文探讨使用Visual Basic与ASP.NET创建高效Web应用的策略,包括了解两者基础、项目规划、MVC架构、数据访问与缓存、代码优化、异步编程、安全性、测试及部署维护。通过这些步骤,开发者能构建出快速、可靠且安全的Web应用,适应不断进步的技术环境。
|
2天前
|
前端开发 JavaScript Go
构建高性能Web应用:优化前端资源加载
在构建现代Web应用时,优化前端资源加载是至关重要的一步。本文将介绍一些提升Web应用性能的关键策略,包括减少HTTP请求、压缩和合并资源、使用CDN加速、以及异步加载技术等。通过实施这些优化策略,开发人员可以显著提升网站的加载速度和用户体验。
|
2天前
|
存储 中间件 Go
探索Gin框架:快速构建高性能的Golang Web应用
探索Gin框架:快速构建高性能的Golang Web应用
|
3天前
|
前端开发 JavaScript Java
前端与后端:构建现代Web应用的双翼
前端与后端:构建现代Web应用的双翼
|
24天前
|
监控 JavaScript 前端开发
《理解 WebSocket:Java Web 开发的实时通信技术》
【4月更文挑战第4天】WebSocket是Java Web实时通信的关键技术,提供双向持久连接,实现低延迟、高效率的实时交互。适用于聊天应用、在线游戏、数据监控和即时通知。开发涉及服务器端实现、客户端连接及数据协议定义,注意安全、错误处理、性能和兼容性。随着实时应用需求增加,WebSocket在Java Web开发中的地位将更加重要。
|
1月前
|
Web App开发 前端开发 开发工具
介绍Web开发的基础知识
介绍Web开发的基础知识
29 7
|
4天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
10天前
|
JSON Java fastjson
Spring Boot 底层级探索系列 04 - Web 开发(2)
Spring Boot 底层级探索系列 04 - Web 开发(2)
16 0
|
10天前
|
安全 编译器 PHP
PHP 8.1版本发布:引领Web开发新潮流
PHP编程语言一直是Web开发的主力军,而最新发布的PHP 8.1版本则为开发者们带来了更多创新和便利。本文将介绍PHP 8.1版本的主要特性,包括更快的性能、新的语言功能和增强的安全性,以及如何利用这些功能来提升Web应用程序的质量和效率。
|
13天前
|
PHP
web简易开发——通过php与HTML+css+mysql实现用户的登录,注册
web简易开发——通过php与HTML+css+mysql实现用户的登录,注册