Flutter总结:高斯模糊/毛玻璃实现
一、需求
开发中遇到组件动态化实现的需求,可以理解为 json 转 Widget,需要实现高斯模糊,前景和背景都需要实现同时支持。还需要同时支持内外边距,背景色,背景渐进色,背景图,透明度,圆角,边框,阴影等效果实现。
二、效果图
三、示例
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