Flutter - How to get a semitransparent blurring layer with a hole with soft edges.

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP

Flutter - How to get a semitransparent blurring layer with a hole with soft edges.



I want to create screen for coach mark. Idea is to blur and make darker everything except region where is my icon located.



I could cut circle with feather edges. But icon on background is also blurred.


import 'dart:ui' as ui;
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());

class MyApp extends StatefulWidget
@override
State<StatefulWidget> createState() => MyAppState();


class MyAppState extends State<MyApp>
@override
Widget build(BuildContext context)
return new MaterialApp(
title: 'Flutter Demo',
home: HomeScreen(),
);



class HomeScreen extends StatefulWidget
@override
_HomeScreenState createState() => new _HomeScreenState();


class _HomeScreenState extends State<HomeScreen>
@override
Widget build(BuildContext context)
return Stack(children: <Widget>[
_buildScaffold(),
CustomPaint(
child: Container(
constraints: BoxConstraints.expand(),
child: BackdropFilter(
filter: new ui.ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
decoration: new BoxDecoration(
color: Colors.grey[900].withOpacity(0.7)),
))),
foregroundPainter: CoachMarksPainter(),
),
]);


Widget _buildScaffold()
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
actions: <Widget>[
new IconButton(
onPressed: () => print("press"),
icon: new Icon(Icons.calendar_today),
),
PopupMenuButton<String>(
itemBuilder: (BuildContext context) ,
),
],
),
body: new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new NetworkImage(
"http://www.mobileswall.com/wp-content/uploads/2015/03/640-Sunset-Beach-2-l.jpg"),
fit: BoxFit.cover))));



class CoachMarksPainter extends CustomPainter
void paint(Canvas canvas, Size size)
print("Paint size=$size canvas=$canvas.getSaveCount()");
canvas.save();

Path path = Path()
..addOval(Rect.fromCircle(center: Offset(287.0, 52.0), radius: 25.0))
..addRect(new Rect.fromLTWH(
-10.0, -10.0, size.width + 20.0, size.height + 20.0))
//to have rect a bit larger than screen, so blurred edges won't be seen
..fillType = PathFillType.evenOdd;

Paint paint = Paint()
..blendMode = BlendMode.dstOut
..color = Colors.white.withOpacity(0.4)
..maskFilter = new MaskFilter.blur(
BlurStyle.normal, 2.0); //BoxShadow.convertRadiusToSigma(25.0)

canvas.drawPath(path, paint);
canvas.restore();


@override
bool shouldRepaint(CoachMarksPainter oldDelegate) => false;
@override
bool shouldRebuildSemantics(CoachMarksPainter oldDelegate) => false;



blurred background with highlighted icon in circle



Is it possible to use ImageFilter.blur for Canvas? I use MaskFilter, but it does not blur canvas as much as ImageFilter for BackdropFilter widget.
Ideally I want to get a semitransparent blurring layer with a hole with soft edges.



P.S. I read this question but I need to invert it.





@rmtmckenzie Hi! I saw your answer in similar question stackoverflow.com/questions/49374893/flutter-inverted-clipoval. I hope you can help me with this one.
– Marica
yesterday





2 Answers
2



@Marica Hopefully this is doing what you want.



screen capture



https://gist.github.com/slightfoot/76043f8f3fc4a8b20fc24c5a6f22b0a0


import 'dart:async';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget
@override
State<StatefulWidget> createState() => MyAppState();


class MyAppState extends State<MyApp>
@override
Widget build(BuildContext context)
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Coach Mark Demo',
home: HomeScreen(),
);



class HomeScreen extends StatefulWidget
@override
_HomeScreenState createState() => _HomeScreenState();


class _HomeScreenState extends State<HomeScreen>
final GlobalKey<ScaffoldState> _scaffold = GlobalKey();
final GlobalKey<CoachMarkState> _calendarMark = GlobalKey();

@override
Widget build(BuildContext context)
return Scaffold(
key: _scaffold,
appBar: AppBar(
title: Text("Hello"),
actions: <Widget>[
CoachMark(
key: _calendarMark,
id: 'calendar_mark',
text: 'Tap here to use the Calendar!',
child: GestureDetector(
onLongPress: () => _calendarMark.currentState.show(),
child: IconButton(
onPressed: () => print('calendar'),
icon: Icon(Icons.calendar_today),
),
),
),
PopupMenuButton<String>(
itemBuilder: (BuildContext context)
return <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'reset',
child: Text('Reset'),
),
];
,
onSelected: (String value)
if (value == 'reset')
_calendarMark.currentState.reset();
_scaffold.currentState.showSnackBar(SnackBar(
content: Text('Hot-restart the app to see the coach-mark again.'),
));

,
),
],
),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("http://www.mobileswall.com/wp-content/uploads/2015/03/640-Sunset-Beach-2-l.jpg"),
fit: BoxFit.cover),
),
),
);



class CoachMark extends StatefulWidget
const CoachMark(
Key key,
@required this.id,
@required this.text,
@required this.child,
) : super(key: key);

final String id;
final String text;
final Widget child;

@override
CoachMarkState createState() => CoachMarkState();


typedef CoachMarkRect = Rect Function();

class CoachMarkState extends State<CoachMark>
_CoachMarkRoute _route;

String get _key => 'mark_$widget.id';

@override
void initState()
super.initState();
test().then((bool seen)
if (seen == false)
show();

);


@override
void didUpdateWidget(CoachMark oldWidget)
super.didUpdateWidget(oldWidget);
_rebuild();


@override
void reassemble()
super.reassemble();
_rebuild();


@override
void dispose()
dismiss();
super.dispose();


@override
Widget build(BuildContext context)
_rebuild();
return widget.child;


void show()
if (_route == null)
_route = _CoachMarkRoute(
rect: ()
final box = context.findRenderObject() as RenderBox;
return box.localToGlobal(Offset.zero) & box.size;
,
text: widget.text,
padding: EdgeInsets.all(4.0),
onPop: ()
_route = null;
mark();
,
);
Navigator.of(context).push(_route);



void _rebuild()
if (_route != null)
WidgetsBinding.instance.addPostFrameCallback((_)
_route.changedExternalState();
);



void dismiss()
if (_route != null)
_route.dispose();
_route = null;



Future<bool> test() async
return (await SharedPreferences.getInstance()).getBool(_key) ?? false;


void mark() async
(await SharedPreferences.getInstance()).setBool(_key, true);


void reset() async
(await SharedPreferences.getInstance()).remove(_key);



class _CoachMarkRoute<T> extends PageRoute<T>
_CoachMarkRoute(
@required this.rect,
@required this.text,
this.padding,
this.onPop,
this.shadow = const BoxShadow(color: const Color(0xB2212121), blurRadius: 8.0),
this.maintainState = true,
this.transitionDuration = const Duration(milliseconds: 450),
RouteSettings settings,
) : super(settings: settings);

final CoachMarkRect rect;
final String text;
final EdgeInsets padding;
final BoxShadow shadow;
final VoidCallback onPop;

@override
final bool maintainState;

@override
final Duration transitionDuration;

@override
bool didPop(T result)
onPop();
return super.didPop(result);


@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation)
Rect position = rect();
if (padding != null)
position = padding.inflateRect(position);

position = Rect.fromCircle(center: position.center, radius: position.longestSide * 0.5);
final clipper = _CoachMarkClipper(position);
return Material(
type: MaterialType.transparency,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (d) => Navigator.of(context).pop(),
child: IgnorePointer(
child: FadeTransition(
opacity: animation,
child: Stack(
children: <Widget>[
ClipPath(
clipper: clipper,
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
color: Colors.transparent,
),
),
),
CustomPaint(
child: SizedBox.expand(
child: Center(
child: Text(text,
style: const TextStyle(
fontSize: 22.0,
fontStyle: FontStyle.italic,
color: Colors.white,
)),
),
),
painter: _CoachMarkPainter(
rect: position,
shadow: shadow,
clipper: clipper,
),
),
],
),
),
),
),
);


@override
bool get opaque => false;

@override
Color get barrierColor => null;

@override
String get barrierLabel => null;


class _CoachMarkClipper extends CustomClipper<Path>
final Rect rect;

_CoachMarkClipper(this.rect);

@override
Path getClip(Size size)
return Path.combine(PathOperation.difference, Path()..addRect(Offset.zero & size), Path()..addOval(rect));


@override
bool shouldReclip(_CoachMarkClipper old) => rect != old.rect;


class _CoachMarkPainter extends CustomPainter
_CoachMarkPainter(
@required this.rect,
@required this.shadow,
this.clipper,
);

final Rect rect;
final BoxShadow shadow;
final _CoachMarkClipper clipper;

void paint(Canvas canvas, Size size)
final circle = rect.inflate(shadow.spreadRadius);
canvas.saveLayer(Offset.zero & size, Paint());
canvas.drawColor(shadow.color, BlendMode.dstATop);
canvas.drawCircle(circle.center, circle.longestSide * 0.5, shadow.toPaint()..blendMode = BlendMode.clear);
canvas.restore();


@override
bool shouldRepaint(_CoachMarkPainter old) => old.rect != rect;

@override
bool shouldRebuildSemantics(_CoachMarkPainter oldDelegate) => false;





Simon, thanks a lot! A few questions: 1) We need to wrap icon to CoachMark in order to get it position. I guess it is also possible just pass position to _CoachMarkRoute and push it from my screen when conditions are pass. And I need somehow to get know position of icon (maybe using after layout). But your solution is more elegant and flexible. 2) Why do you need a _route variable in CoachMarkState? I don't see where it is assigned a value. 3) What does _route.changedExternalState(); do?
– Marica
yesterday


CoachMark


_CoachMarkRoute


_route


CoachMarkState


_route.changedExternalState();





1) Yes you can just pass the rect to the route, or just pass the context of any Widget. 2) Ah, that explains why a bug I had exists. I'll update the code. 3) The idea is to have it update the rect when the widget changes.
– Simon
23 hours ago





@Marica updated to fix the blurred background.
– Simon
3 mins ago



I'm not sure I understand the question. It seems that what you want would be achievable by using 3 layers in a stack. Lowest is your background, second is the darker frosted blur and put your icon on top.



Am I misunderstanding something?





I start with icon. But it can be another part of UI in the scaffold. I don't want to draw it again. I don't know exact position of all elements. So it should be 2 layers - scaffold on background and dark frosted blur with hole.
– Marica
2 days ago






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Comments

Popular posts from this blog

Executable numpy error

Trying to Print Gridster Items to PDF without overlapping contents

Hystrix command on request collapser fallback