flutter_physics_ui 0.1.0
flutter_physics_ui: ^0.1.0 copied to clipboard
Physics-based UI elements that respond to touch and gravity
import 'package:flutter/material.dart';
import 'package:flutter_physics_ui/flutter_physics_ui.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(final BuildContext context) => MaterialApp(
title: 'Flutter Physics UI Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: const PhysicsFeaturesPage(),
);
}
class PhysicsFeaturesPage extends StatelessWidget {
const PhysicsFeaturesPage({super.key});
@override
Widget build(final BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Physics UI Features'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// 1. Gravity Container (Moved to Top)
const SectionHeader(title: '1. Gravity Container'),
const SizedBox(height: 8),
Container(
height: 300,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(
1,
), // Padding to keep physics inside border
child: ClipRect(
child: PhysicsContainer(
physics:
const PhysicsProperties(gravity: 300, restitution: 0.5),
child: Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
color: Colors.purpleAccent,
shape: BoxShape.circle,
),
child: const Center(
child: Icon(Icons.arrow_downward, color: Colors.white),
),
),
),
),
),
const SizedBox(height: 8),
const Text(
'The circle interacts with the physics world defined by the '
'container.',
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
const Divider(height: 48),
// 2. Physics Button
const SectionHeader(
title: '2. Physics Button (Restitution/Bounce)',
),
const SizedBox(height: 8),
Center(
child: SizedBox(
height: 120, // Providing height for the button to jump
width: 200, // Constraining width
child: PhysicsButton(
onPressed: () => debugPrint('Button Tapped'),
// Gravity enabled so it falls back down after jumping
physics:
const PhysicsProperties(restitution: 0.5, gravity: 300),
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
decoration: BoxDecoration(
color: Colors.blue, // Standard button color
borderRadius:
BorderRadius.circular(30), // Stadium/Pill shape
boxShadow: [
BoxShadow(
color: Colors.blue.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: const Text(
'Bounce Me!',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
const Divider(height: 48),
// 3. Draggable Card
const SectionHeader(title: '3. Draggable Card (Friction)'),
const SizedBox(height: 8),
Center(
child: Container(
// Visible Physics World
width: 300,
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400, width: 2),
borderRadius: BorderRadius.circular(12),
color: Colors.grey.shade50,
),
padding: const EdgeInsets.all(
2,
), // Padding to keep physics inside border
child: PhysicsCard(
// Gravity 0 for top-down view.
// High Restitution (0.9) to make it bounce off the walls!
physics: const PhysicsProperties(
friction: 0.05, // Low friction for sliding
mass: 5,
gravity: 0,
restitution: 0.9, // BOUNCE!
),
child: Container(
width: 140, // Smaller card to have room to move
height: 90,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: const Center(
child: Text(
'Throw Me!',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
),
],
),
);
}
class SectionHeader extends StatelessWidget {
const SectionHeader({required this.title, super.key});
final String title;
@override
Widget build(final BuildContext context) => Text(
title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
);
}