I am trying to build a custom AppBar with a progress bar underneath it. I use different colored an animated Containers with rounded borders. The progress bar should have a height of 6 px. And it should look like this.
But I encounter the following problem: The black Container mostly overlaps the progress bar on different screen sizes as the SafeArea changes and the IconButton (back arrow) changes size.
So I would like to get a better way to find out how much size the black Container with the Row will need so I can set the progress bar Container's height accordingly.
My solution so far is setting up a WidgetsBinding.instance.addPostFrameCallback where I calculate the size of the row using a GlobalKey.
This is the code I use in my custom flutter hook to get the size with the GlobalKey:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
PositionAndSize useGetSize(GlobalKey k) {
return use(_SizeHook(k));
}
class PositionAndSize {
PositionAndSize(this.position, this.size);
Offset position;
Size size;
}
class _SizeHook extends Hook<PositionAndSize> {
const _SizeHook(this.k);
final GlobalKey k;
@override
_SizeHookState createState() => _SizeHookState();
}
class _SizeHookState extends HookState<PositionAndSize, _SizeHook> {
late PositionAndSize positionAndSize =
PositionAndSize(Offset.zero, const Size(0, 0));
void calculateSizeAndPosition() {
final RenderBox renderBox =
hook.k.currentContext!.findRenderObject() as RenderBox;
var size = renderBox.size;
var offset = renderBox.localToGlobal(Offset.zero);
positionAndSize = PositionAndSize(offset, size);
}
@override
void initHook() {
super.initHook();
WidgetsBinding.instance.addPostFrameCallback((_) {
calculateSizeAndPosition();
});
}
@override
PositionAndSize build(BuildContext context) {
return positionAndSize;
}
@override
void dispose() {
super.dispose();
}
}
Here is the code of my CustomAppBar:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spendtrend/shared/models/size_hook.dart';
class ProgressHeader extends HookWidget implements PreferredSizeWidget {
ProgressHeader(
{super.key, required this.title, required this.begin, required this.end})
: preferredSize = const Size.fromHeight(64),
rowSizeKey = GlobalKey();
final String title;
final double begin;
final double end;
final GlobalKey rowSizeKey;
@override
final Size preferredSize;
@override
Widget build(BuildContext context) {
var rowSize = useGetSize(rowSizeKey);
double headerSize = MediaQuery.of(context).padding.top +
rowSize.size.height +
8 + // padding bottom
6; // size of progress bar
return Container(
constraints: BoxConstraints(maxHeight: headerSize),
decoration: BoxDecoration(
color: const Color.fromRGBO(170, 217, 149, 1),
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(20),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.16),
blurRadius: 4,
offset: const Offset(0, 3),
),
BoxShadow(
color: Colors.black.withOpacity(.16),
blurRadius: 6,
offset: const Offset(0, 6),
)
],
),
child: Stack(
children: [
// active progress bar
Container(
decoration: const BoxDecoration(
color: Color.fromRGBO(127, 181, 103, 1),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
),
),
),
Container(
decoration: const BoxDecoration(
color: Colors.black,
borderRadius:
BorderRadius.vertical(bottom: Radius.circular(25))),
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
bottom: 8,
),
child: SafeArea(
bottom: false,
child: Row(
key: rowSizeKey,
children: [
IconButton(
iconSize: 18,
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.pop(context);
}),
const SizedBox(
width: 16,
),
Text(
title,
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(color: Colors.white),
),
],
),
),
),
)
],
),
);
}
}
Is there a better way to achieve this or am I missing something when using Flutter? I am still quite new to this framework.
I took a deeper look into your question and I dont think you have to do anything complicated like calculate height before rendering
How does my solution work out for you? https://dartpad.dev/?id=efb667ce370926d82d5ec9ef245a696e
class ProgressHeader extends StatelessWidget implements PreferredSizeWidget {
ProgressHeader({
super.key,
required this.title,
}) : preferredSize = const Size.fromHeight(64),
rowSizeKey = GlobalKey();
final String title;
final GlobalKey rowSizeKey;
@override
final Size preferredSize;
@override
Widget build(BuildContext context) {
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(26),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.16),
blurRadius: 4,
offset: const Offset(0, 3),
),
BoxShadow(
color: Colors.black.withOpacity(.16),
blurRadius: 6,
offset: const Offset(0, 6),
)
],
),
child: Stack(
children: [
LinearProgressIndicator(
backgroundColor: const Color.fromRGBO(170, 217, 149, 1),
color: Color.fromRGBO(127, 181, 103, 1),
minHeight: preferredSize.height,
),
Positioned.fill(
bottom: 6,
child: AppBar(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
),
backgroundColor: Colors.black,
foregroundColor: Colors.white,
title: Text(title),
),
)
],
),
);
}
}