Skeleton
A pulsing placeholder in the shape of content still loading.
import 'package:flutter/widgets.dart';
import 'ui/skeleton.dart';
class SkeletonDemo extends StatelessWidget {
const SkeletonDemo({super.key});
@override
Widget build(BuildContext context) => const Row(
mainAxisSize: MainAxisSize.min,
children: [
Skeleton(width: 48, height: 48, circle: true),
SizedBox(width: 16),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [Skeleton(width: 220), SizedBox(height: 8), Skeleton(width: 160)],
),
],
);
}
Try its options
Each option its constructor takes, to change and see; the code below it is what you chose, to copy.
Add it
In your Flutter project:
ashlar add skeleton
It writes theme.dart, palette.dart and skeleton.dart into lib/ui, keeping any you already have.
Examples
Other ways to use it, each running live, with its code.
A card loading
Grey shapes where the content will be.
// A card loading: Grey shapes where the content will be.
import 'package:flutter/widgets.dart';
import 'ui/skeleton.dart';
class SkeletonCardExample extends StatelessWidget {
const SkeletonCardExample({super.key});
@override
Widget build(BuildContext context) => const SizedBox(
width: 320,
child: Row(
children: [
Skeleton(width: 48, height: 48, circle: true),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [Skeleton(width: 180), SizedBox(height: 8), Skeleton(width: 120)],
),
),
],
),
);
}
API
Read from the source above: each parameter with its type, its default, and what it is for.
Skeletonclass
A placeholder in the shape of content that is still loading. It pulses, unless the device asks for less motion.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
width |
double? |
Null fills the width it is given. | |
height | double | 16 | Its height. |
circle |
bool |
false |
Round, for where an avatar will be. |
Source
What lands in your app. It is yours from then on: change it as you would your own code, and ashlar add
will not write over it.
lib/ui/skeleton.dart 71 lines
// Ashlar UI: Skeleton. Copied into your app by `ashlar add skeleton`; it is yours to change.
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// A placeholder in the shape of content that is still loading. It pulses, unless the
/// device asks for less motion.
///
/// ```dart
/// Row(children: [Skeleton(width: 40, height: 40, circle: true), SizedBox(width: 12), Skeleton(width: 160, height: 16)])
/// ```
class Skeleton extends StatefulWidget {
const Skeleton({
super.key,
this.width,
this.height = 16,
this.circle = false,
});
/// Null fills the width it is given.
final double? width;
/// Its height.
final double height;
/// Round, for where an avatar will be.
final bool circle;
@override
State<Skeleton> createState() => _SkeletonState();
}
class _SkeletonState extends State<Skeleton>
with SingleTickerProviderStateMixin {
late final _pulse = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
)..repeat(reverse: true);
@override
void dispose() {
_pulse.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final t = AshlarTheme.of(context);
final still = MediaQuery.maybeDisableAnimationsOf(context) ?? false;
final box = Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
color: t.colors.accent,
shape: widget.circle ? BoxShape.circle : BoxShape.rectangle,
borderRadius: widget.circle ? null : BorderRadius.circular(t.radiusMd),
),
);
return ExcludeSemantics(
child: still
? box
: FadeTransition(
opacity: Tween(begin: 1.0, end: .5).animate(
CurvedAnimation(parent: _pulse, curve: Curves.easeInOut),
),
child: box,
),
);
}
}