Transparent background behind ClipPath - flutter

So, I'm pretty new to flutter, I'm trying to have a AppBar that looks like this :
And I'm actually able to do it using ClipPath like this :
class Header extends StatelessWidget {
final String page;
const Header({required this.page});
#override
Widget build(BuildContext context) {
final double topPadding = MediaQuery.of(context).padding.top;
return ClipPath(
child: HeaderContent(page: page, padding: topPadding),
clipper: Clipper(page: page));
}
}
class HeaderContent extends StatelessWidget {
final String page;
final double padding;
const HeaderContent({required this.page, required this.padding});
#override
Widget build(BuildContext context) {
final int headerHeight = page == "inscription" ? 250 : 150;
return Container(
height: headerHeight + padding,
padding: EdgeInsets.all(15),
width: double.infinity,
color: Theme.of(context).primaryColor,
child: AppBar(
title: Text("NOTIFICATIONS", style: Theme.of(context).textTheme.headline6),
centerTitle: true,
leading: Icon(
Icons.arrow_back,
size: 40,
),
),
);
}
}
class Clipper extends CustomClipper<Path> {
final String page;
const Clipper({required this.page});
#override
Path getClip(Size size) {
final Path path = Path();
selectHeaderClip(size, page, path);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Where the selectHeaderClip is a custom function that return the appropriate clip for a given page.
However, when I have a scrollable page (list of notifications for example), I see the notifications hiding too early as if there was a white rectangular container underneath my appBar. So my question basically is: how can I make that container transparent ?
Ps: sorry for the bad formulation of the question, don't hesitate to edit.

Related

How to Make Widget Flutter Fast?

I'm trying to make my own photo codecs, I made a 512 * 512 image,
I'm just trying to build with one color and arrange in a Container in Column and Row
My Code:
SizedBox(
height: 512,
width: 512,
child: Column(
children: List.generate(512, (index) {
return Row(
children: List.generate(512, (index) {
return Container(
height: 1,
width: 1,
color: Colors.blue,
);
}),
);
}),
),
),
I tried this code, it is very slow,
So how to build flutter widget fast?
You have to use CustomPainter class to draw your own custom widget
As said by powerman23rus said, you should use a CustomPainter, here's an example of implementation based on the code you've provided:
class ImageWidget extends StatelessWidget {
final Color color;
final Size size;
const ImageWidget({
super.key,
this.color = Colors.blue,
this.size = const Size(512, 512),
});
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: ImagePainter(color: color),
size: size,
);
}
}
class ImagePainter extends CustomPainter {
final Color color;
ImagePainter({required this.color});
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawRect(Offset.zero & size, paint);
}
#override
bool shouldRepaint(ImagePainter oldDelegate) => false;
}
You can try the full example on DartPad to check by yourself the performance.

CustomPainter together with Paint()..blendMode gives weird blinking effect while scrolling

After launching the application, the green circle is not visible. In order for the circle to appear, you need to scroll up. Then if you scroll down, the circle starts blinking as long as the scrolloffset is within about 20 pixels. If the scrolloffset is larger than 20 pixels, the circle disappears and appears only when the scrolloffset is 0.
If wrap the widget in RepaintBoundary, then the circle appears on any scroll and then does not disappear.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class DemoPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.save();
final path = Path()..addOval(Rect.fromCircle(center: Offset.zero, radius: 50));
final paint = Paint()..color = Colors.green;
paint.blendMode = BlendMode.dstOver;
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
class TestWidget extends StatelessWidget {
const TestWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: 80,
child: Center(
child: CustomPaint(
painter: DemoPainter(),
child: Text(
"Child widget",
style: TextStyle(color: Colors.blue[200], fontWeight: FontWeight.bold, fontSize: 20),
),
),
));
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
const TestWidget(),
const TestWidget(),
const TestWidget(),
const TestWidget(),
const RepaintBoundary(child: TestWidget()),
Container(
height: 2000,
color: Colors.red,
)
],
),
),
),
);
}
}
The link to Dartpad, but i can't reproduce the effect on the web platform.
What could be the reason for such effect and how to get rid of it?

Flutter How to get size of dynamic widget

What I've done:
The black rectangle is the size of the canvas.
const double radius = 50;
class TableShape extends StatelessWidget {
final String name;
final Color color;
const TableShape({
Key? key,
required this.name,
required this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap:(){debugPrint("ok");},
child: LayoutBuilder(
builder: (context, constraints) {
final maxWidth = constraints.maxWidth;
final textPainter = TextPainter(
text: TextSpan(
text: name,
style: const TextStyle(fontFamily: 'Graphik', fontSize: 30, color: Colors.white),
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center
);
textPainter.layout(maxWidth: maxWidth);
return CustomPaint(
size: Size(textPainter.width>radius*2?textPainter.width:radius*2, radius*2),
painter: MyPainter(color: color, txt: textPainter),
);
})
);
}
}
class MyPainter extends CustomPainter {
TextPainter txt;
Color color;
MyPainter({
required this.txt,
required this.color,
});
#override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height));
var paint = Paint()..color = color;
bool txtLarger = txt.width>radius*2;
canvas.drawCircle(Offset(txtLarger?txt.width/2:radius,radius), radius, paint);
//table name:
txt.paint(canvas, Offset(txtLarger?0:radius-txt.width/2,radius-txt.height/2));
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
#override
bool hitTest(Offset position) {
return sqrt(pow(txt.width/2-position.dx,2)+pow(radius-position.dy,2)) <= radius;
}
}
I need to get the width because I place the widget on my screen according to its width. The width is dynamic: the bigger the text, the wider the canvas. Is it possible ? Or maybe you have an other approach to get this widget than the way I did ?
get widget size by global key:
final GlobalKey _widgetKey = GlobalKey();
Size _getSize(GlobalKey key){
final State state = key.currentState;
final BuildContext context = key.currentContext;
final RenderBox box = state.context.findRenderObject();
return context.size;
}
Widget build(BuildContext context) {
return GestureDetector(
key: _widgetKey,
onTap:(){_getSize(_widgetKey);},
child: LayoutBuilder(
builder: (context, constraints) {
Use GlobalKey to find RenderBox then get the size. Remember you need to make sure the widget was rendered.
Example:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) => const MaterialApp(home: Home());
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
var key = GlobalKey();
Size? redboxSize;
#override
void initState() {
WidgetsBinding.instance?.addPostFrameCallback((_) {
setState(() {
redboxSize = getRedBoxSize(key.currentContext!);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Example')),
body: Column(
children: [
SizedBox(
height: 100,
child: Center(
child: Container(
key: key,
child: const Text('Hello oooooooooooooooo'),
color: Colors.redAccent,
),
),
),
if (redboxSize != null) Text('Redbox size: $redboxSize')
],
),
);
}
Size getRedBoxSize(BuildContext context) {
final box = context.findRenderObject() as RenderBox;
return box.size;
}
}

Flutter: Canvas gives error on setState - painting random circles after a condition is satisfied

I'm trying to do the following in my code:
the user can click on the plus button and increment the counter.
if the counter is more than or equal to 4, the user can no longer press the plus button, since the absorb pointer, turns it's absorb field to true.
after the point where the button cannot be pressed, on each tap, the canvas changes the offset and the color of the circle and then, the build widget rebuilds the entire page (there is a GestureDetector widget as the parent of the AbsorbPointer widget to check taps on the entire screen and set new offset and color to the circle.)
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool absorbPointer = false;
Color color = Colors.red;
Offset offset = const Offset(0,0);
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _setRandomCircle(double maxHeight, double maxWidth) {
var rnd = Random();
double randWidth = rnd.nextInt(maxWidth.toInt() - 0).toDouble();
double randHeight = rnd.nextInt(maxHeight.toInt() - 0).toDouble();
offset = Offset(randWidth, randHeight);
}
void _setRandomColor() {
color = Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0);
}
void _setAbsorbPointer(){
absorbPointer = true;
setState(() {});
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
double maxWidth = size.width;
double maxHeight = size.height;
return GestureDetector(
onTap: () {
if (_counter < 4) {
_incrementCounter();
} else {
_setRandomColor();
_setRandomCircle(maxHeight, maxWidth);
_setAbsorbPointer();
}
},
child: AbsorbPointer(
absorbing: absorbPointer,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title),
),
body: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
CustomPaint(
painter: MyPainter(
offsetOfCircle: offset,
colorOfCircle: color,
),
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
),
),
);
}
}
My problem:
Nothing seems to work properly and I get these errors:
"UnimplementedError"
"Each child must be laid out exactly once."
with these details:
The _ScaffoldLayout custom multichild layout delegate forgot to lay out the following child:
_ScaffoldSlot.body: RenderErrorBox#f9667 NEEDS-LAYOUT NEEDS-PAINT
parentData: offset=Offset(0.0, 0.0); id=_ScaffoldSlot.body
constraints: MISSING
size: MISSING
And this is Mypainter:
class MyPainter extends CustomPainter {
final Color colorOfCircle;
final Offset offsetOfCircle;
MyPainter({required this.colorOfCircle, required this.offsetOfCircle});
#override
void paint(Canvas canvas, Size size) {
var hexColor = "0x${colorOfCircle.value.toRadixString(16)}";
var myCustomPaint = Paint()..color = Color(int.parse(hexColor));
canvas.drawCircle(offsetOfCircle, 20, myCustomPaint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
}
The code structure you are following, it is little different. You can do it the way you described, you dont need to use extra AbsorbPointer widget tap event.
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool absorbPointer = false;
Color color = Colors.red;
Offset offset = const Offset(0, 0);
void _setRandomCircleColorAndPosition(double maxHeight, double maxWidth) {
double randWidth = math.Random().nextDouble() * (maxWidth * .9);
double randHeight = math.Random().nextDouble() * (maxHeight * .5);
setState(() {
offset = Offset(randWidth, randHeight);
color = Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0);
});
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
double maxWidth = size.width;
double maxHeight = size.height;
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title),
),
body: GestureDetector(
onTap: () {
_setRandomCircleColorAndPosition(maxHeight, maxWidth);
},
child: Container(
color: Colors.cyanAccent.withOpacity(.3),
child: Stack(
children: [
Align(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
CustomPaint(
painter: MyPainter(
offsetOfCircle: offset,
colorOfCircle: color,
),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_counter >= 4) {
absorbPointer = true;
_setRandomCircleColorAndPosition(maxHeight, maxWidth);
} else {
_counter++;
}
setState(() {});
},
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyPainter extends CustomPainter {
Color colorOfCircle;
Offset offsetOfCircle;
MyPainter({required this.colorOfCircle, required this.offsetOfCircle});
#override
void paint(Canvas canvas, Size size) {
var myCustomPaint = Paint()..color = colorOfCircle;
canvas.drawCircle(offsetOfCircle, 20, myCustomPaint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

Animate widget alignment

I'm building a custom flexible app bar to use in a NestedScrollView and i'm running into issues with the animation.
What I want to achieve is something like this:
In the expanded state, the text is aligned with the top of the Profile picture (in orange), but when the bar collapse, it ends up aligned in the center. I also need all the elements (text + picture) to scale accordingly.
I have access to the current expand factor of the bar using a LayoutBuilder and a bit of math
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double paddingTop = MediaQuery.of(context).padding.top;
double maxExtent = kExpandedHeight + paddingTop;
double minExtent = kToolbarHeight + paddingTop;
final double deltaExtent = maxExtent - minExtent;
// 0.0 -> Expanded
// 1.0 -> Collapsed to toolbar
final double t = (1.0 - (constraints.maxHeight - minExtent) / deltaExtent)
.clamp(0.0, 1.0);
// t can be used to animate here
});
I have managed to scale elements with the Transform widget and the value of t but what I can't figure out is how to animate the switch of alignment of the text part so that it end up perfectly aligned in the center with the picture.
Any ideas? :)
try this,
class Act_Demo extends StatefulWidget {
#override
_Act_DemoState createState() => _Act_DemoState();
}
class _Act_DemoState extends State<Act_Demo> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: CustomScrollView(
slivers: <Widget>[
TransitionAppBar(
backgroundColor: Colors.red,
extent: 150,
avatar: ListTile(
title: Text("Name", style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),),
subtitle: Text("abc#gmail.com"),
trailing: CircleAvatar(backgroundColor: Colors.orange,radius: 30.0,),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
child: ListTile(
title: Text("${index}a"),
));
}, childCount: 25))
],
),
),
);
}
}
.
class TransitionAppBar extends StatelessWidget {
final Widget avatar;
final double extent;
final Color backgroundColor;
TransitionAppBar({this.avatar, this.backgroundColor = Colors.transparent, this.extent = 200, Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SliverPersistentHeader(
pinned: true,
delegate: _TransitionAppBarDelegate(
avatar: avatar,
backgroundColor: backgroundColor,
extent: extent > 150 ? extent : 150
),
);
}
}
class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
final _avatarAlignTween = AlignmentTween(begin: Alignment.center, end: Alignment.topCenter);
final Widget avatar;
final double extent;
final Color backgroundColor;
_TransitionAppBarDelegate({this.avatar, this.backgroundColor, this.extent = 200})
: assert(avatar != null),
assert(backgroundColor != null),
assert(extent == null || extent >= 150);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final progress = shrinkOffset / maxExtent;
final avatarAlign = _avatarAlignTween.lerp(progress);
return Container(
color: backgroundColor,
child: Align(
alignment: avatarAlign,
child: Container(
child: avatar,
),
),
);
}
#override
double get maxExtent => extent;
#override
double get minExtent => 70;
#override
bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {
return avatar != oldDelegate.avatar;
}
}