likes
comments
collection
share

Flutter总结:高斯模糊/毛玻璃实现

作者站长头像
站长
· 阅读数 3

一、需求

开发中遇到组件动态化实现的需求,可以理解为 json 转 Widget,需要实现高斯模糊,前景和背景都需要实现同时支持。还需要同时支持内外边距,背景色,背景渐进色,背景图,透明度,圆角,边框,阴影等效果实现。

二、效果图

Flutter总结:高斯模糊/毛玻璃实现

三、示例

flutter中模糊是通过背景滤镜 BackdropFilter 和前景滤镜 ImageFiltered 结合实现

1、示例页面
//
//  FilterDemo.dart
//  flutter_templet_project
//
//  Created by shang on 3/15/23 9:33 AM.
//  Copyright © 3/15/23 shang. All rights reserved.
//

import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter_templet_project/basicWidget/n_filter.dart';
import 'package:flutter_templet_project/basicWidget/n_slider.dart';


class FilterDemoOne extends StatefulWidget {

  FilterDemoOne({
    Key? key, 
    this.title
  }) : super(key: key);

  final String? title;

  @override
  _FilterDemoOneState createState() => _FilterDemoOneState();
}

class _FilterDemoOneState extends State<FilterDemoOne> {

  var imageFilteredVN = ValueNotifier(0.0);
  var backdropFilterVN = ValueNotifier(0.0);

  @override
  Widget build(BuildContext context) {
    dynamic arguments = ModalRoute.of(context)!.settings.arguments;

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title ?? "$widget"),
        actions: ['done',].map((e) => TextButton(
          child: Text(e,
            style: TextStyle(color: Colors.white),
          ),
          onPressed: () => print(e),)
        ).toList(),
      ),
      body: _buildBody(),
    );
  }

  _buildBody() {
    return Container(
      child: Center(
        child: Column(
          // mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            TextButton(
              onPressed: (){
                print("ElevatedButton");
              },
              child: Container(
                padding: EdgeInsets.symmetric(vertical: 6),
                child: Text("前景模糊 ImageFilter 显示在组件上边, \n背景模糊 BackdropFilter 显示在组件下边"),
              ),
            ),
            _buildNSlider(
              max: 20,
              leading: Text("前景模糊"),
              onChanged: (val) {
                imageFilteredVN.value = val;
              }
            ),
            _buildNSlider(
              max: 20,
              leading: Text("背景模糊"),
              onChanged: (val) {
                backdropFilterVN.value = val;
              }
            ),
            AnimatedBuilder(
              animation: Listenable.merge([
                imageFilteredVN,
                backdropFilterVN
              ]),
              builder: ( context, child) {

                return Stack(
                  alignment: AlignmentDirectional.center,
                  children: [
                    Text("01" * 399),
                    NFilter(
                      foregroundFilter: ui.ImageFilter.blur(
                        sigmaX: imageFilteredVN.value,
                        sigmaY: imageFilteredVN.value,
                      ),
                      filter: ui.ImageFilter.blur(
                        sigmaX: backdropFilterVN.value,
                        sigmaY: backdropFilterVN.value,
                      ),
                      child: Image.asset(
                        'images/404.png',
                        fit: BoxFit.cover,
                        width: 200.0,
                        height: 120.0,
                      ),
                    ),
                  ],
                );
              }
            )
          ],
        ),
      ),
    );
  }

  _buildNSlider({
    double max = 100,
    Widget? leading,
    ValueChanged<double>? onChanged
  }) {
    return NSlider(
      leading: leading,
      max: max,
      onChanged: onChanged,
      trailingBuilder: (context, value) {
        // final result = (value/100).toStringAsFixed(2);
        final result = "${value.toStringAsFixed(0)}";

        return TextButton(
          onPressed: () {
            print(result);
          },
          child: Text(result),
        );
      },
    );
  }
}

四、组件源码

1、NSlider - 自定义 Slider 组件封住,增加首尾组件,尾部组件实时显示当前数值;
//
//  NSlider.dart
//  flutter_templet_project
//
//  Created by shang on 3/22/23 11:09 AM.
//  Copyright © 3/22/23 shang. All rights reserved.
//

import 'package:flutter/material.dart';

typedef ValueChangedWidgetBuilder<T> = Widget Function(BuildContext context, T value);

/// 自定义 Slider 组件封住,增加首尾组件,尾部组件实时显示当前数值;
class NSlider extends StatefulWidget {

  NSlider({
    Key? key, 
    this.title,
    this.leading,
    this.trailingBuilder,
    this.onChanged,
    this.onChangeStart,
    this.onChangeEnd,
    this.value = 0,
    this.min = 0.0,
    this.max = 1.0,
    this.divisions = 100,
    this.label,
    this.activeColor,
    this.inactiveColor,
    this.thumbColor,
    this.mouseCursor,
    this.semanticFormatterCallback,
    this.focusNode,
    this.autofocus = false,
  }) : super(key: key);

  String? title;
  Widget? leading;

  ValueChangedWidgetBuilder<double>? trailingBuilder;

  ValueChanged<double>? onChanged;

  ValueChanged<double>? onChangeStart;

  ValueChanged<double>? onChangeEnd;

  double? value;

  double min;

  double max;

  int? divisions;

  String? label;

  Color? activeColor;

  Color? inactiveColor;

  Color? thumbColor;

  MouseCursor? mouseCursor;

  SemanticFormatterCallback? semanticFormatterCallback;

  FocusNode? focusNode;

  bool autofocus;

  @override
  _NSliderState createState() => _NSliderState();
}

class _NSliderState extends State<NSlider> {

  var sliderVN = ValueNotifier(0.0);

  @override
  void initState() {
    // TODO: implement initState
    sliderVN.value = widget.value ?? 0;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return _buildSlider();
  }

  _buildSlider() {
    return Row(
      children: [
        if (widget.leading != null) widget.leading!,
        Expanded(
          child: StatefulBuilder(
            builder: (context, setState) {
              return Slider(
                value: sliderVN.value,
                onChanged: (double value) {
                  sliderVN.value = value;
                  widget.onChanged?.call(value);
                  setState(() {});
                },
                onChangeStart: widget.onChangeStart,
                onChangeEnd: widget.onChangeEnd,
                min: widget.min,
                max: widget.max,
                divisions: widget.divisions,
                label: widget.label,
                activeColor: widget.activeColor,
                inactiveColor: widget.inactiveColor,
                thumbColor: widget.thumbColor,
                mouseCursor: widget.mouseCursor,
                semanticFormatterCallback: widget.semanticFormatterCallback,
                focusNode: widget.focusNode,
                autofocus: widget.autofocus,
              );
            }
          ),
        ),
        ValueListenableBuilder<double>(
          valueListenable: sliderVN,
          builder: (context, value, child) {

            final result = widget.max > 1 ? value.toStringAsFixed(0) : value.toStringAsFixed(2);
            return widget.trailingBuilder?.call(context, value) ?? TextButton(
              onPressed: () { print(result); },
              child: Text(result),
            );
          }
        ),
      ],
    );
  }
}
2、自定义滤镜组件, 支持前后滤镜
//
//  n_filter.dart
//  flutter_templet_project
//
//  Created by shang on 3/15/23 10:48 AM.
//  Copyright © 3/15/23 shang. All rights reserved.
//

import 'dart:ui' as ui;

import 'package:flutter/material.dart';

/// 自定义滤镜组件, 支持前后滤镜
class NFilter extends StatelessWidget {

  NFilter({
    Key? key,
    this.title,
    this.borderRadius = BorderRadius.zero,
    this.clipper,
    this.clipBehavior = Clip.antiAlias,
    this.foregroundFilter,
    this.filter,
    this.child,
  }) : super(key: key);

  String? title;

  BorderRadius borderRadius;

  CustomClipper<RRect>? clipper;

  Clip clipBehavior ;
  /// 前景滤镜
  ui.ImageFilter? foregroundFilter;
  /// 背景滤镜
  ui.ImageFilter? filter;

  Widget? child;


  @override
  Widget build(BuildContext context) {
    if ([foregroundFilter, filter].every((e) => e == null)) {
      return child ?? SizedBox();
    }

    return ClipRRect(
      borderRadius: borderRadius,
      clipper: clipper,
      clipBehavior: clipBehavior,
      child: BackdropFilter(
        filter: filter ?? ui.ImageFilter.blur(
          sigmaX: 0,
          sigmaY: 0,
        ),
        child: ImageFiltered(
          imageFilter: foregroundFilter ?? ui.ImageFilter.blur(
            sigmaX: 0,
            sigmaY: 0,
          ),
          child: child,
        ),
      ),
    );
  }
}

总结

1、高斯模糊只需要 ClipRRect + BackdropFilter + ImageFiltered 组件,即可实现;

2、使用 Slider 时,要避免全局刷新,建议用 ValueListenableBuilder 进行局部精准刷新;

3、NBox 组件同时支持内外边距,背景色,背景渐进色,背景图,透明度,圆角,边框,阴影等效果实现,见 Github

转载自:https://juejin.cn/post/7213277254184026169
评论
请登录