s_liquid_pull_to_refresh 2.0.0 copy "s_liquid_pull_to_refresh: ^2.0.0" to clipboard
s_liquid_pull_to_refresh: ^2.0.0 copied to clipboard

A liquid/spring styled pull-to-refresh widget for Flutter with customizable animations.

example/lib/main.dart

import 'package:flutter/gestures.dart';
import 'package:s_liquid_pull_to_refresh/s_liquid_pull_to_refresh.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SLiquidPullToRefresh Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      scrollBehavior: MaterialScrollBehavior().copyWith(
        physics: BouncingScrollPhysics(),
        scrollbars: true,
        dragDevices: {
          PointerDeviceKind.mouse,
          PointerDeviceKind.touch,
          PointerDeviceKind.stylus,
          PointerDeviceKind.unknown,
          PointerDeviceKind.trackpad
        },
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const ExamplesListPage(),
    );
  }
}

/// Main page showing different example variations
class ExamplesListPage extends StatelessWidget {
  const ExamplesListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SLiquidPullToRefresh Examples'),
        centerTitle: true,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _ExampleCard(
            title: 'Basic Example',
            description: 'Simple pull-to-refresh with default settings',
            icon: Icons.refresh,
            color: Colors.blue,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const BasicExamplePage(),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _ExampleCard(
            title: 'Custom Colors',
            description: 'Customized colors and styling',
            icon: Icons.palette,
            color: Colors.purple,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const CustomColorsExamplePage(),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _ExampleCard(
            title: 'Fast Animation',
            description: 'Faster animation with custom speed',
            icon: Icons.speed,
            color: Colors.orange,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const FastAnimationExamplePage(),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _ExampleCard(
            title: 'Programmatic Trigger',
            description: 'Trigger refresh with a button',
            icon: Icons.touch_app,
            color: Colors.green,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const ProgrammaticExamplePage(),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _ExampleCard(
            title: 'GridView Example',
            description: 'Using with GridView scrollable',
            icon: Icons.grid_view,
            color: Colors.teal,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const GridViewExamplePage(),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _ExampleCard extends StatelessWidget {
  final String title;
  final String description;
  final IconData icon;
  final Color color;
  final VoidCallback onTap;

  const _ExampleCard({
    required this.title,
    required this.description,
    required this.icon,
    required this.color,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 2,
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: color.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(icon, color: color, size: 32),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: Theme.of(context).textTheme.titleMedium?.copyWith(
                            fontWeight: FontWeight.bold,
                          ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      description,
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ],
                ),
              ),
              const Icon(Icons.chevron_right),
            ],
          ),
        ),
      ),
    );
  }
}

/// Basic example with default settings
class BasicExamplePage extends StatefulWidget {
  const BasicExamplePage({super.key});

  @override
  State<BasicExamplePage> createState() => _BasicExamplePageState();
}

class _BasicExamplePageState extends State<BasicExamplePage> {
  final List<int> _items = List.generate(20, (index) => index);
  int _refreshCount = 0;

  Future<void> _handleRefresh() async {
    await Future.delayed(const Duration(seconds: 2));
    setState(() {
      _refreshCount++;
      _items.insert(0, _refreshCount);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Basic Example'),
      ),
      body: SLiquidPullToRefresh(
        onRefresh: _handleRefresh,
        child: ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          padding: const EdgeInsets.all(16),
          itemCount: _items.length,
          itemBuilder: (context, index) {
            return Card(
              margin: const EdgeInsets.only(bottom: 12),
              child: ListTile(
                leading: CircleAvatar(child: Text('${_items[index]}')),
                title: Text('Item ${_items[index]}'),
                subtitle: Text('Pull down to refresh • Count: $_refreshCount'),
              ),
            );
          },
        ),
      ),
    );
  }
}

/// Example with custom colors
class CustomColorsExamplePage extends StatefulWidget {
  const CustomColorsExamplePage({super.key});

  @override
  State<CustomColorsExamplePage> createState() =>
      _CustomColorsExamplePageState();
}

class _CustomColorsExamplePageState extends State<CustomColorsExamplePage> {
  final List<String> _items = List.generate(15, (i) => 'Message ${i + 1}');

  Future<void> _handleRefresh() async {
    await Future.delayed(const Duration(seconds: 2));
    setState(() {
      _items.insert(0, 'New Message ${_items.length + 1}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Custom Colors'),
        backgroundColor: Colors.purple,
        foregroundColor: Colors.white,
      ),
      body: SLiquidPullToRefresh(
        onRefresh: _handleRefresh,
        height: 150,
        color: Colors.deepPurple,
        backgroundColor: Colors.white,
        borderWidth: 4.0,
        child: ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          padding: const EdgeInsets.all(16),
          itemCount: _items.length,
          itemBuilder: (context, index) {
            return Container(
              margin: const EdgeInsets.only(bottom: 12),
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: [Colors.deepPurple.shade100, Colors.purple.shade50],
                ),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(
                _items[index],
                style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w500,
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

/// Example with fast animation
class FastAnimationExamplePage extends StatefulWidget {
  const FastAnimationExamplePage({super.key});

  @override
  State<FastAnimationExamplePage> createState() =>
      _FastAnimationExamplePageState();
}

class _FastAnimationExamplePageState extends State<FastAnimationExamplePage> {
  final List<String> _news = List.generate(
    10,
    (i) => 'News Article ${i + 1}',
  );

  Future<void> _handleRefresh() async {
    await Future.delayed(const Duration(milliseconds: 1500));
    setState(() {
      _news.insert(0, 'Breaking News ${_news.length + 1}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Fast Animation')),
      body: SLiquidPullToRefresh(
        onRefresh: _handleRefresh,
        animSpeedFactor: 2.0,
        springAnimationDurationInMilliseconds: 600,
        height: 100,
        color: Colors.orange,
        child: ListView.separated(
          physics: const AlwaysScrollableScrollPhysics(),
          padding: const EdgeInsets.all(16),
          itemCount: _news.length,
          separatorBuilder: (_, __) => const Divider(),
          itemBuilder: (context, index) {
            return ListTile(
              leading: const Icon(Icons.article, color: Colors.orange),
              title: Text(_news[index]),
              subtitle: Text(DateTime.now().toString().split('.')[0]),
              trailing: const Icon(Icons.chevron_right),
            );
          },
        ),
      ),
    );
  }
}

/// Example with programmatic trigger
class ProgrammaticExamplePage extends StatefulWidget {
  const ProgrammaticExamplePage({super.key});

  @override
  State<ProgrammaticExamplePage> createState() =>
      _ProgrammaticExamplePageState();
}

class _ProgrammaticExamplePageState extends State<ProgrammaticExamplePage> {
  final _refreshKey = GlobalKey<SLiquidPullToRefreshState>();
  final List<String> _tasks = List.generate(12, (i) => 'Task ${i + 1}');
  bool _isRefreshing = false;

  Future<void> _handleRefresh() async {
    setState(() => _isRefreshing = true);
    await Future.delayed(const Duration(seconds: 2));
    setState(() {
      _tasks.insert(0, 'New Task ${_tasks.length + 1}');
      _isRefreshing = false;
    });
  }

  void _triggerRefresh() {
    _refreshKey.currentState?.show();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Programmatic Trigger')),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.green.shade50,
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    'Tap the button to trigger refresh programmatically',
                    style: TextStyle(color: Colors.green.shade900),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton.icon(
                  onPressed: _isRefreshing ? null : _triggerRefresh,
                  icon: const Icon(Icons.refresh),
                  label: const Text('Refresh'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green,
                    foregroundColor: Colors.white,
                  ),
                ),
              ],
            ),
          ),
          Expanded(
            child: SLiquidPullToRefresh(
              key: _refreshKey,
              onRefresh: _handleRefresh,
              color: Colors.green,
              child: ListView.builder(
                physics: const AlwaysScrollableScrollPhysics(),
                padding: const EdgeInsets.all(16),
                itemCount: _tasks.length,
                itemBuilder: (context, index) {
                  return CheckboxListTile(
                    value: false,
                    onChanged: (_) {},
                    title: Text(_tasks[index]),
                    secondary: const Icon(Icons.task_alt),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Example with GridView
class GridViewExamplePage extends StatefulWidget {
  const GridViewExamplePage({super.key});

  @override
  State<GridViewExamplePage> createState() => _GridViewExamplePageState();
}

class _GridViewExamplePageState extends State<GridViewExamplePage> {
  List<Color> _colors = [
    Colors.red,
    Colors.blue,
    Colors.green,
    Colors.orange,
    Colors.purple,
    Colors.teal,
    Colors.pink,
    Colors.indigo,
  ];

  Future<void> _handleRefresh() async {
    await Future.delayed(const Duration(seconds: 2));
    setState(() {
      _colors = [
        Colors.primaries[_colors.length % Colors.primaries.length],
        ..._colors,
      ];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('GridView Example')),
      body: SLiquidPullToRefresh(
        onRefresh: _handleRefresh,
        color: Colors.teal,
        showChildOpacityTransition: false,
        child: GridView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          padding: const EdgeInsets.all(16),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            crossAxisSpacing: 12,
            mainAxisSpacing: 12,
          ),
          itemCount: _colors.length,
          itemBuilder: (context, index) {
            return Container(
              decoration: BoxDecoration(
                color: _colors[index],
                borderRadius: BorderRadius.circular(12),
                boxShadow: [
                  BoxShadow(
                    color: _colors[index].withValues(alpha: 0.3),
                    blurRadius: 8,
                    offset: const Offset(0, 4),
                  ),
                ],
              ),
              child: Center(
                child: Text(
                  '${index + 1}',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}
1
likes
160
points
6
downloads

Publisher

unverified uploader

Weekly Downloads

A liquid/spring styled pull-to-refresh widget for Flutter with customizable animations.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, s_packages

More

Packages that depend on s_liquid_pull_to_refresh