skeleton_tint 0.2.0
skeleton_tint: ^0.2.0 copied to clipboard
Color-matched skeleton loading widgets for Flutter that match the color of the content they replace.
import 'package:flutter/material.dart';
import 'package:skeleton_tint/skeleton_tint.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: _DemoScreen());
}
}
class _DemoScreen extends StatefulWidget {
const _DemoScreen();
@override
State<_DemoScreen> createState() => _DemoScreenState();
}
class _DemoScreenState extends State<_DemoScreen> {
SkeletonStyle _style = SkeletonStyle.shimmer;
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Skeleton Loading Demo'),
elevation: 0,
bottom: const TabBar(
tabs: [
Tab(text: 'Loading'),
Tab(text: 'Loaded'),
],
),
),
body: Column(
children: [
_StylePicker(
selected: _style,
onSelected: (style) => setState(() => _style = style),
),
Expanded(
child: TabBarView(
children: [
_DemoList(loading: true, style: _style),
_DemoList(loading: false, style: _style),
],
),
),
],
),
),
);
}
}
class _StylePicker extends StatelessWidget {
const _StylePicker({required this.selected, required this.onSelected});
final SkeletonStyle selected;
final ValueChanged<SkeletonStyle> onSelected;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 56,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: SkeletonStyle.values.length,
separatorBuilder: (context, index) => const SizedBox(width: 8),
itemBuilder: (context, index) {
final style = SkeletonStyle.values[index];
return ChoiceChip(
label: Text(style.name),
selected: style == selected,
onSelected: (_) => onSelected(style),
);
},
),
);
}
}
class _DemoList extends StatelessWidget {
const _DemoList({required this.loading, required this.style});
final bool loading;
final SkeletonStyle style;
@override
Widget build(BuildContext context) {
return Skeleton(
loading: loading,
style: style,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_UserCard(),
const SizedBox(height: 16),
_BlogPostCard(),
const SizedBox(height: 16),
_ProductCard(),
],
),
);
}
}
class _UserCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
SkeletonImage(
image: const NetworkImage('https://picsum.photos/64/64?random=1'),
borderRadius: BorderRadius.circular(32),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SkeletonText(
child: Text(
'Sarah Anderson',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
const SizedBox(height: 8),
SkeletonText(
child: Text(
'@sarahdesigns',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.grey[600]),
),
),
],
),
),
],
),
),
);
}
}
class _BlogPostCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SkeletonImage(
image: const NetworkImage(
'https://picsum.photos/280/140?random=2',
),
borderRadius: BorderRadius.circular(8),
),
const SizedBox(height: 16),
SkeletonText(
child: Text(
'The Future of Flutter Design Patterns',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
const SizedBox(height: 8),
SkeletonText(
child: Text(
'Learn about the latest design patterns and best practices '
'for building scalable Flutter applications.',
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: Colors.grey[700]),
),
),
const SizedBox(height: 12),
SkeletonBox(
child: Container(
width: 100,
height: 32,
alignment: Alignment.center,
color: Colors.blue.shade50,
child: const Text('\$149.99'),
),
),
],
),
),
);
}
}
class _ProductCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
SkeletonImage(
image: const NetworkImage('https://picsum.photos/80/80?random=3'),
borderRadius: BorderRadius.circular(4),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SkeletonText(
child: Text(
'Premium Wireless Headphones',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
const SizedBox(height: 4),
SkeletonText(
child: Text(
'⭐ 4.8 (2,341 reviews)',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.amber[700]),
),
),
const SizedBox(height: 8),
SkeletonBox(
child: Container(
width: 70,
height: 28,
alignment: Alignment.center,
color: Colors.green[50],
child: const Text('In Stock'),
),
),
],
),
),
],
),
),
);
}
}