Flutter开发常见问题解决方案与实用技巧

Viewed 0

一、访问加速

1. 编译时加速

在安卓编译时,Gradle会下载依赖,默认的Google仓库在国内访问较慢。可以通过修改$ROOT_PATH/android/build.gradle文件,将仓库替换为阿里云镜像以提升速度。

buildscript {
    ext.kotlin_version = '1.6.21'
    repositories {
        maven { url 'https://maven.aliyun.com/repository/jcenter' }
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/central' }
        maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.4.0-rc03'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

2. Flutter的Dart仓库访问加速

设置国内Flutter镜像可以显著加快flutter pub get的速度。可以通过以下命令设置环境变量,或将其写入.zshrc(或.bashrc)文件后重启终端。

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

二、常见实现

1. 取消点击的水波效果

在iOS平台或某些场景下,可能需要取消Material组件默认的水波纹效果。可以通过修改主题实现。

theme: ThemeData(
  highlightColor: Colors.transparent,
  splashColor: Colors.transparent
),

2. 修改TabBar指示器的宽度为固定值

Flutter自带的TabBar指示器宽度通常与标签文本或标签本身等宽。若需要固定宽度的指示器(例如宽度为20的逻辑像素),可以自定义一个Decoration

import 'package:flutter/material.dart';

class FixedUnderlineTabIndicator extends Decoration {
  const FixedUnderlineTabIndicator({
    this.width = 20,
    this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
    this.insets = EdgeInsets.zero,
  });

  final BorderSide borderSide;
  final EdgeInsetsGeometry insets;
  final double width;

  @override
  Decoration? lerpFrom(Decoration? a, double t) {
    if (a is FixedUnderlineTabIndicator) {
      return FixedUnderlineTabIndicator(
        borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
        insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
      );
    }
    return super.lerpFrom(a, t);
  }

  @override
  Decoration? lerpTo(Decoration? b, double t) {
    if (b is FixedUnderlineTabIndicator) {
      return FixedUnderlineTabIndicator(
        borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
        insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
      );
    }
    return super.lerpTo(b, t);
  }

  @override
  BoxPainter createBoxPainter([VoidCallback? onChanged]) {
    return _FixedUnderlinePainter(this, onChanged);
  }

  Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
    final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
    return Rect.fromLTWH(
      indicator.left + (indicator.width - width) / 2,
      indicator.bottom - borderSide.width,
      width,
      borderSide.width,
    );
  }

  @override
  Path getClipPath(Rect rect, TextDirection textDirection) {
    return Path()..addRect(_indicatorRectFor(rect, textDirection));
  }
}

class _FixedUnderlinePainter extends BoxPainter {
  _FixedUnderlinePainter(this.decoration, VoidCallback? onChanged) : super(onChanged);
  final FixedUnderlineTabIndicator decoration;

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    assert(configuration.size != null);
    final Rect rect = offset & configuration.size!;
    final TextDirection textDirection = configuration.textDirection!;
    final Rect indicator = decoration._indicatorRectFor(rect, textDirection).deflate(decoration.borderSide.width / 2.0);
    final Paint paint = decoration.borderSide.toPaint()..strokeCap = StrokeCap.square;
    canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
  }
}

使用时,在TabBarindicator属性中传入自定义的FixedUnderlineTabIndicator即可。

3. 修改状态栏样式

若需将状态栏背景色设置为白色,可以在runApp之后调用系统方法。

void main() {
    runApp(const MyApp());
    SystemChrome.setSystemUIOverlayStyle(
        const SystemUiOverlayStyle(statusBarColor: Colors.white));
}

另一种方式是通过全局主题设置AppBar的样式,这可以统一管理所有页面的状态栏。

appBarTheme: const AppBarTheme(
    elevation: 0,
    color: Colors.white,
    foregroundColor: Colors.black,
    titleTextStyle: TextStyle(color: Colors.black, fontSize: 20),
    systemOverlayStyle: SystemUiOverlayStyle(
        statusBarColor: Colors.white,
        statusBarIconBrightness: Brightness.dark // 浅色背景用深色图标
    )
);

4. 隐藏Debug标识

在开发模式下,Flutter App右上角会有一个“Debug”横幅。要在MaterialApp中隐藏它,设置如下属性:

debugShowCheckedModeBanner: false

5. 设置AppBar高度

AppBar组件本身不直接提供高度属性。如果需要自定义高度,可以使用PreferredSize组件进行包裹。

Widget build(BuildContext context) {
    return Scaffold(
        appBar: PreferredSize(
          preferredSize: const Size(double.infinity, 80), // 设置高度为80
          child: AppBar(
              title: Text(_tabs[_tabIndex]["name"]),
              backgroundColor: Colors.red,
              actions: [
                IconButton(
                    onPressed: () { context.push("/scan"); },
                    icon: const Icon(Icons.qr_code_scanner))
              ]),
        )
    );
}

6. 隐藏和显示状态栏

在某些全屏页面(如拍照页)可能需要隐藏状态栏。但需注意,Flutter提供的该方法在隐藏和显示时可能没有平滑的动画过渡,体验上可能有些卡顿,请谨慎使用。

// 隐藏状态栏
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);

// 显示状态栏(例如采用edgeToEdge模式)
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);

7. Positioned组件占满父容器宽度或高度

Positioned组件想水平撑满父容器(Stack),将leftright都设为0即可,设置width: double.infinity是无效的。同理,垂直撑满则需要设置topbottom为0。

Positioned(
    left: 0,
    right: 0,
    child: Container(
      color: Colors.red,
      child: Text("测试定位"),
    ))

8. 实现文本部分点击

使用RichTextTextSpan组合,可以实现在一段文本中让特定部分可点击。

RichText(
    text: TextSpan(
        style: TextStyle(
            color: const Color(0xff252525),
            height: 20 / 14,
            fontSize: 14), // 基础样式
        text: '上述是常见问题,如果您有新的疑问,请',
        children: [
            TextSpan(
                text: “点此反馈”,
                style: const TextStyle(color: Color(0xff2F92FF)),
                recognizer: TapGestureRecognizer()..onTap = () {
                    Get.toNamed(‘/feedback’); // 点击跳转
                }
            ),
            const TextSpan(text: “,我们会尽快联系您”)
        ]
    )
)

9. 防止圆角Container的子元素溢出

当为Container设置borderRadius圆角后,如果其子元素(如图片)超出边界,默认不会被裁剪。需要设置clipBehavior属性。

Container(
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(10),
  ),
  clipBehavior: Clip.hardEdge, // 关键属性,裁剪超出部分
  child: Image.network(...),
)

10. 自定义AppBar需实现PreferredSizeWidget

自定义的AppBar组件如果直接用在ScaffoldappBar属性中,可能会遇到类型错误。需要让自定义组件混入PreferredSizeWidget并实现preferredSize的getter。

class CustomAppBar extends StatelessWidget with PreferredSizeWidget {
  const CustomAppBar({super.key, this.title});
  final Widget? title;
  // ... 其他属性和build方法

  @override
  Widget build(BuildContext context) {
    return AppBar(title: title, ...);
  }

  @override // 必须实现此getter
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

11. 清除应用缓存

Flutter应用的缓存文件通常存储在应用的临时目录中。可以使用path_provider插件获取该目录并清除。

import 'package:path_provider/path_provider.dart';

_clearCache() async {
  final dir = await getTemporaryDirectory(); // 获取缓存目录
  dir.deleteSync(recursive: true); // 递归删除
}

注意:如果在该缓存目录中存储了应用运行所需的重要数据,则不应直接删除整个文件夹,而应遍历后选择性删除。

12. 在Flutter页面中展示Markdown

使用flutter_markdown插件可以方便地渲染Markdown内容。注意,该插件可能不支持内联HTML标签。

MarkdownBody(
    data: markdownContent,
    selectable: true, // 允许文本选择
    softLineBreak: true,
    styleSheet: MarkdownStyleSheet(
        p: TextStyle(fontSize: 15, color: Colors.grey[700])
    ),
    onTapLink: (text, href, title) {
        // 处理链接点击
        Get.toNamed(‘/webview?url=$href’);
    }
)

13. 生成带透明度的实际颜色

有两种方式为颜色添加透明度:

  1. Colors.red.withOpacity(0.16): 这种方式生成的颜色是真正半透明的,如果作为背景,会透出底部的内容。
  2. Color.alphaBlend(Colors.red.withOpacity(0.16), Colors.white): 这种方式是将半透明颜色与一个背景色(如白色)进行混合,生成一个不透明的新颜色,不会透出底部内容。

14. 为操作添加震动反馈

适当的触觉反馈可以提升用户体验。Flutter提供了HapticFeedback类来调用设备震动。

import ‘package:flutter/services.dart’;
// 中度震动反馈
HapticFeedback.mediumImpact();
// 也可使用 .lightImpact() 或 .heavyImpact()

15. 实现毛玻璃效果

使用BackdropFilter组件可以实现背景模糊的毛玻璃效果,常见于导航栏等元素。

ClipRect(
  child: BackdropFilter(
    filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), // 模糊度
    child: Container(
      decoration: BoxDecoration(
        color: Colors.white.withOpacity(0.4), // 半透明底色
      ),
      child: Text(“毛玻璃后的文字”),
    ),
  ),
)

三、常见问题

1. 组件回调参数不可省略

例如Switch组件的onChanged回调,即使你不使用value参数,也必须在声明时写上。

Switch(value: true, onChanged: (value) {}), // 正确
// Switch(value: true, onChanged: () {}), // 错误,会报类型不匹配

2. Android模拟器实用快捷键

  • 摇一摇(打开DevMenu):在模拟器窗口激活时,按 Ctrl + M (Windows/Linux) 或 Cmd + M (Mac)。

3. 折叠IDE控制台中冗长的调试输出

在Android Studio或IntelliJ IDEA中,可以折叠以特定模式开头的日志行,让自定义的print信息更清晰。

  1. 在Run/Debug控制台选中一条你想折叠的日志行中的特征字符串(如E/FrameEvents)。
  2. 右键点击,选择 “Fold lines like this” (或中文“像这样折叠行”)。

4. iOS真机调试LLDB警告

在iOS真机调试时,可能会遇到关于libobjc的LLDB警告,这可能会影响调试性能。通常可以通过删除Xcode的设备支持缓存来解决。

rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport

删除后,重新使用Xcode运行一次应用,之后再用Flutter命令或Android Studio运行即可。

四、常用插件推荐

1. 功能型插件

2. UI型插件

0 Answers