I want to create a screen for the coach mark. Idea is to blur and make darker everything except the region where is my icon located.
I could cut a circle with feather edges. But the icon on the 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 a highlighted icon in a 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.
@Marica Hopefully this is doing what you want.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With