Flutter:使用图像作为屏幕背景

我本来想用渐变色 做屏幕背景,结果它给我报了个"shader 编译时间太长"的错误 (或警告?)。主要是我的安卓设备 GPU 性能太弱了

所以我就开始琢磨:"干脆直接用图片来做背景,不是更好吗?"


关于性能(Performance)

很明显,我们不能用那种又大、分辨率又高 的图片来做背景。下面我给的两个例子,用的都是 10KB 大小的 WebP 格式图片

从 assets 里加载一个 10KB 的 WebP 图,对性能的影响可以忽略不计 ,尤其如果我们在应用启动时就把它预加载好的话。

不过,第一次加载时可能还是会有一点感觉 ,所以我们最好把 ColorScheme 里的 surface color (表面颜色)设置成跟背景图的主色调接近


看看例子(Examples)

这里有两个注册页面的例子,分别展示了在浅色和深色的图片背景下的效果。

ColorScheme (配色方案)是由 ChatGPT 生成 的。一个是为了 surface color (表面颜色)是 pink.shade100 ,第二个是为了 surface colorgrey.shade800

有点太花哨了 (或者说太活泼了 )不合我的口味,但就像我说的,这得怪 ChatGPT 。而且,这里的重点不是按钮的颜色背景 才是重点。背景看起来效果不错

显然,我们应该把图片放到 assets 文件夹里:

并且在 pubspec.yaml 文件中提及路径

我是从 Canva 获取我的图片的,但其实你可以随便找一张照片 ,给它做个模糊效果加一个半透明图层 ,然后以中等质量 保存成 WebP 格式就行了。

以下就是本文的主要核心代码BgScaffold

dart 复制代码
import 'package:flutter/material.dart';
import 'package:getx_miscellanous/app/data/memory_settings_service.dart';

class BgScaffold  extends StatelessWidget {
  final Widget? body;
  final PreferredSizeWidget? appBar;
  final Widget? floatingActionButton;
  final FloatingActionButtonLocation? floatingActionButtonLocation;
  final Widget? bottomNavigationBar;
  final Widget? drawer;
  final Widget? endDrawer;
  final String? lightBackgroundImagePath;
  final String? darkBackgroundImagePath;

  const BgScaffold({
    super.key,
    this.body,
    this.appBar,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.bottomNavigationBar,
    this.drawer,
    this.endDrawer,
    this.lightBackgroundImagePath,
    this.darkBackgroundImagePath,
  }) : assert(
          lightBackgroundImagePath != null || darkBackgroundImagePath != null,
          'At least one background image path must be provided',
        );

  @override
  Widget build(BuildContext context) {
    cacheImages(context, darkBackgroundImagePath, lightBackgroundImagePath);
    ThemeData theme = Theme.of(context);
    
    final isDark = theme.brightness == Brightness.dark;
    final imagePath = isDark
        ? (darkBackgroundImagePath ?? lightBackgroundImagePath!)
        : (lightBackgroundImagePath ?? darkBackgroundImagePath!);
    final loadingColor = Theme.of(context).colorScheme.surface;

    return Scaffold(
      backgroundColor: Colors.transparent,
      extendBodyBehindAppBar: true,
      appBar: appBar,
      drawer: drawer,
      endDrawer: endDrawer,
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      bottomNavigationBar: bottomNavigationBar,
      body: Stack(
        children: [
          // Background image container with loading color
          Container(
            width: double.infinity,
            height: double.infinity,
            color: loadingColor,
            child: Image.asset(
              imagePath,
              fit: BoxFit.cover,
              cacheWidth: null,
              cacheHeight: null,
              frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
                if (wasSynchronouslyLoaded || frame != null) {
                  return child;
                }
                return Container(color: loadingColor);
              },
              errorBuilder: (context, error, stackTrace) {
                return Container(color: loadingColor);
              },
            ),
          ),
          // Actual body content
          if (body != null) body!,
        ],
      ),
    );
  }

  void cacheImages(
    BuildContext context,
    String? darkBackgroundImagePath,
    String? lightBackgroundImagePath,
  ) {
    if (MemorySettingsService().bgImagesCached){
      return;
    }
    if (darkBackgroundImagePath != null){
      precacheImage(AssetImage(darkBackgroundImagePath), context);
    }
    if (lightBackgroundImagePath != null){
      precacheImage(AssetImage(lightBackgroundImagePath), context);
    }
    MemorySettingsService().bgImagesCached = true;
  }
}

请注意 extendBodyBehindAppBar 这个属性。我以前不知道它有这个功能。

下面是我们如何使用 BgScaffold 的方法:

dart 复制代码
   return BgScaffold(
      darkBackgroundImagePath: 'assets/images/background/black_mramor.webp',
      lightBackgroundImagePath: 'assets/images/background/light_pink_flower.webp',
      appBar: AppBar(
        title: const Text('Registration'),
        centerTitle: true,
        backgroundColor: Colors.transparent,
      ),
      body: RegistrationPage(),
    );

与普通的 Scaffold 唯一的区别是,我们提供了背景图片的路径

frameBuilder 是一个回调函数 ,每当 Flutter 解码图像的一帧 时,它就会被调用。它主要用于两个目的:显示加载状态处理动画

以下是每个参数的含义:

dart 复制代码
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {  
// context - 标准的 BuildContext 
// child - 正在被解码的实际的 Image 组件 
// frame - 我们正在处理的是第几帧(0, 1, 2...),如果尚未加载,则为 null 
// wasSynchronouslyLoaded - 如果图像是从缓存加载的,则为 true;如果正在从磁盘/网络加载,则为 false
}

在我们的代码中:

dart 复制代码
if (wasSynchronouslyLoaded || frame != null) {
  return child;
}
return Container(color: loadingColor);

这段代码的意思是:"如果图片已经在缓存中 (即 wasSynchronouslyLoaded 为真),或者 我们至少解码了一帧 (即 frame != null ),就显示图片 。否则,就显示加载颜色。"

关键在于,对于已经预缓存asset 图片 来说,wasSynchronouslyLoaded 几乎总是 true ,所以加载颜色很少会显示 。这其实就是我们想要的效果------瞬间显示

我们在每一次构建时 都调用 cacheImages 方法,这效率上有点低 ,但(除了第一次之外)我们所做的只是检查 MemorySettingsService().bgImagesCached 这个变量 。我选择这种方式是为了让 BgScaffold 保持自包含(self-contained)。

或者 ,我们可以在应用启动时就缓存图片:

dart 复制代码
return MaterialApp(
      home: Builder(
        builder: (context) {
          cacheImages(context, darkBackgroundImagePath, 
                               lightBackgroundImagePath);
          
          return RegistrationScreen();
        },
      ),

并且,这样可能 就能摆脱 使用 MemorySettingsService().bgImagesCached 这个变量了。因为 MaterialApp 组件很少会被重建

无论是采用哪种方法,实际的图片加载和缓存都只会发生一次 ,所以两者之间并没有太大的区别

这就是我今天想分享的所有内容了。

感谢您的阅读!

相关推荐
工藤学编程7 小时前
零基础学AI大模型之CoT思维链和ReAct推理行动
前端·人工智能·react.js
徐同保7 小时前
上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片
前端·javascript·pdf
怕浪猫7 小时前
React从入门到出门第四章 组件通讯与全局状态管理
前端·javascript·react.js
欧阳天风7 小时前
用setTimeout代替setInterval
开发语言·前端·javascript
EndingCoder7 小时前
箭头函数和 this 绑定
linux·前端·javascript·typescript
郑州光合科技余经理7 小时前
架构解析:同城本地生活服务o2o平台海外版
大数据·开发语言·前端·人工智能·架构·php·生活
沐墨染7 小时前
大型数据分析组件前端实践:多维度检索与实时交互设计
前端·elementui·数据挖掘·数据分析·vue·交互
xkxnq7 小时前
第一阶段:Vue 基础入门(第 11 天)
前端·javascript·vue.js
lifejump7 小时前
Pikachu | Unsafe Filedownload
前端·web安全·网络安全·安全性测试
Irene19917 小时前
CSS新属性分类总结(2020年后引入)
前端·css