Created
June 6, 2020 18:49
-
-
Save liyuqian/65680f25573bb4339030ddecaf236232 to your computer and use it in GitHub Desktop.
TightClipper to avoid some AA artifacts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(IssueDemoApp()); | |
} | |
class IssueDemoApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData( | |
primarySwatch: Colors.indigo, | |
scaffoldBackgroundColor: Colors.grey[100], | |
buttonTheme: ButtonThemeData( | |
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.indigo), | |
textTheme: ButtonTextTheme.primary, | |
), | |
), | |
debugShowCheckedModeBanner: false, | |
home: ClipRectIssueDemo(), | |
); | |
} | |
} | |
class ClipRectIssueDemo extends StatefulWidget { | |
@override | |
State<StatefulWidget> createState() { | |
return _ClipRectIssueDemoState(); | |
} | |
} | |
class _ClipRectIssueDemoState extends State<ClipRectIssueDemo> { | |
bool _clipRectOn = false; | |
double _width = 219.0; | |
@override | |
Widget build(BuildContext context) { | |
final double pixelRatio = MediaQuery.of(context).devicePixelRatio; | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('ClipRect Issue Demo'), | |
centerTitle: true, | |
elevation: 0, | |
), | |
body: SingleChildScrollView( | |
child: Column( | |
children: [ | |
// And then some gap space too | |
const SizedBox(height: 20), | |
Text('ClipRect over Container with BoxShadow (pixel ratio: $pixelRatio)', | |
style: Theme.of(context).textTheme.headline6), | |
const SizedBox(height: 20), | |
SizedBox( | |
width: 420, | |
height: 420, | |
child: Center( | |
child: SizedBox( | |
height: _width, | |
width: _width, | |
child: ClipRectDecoratedContainer(clipRectOn: _clipRectOn), | |
), | |
), | |
), | |
const SizedBox(height: 10), | |
Center( | |
child: SizedBox( | |
width: 450, | |
child: SwitchListTile( | |
title: const Text('ClipRect ON/OFF'), | |
subtitle: const Text( | |
'Turn on ClipRect to see edge remnants. \nIf you resize ' | |
'window/media size or change container size, you can observe the edge ' | |
'remnants appearing and dissapearing at different sizes.'), | |
value: _clipRectOn, | |
onChanged: (value) { | |
setState(() { | |
_clipRectOn = value; | |
}); | |
}, | |
), | |
), | |
), | |
const SizedBox(height: 10), | |
Center( | |
child: SizedBox( | |
width: 450, | |
child: ListTile( | |
title: const Text('Change Container size'), | |
subtitle: Slider( | |
min: 100.0, | |
max: 400.0, | |
divisions: (400 - 100).floor(), | |
label: _width.floor().toString(), | |
value: _width, | |
onChanged: (value) { | |
setState(() { | |
_width = value; | |
}); | |
}, | |
), | |
trailing: Padding( | |
padding: const EdgeInsets.only(right: 12.0), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.end, | |
children: <Widget>[ | |
const Text( | |
'Width', | |
style: TextStyle(fontSize: 11), | |
), | |
Text( | |
_width.floor().toString(), | |
style: const TextStyle(fontSize: 15), | |
), | |
], | |
), | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class ClipRectDecoratedContainer extends StatelessWidget { | |
const ClipRectDecoratedContainer({ | |
Key key, | |
this.clipRectOn = false, | |
}) : super(key: key); | |
final bool clipRectOn; | |
@override | |
Widget build(BuildContext context) { | |
if (clipRectOn) { | |
return ClipRect( | |
clipBehavior: Clip.hardEdge, | |
clipper: TightClipper(MediaQuery.of(context).devicePixelRatio), | |
child: Container( | |
decoration: BoxDecoration( | |
color: Colors.white, | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.red[900], | |
blurRadius: 25.0, | |
) | |
], | |
), | |
), | |
); | |
} else { | |
return Container( | |
decoration: BoxDecoration( | |
color: Colors.white, | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.red[900], | |
blurRadius: 25.0, | |
) | |
], | |
), | |
); | |
} | |
} | |
} | |
class TightClipper extends CustomClipper<Rect> { | |
TightClipper(this.devicePixelRatio, {this.tightFactor = 1}); | |
@override | |
Rect getClip(Size size) { | |
final double padding = 1 / devicePixelRatio * tightFactor; | |
return Rect.fromLTRB( | |
padding, | |
padding, | |
size.width - padding, | |
size.height - padding, | |
); | |
} | |
@override | |
bool shouldReclip(CustomClipper<Rect> oldClipper) { | |
return true; | |
} | |
final double devicePixelRatio; | |
final double tightFactor; | |
} |
Ah yes. I'll revise this quick hack if we're going to put this piece of code in our doc, or code base.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In line 189: I would expect that you only need to reclip if the provided oldClipper has a different runtime type or if devicePixelRatio / tightFactor have changed.