How to Implementing Circular Clip Route Transitions in Flutter ?

Spread the love

example screen recording

Introduction

The circular_clip_route package provides an easy way to create circular clipping effects for route transitions in Flutter. This can add a visually appealing and unique touch to your app’s navigation.

Content

1.Add the circular_clip_route dependency:

Open your pubspec.yaml file and add the circular_clip_route dependency.

 

2.Import the package:

Import the circular_clip_route package in your Dart file.


import 'package:circular_clip_route/circular_clip_route.dart';

 

3.Create a Circular Clip Route:

Use the CircularClipRoute class to create a circular clip route.


Navigator.of(context).push(
  CircularClipRoute(
    builder: (context) => YourNextScreen(),
    animationDuration: Duration(milliseconds: 500),
    curve: Curves.easeInOut,
    reverseCurve: Curves.easeInOut,
    clipColor: Colors.white,
  ),
);

The builder parameter should return the widget for the next screen.

Adjust the animationDuration, curve, reverseCurve, and clipColor parameters to customize the route transition animation

 

4.Customize the Circular Clip Transition:

You can further customize the circular clip transition by providing a custom clipper.


Navigator.of(context).push(
  CircularClipRoute(
    builder: (context) => YourNextScreen(),
    clipper: (animation, child) => MyCustomClipper(animation, child),
    // Other parameters...
  ),
);

 

5.Handle Backward Navigation:

When navigating back, the circular clip effect should also be applied. You can achieve this by using the CircularClipRoute for popping the route.


Navigator.of(context).pop(
  CircularClipRoute(
    builder: (context) => YourPreviousScreen(),
    // Other parameters...
  ),
);

 

6.Run the app:

Run your Flutter app to see the circular clip route transitions in action.

Sample Code


import 'package:flutter/material.dart';

import 'package:circular_clip_route/circular_clip_route.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ContactListPage(),
    );
  }
}

class ContactInfo {
  const ContactInfo({
    required this.avatarAsset,
    required this.name,
    required this.email,
    required this.phone,
  });

  final String avatarAsset;
  final String name;
  final String email;
  final String phone;
}

const contactInfos = [
  ContactInfo(
    avatarAsset: 'https://images.pexels.com/photos/771742/pexels-photo-771742.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
    name: 'Tom Arbo',
    email: 'tom.arbo@example.com',
    phone: '(711) 265-9193',
  ),
  ContactInfo(
    avatarAsset: 'https://images.pexels.com/photos/771742/pexels-photo-771742.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
    name: 'Elly Foster',
    email: 'elly@example.com',
    phone: '(675) 844-7400',
  ),
  ContactInfo(
    avatarAsset: 'https://images.pexels.com/photos/771742/pexels-photo-771742.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
    name: 'Carolyn Durnham',
    email: 'carolyn.durnham@example.com',
    phone: '(995) 565-4039',
  ),
  ContactInfo(
    avatarAsset: 'https://images.pexels.com/photos/771742/pexels-photo-771742.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
    name: 'Corrina Nicholls',
    email: 'c.nicholls@example.com',
    phone: '(966) 291-5045',
  ),
  ContactInfo(
    avatarAsset: 'https://images.pexels.com/photos/771742/pexels-photo-771742.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
    name: 'Omer Anderson',
    email: 'omer@example.net',
    phone: '(519) 978-4733',
  ),
];

class ContactListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Contacts'),
      ),
      body: ListView.builder(
        itemBuilder: (context, i) => ContactListItem(contactInfo: contactInfos[i]),
        itemCount: contactInfos.length,
      ),
    );
  }
}

class ContactListItem extends StatefulWidget {
  const ContactListItem({
    required this.contactInfo,
  });

  final ContactInfo contactInfo;

  @override
  _ContactListItemState createState() => _ContactListItemState();
}

class _ContactListItemState extends State<ContactListItem> {
  final _avatarKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: SizedBox(
        key: _avatarKey,
        width: 50,
        height: 50,
        child: AvatarHero(contactInfo: widget.contactInfo),
      ),
      title: Text(widget.contactInfo.name),
      subtitle: Text(widget.contactInfo.email),
      onTap: () {
        Navigator.push(
          context,
          CircularClipRoute<void>(
            builder: (context) => ContactDetailPage(contactInfo: widget.contactInfo),
            expandFrom: _avatarKey.currentContext!,
            curve: Curves.fastOutSlowIn,
            reverseCurve: Curves.fastOutSlowIn.flipped,
            opacity: ConstantTween(1),
            transitionDuration: const Duration(seconds: 1),
          ),
        );
      },
    );
  }
}

class AvatarHero extends StatelessWidget {
  final ContactInfo contactInfo;

  const AvatarHero({
    required this.contactInfo,
  });

  @override
  Widget build(BuildContext context) {
    final child = Container(
      decoration: const BoxDecoration(
        shape: BoxShape.circle,
        boxShadow: [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 8,
            offset: Offset(0, 3),
          ),
        ],
      ),
      child: Container(
        decoration: const BoxDecoration(
          shape: BoxShape.circle,
        ),
        clipBehavior: Clip.antiAlias,
        child: Image.network(
          contactInfo.avatarAsset,
          fit: BoxFit.cover,
        ),
      ),
    );

    return Hero(
      tag: 'image_${contactInfo.hashCode}',
      createRectTween: (begin, end) {
        return RectTween(
          begin: Rect.fromCenter(
            center: begin!.center,
            width: begin.width,
            height: begin.height,
          ),
          end: Rect.fromCenter(
            center: end!.center,
            width: end.width,
            height: end.height,
          ),
        );
      },
      child: child,
    );
  }
}

class ContactDetailPage extends StatefulWidget {
  const ContactDetailPage({
    required this.contactInfo,
  });

  final ContactInfo contactInfo;

  @override
  _ContactDetailPageState createState() => _ContactDetailPageState();
}

class _ContactDetailPageState extends State<ContactDetailPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.contactInfo.name),
        elevation: 0,
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          FractionallySizedBox(
            widthFactor: 1,
            child: Container(
              alignment: Alignment.center,
              child: Stack(
                alignment: Alignment.topCenter,
                children: [
                  Container(
                    height: 140,
                    color: Colors.blue,
                  ),
                  Container(
                    height: 220,
                    width: 220,
                    padding: const EdgeInsets.all(20.0),
                    child: AvatarHero(contactInfo: widget.contactInfo),
                  ),
                ],
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 20.0),
            child: Table(
              columnWidths: const {
                0: IntrinsicColumnWidth(flex: 1),
                1: FlexColumnWidth(99),
              },
              textBaseline: TextBaseline.alphabetic,
              defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
              children: [
                _buildTableRow(label: 'Email', value: widget.contactInfo.email),
                _buildTableRow(label: 'Phone', value: widget.contactInfo.phone),
              ],
            ),
          ),
        ],
      ),
    );
  }

  TableRow _buildTableRow({required String label, required String value}) {
    return TableRow(
      children: [
        IntrinsicWidth(
          child: Container(
            padding: const EdgeInsets.all(10),
            alignment: Alignment.centerRight,
            child: Text(
              '$label:',
              style: Theme.of(context).textTheme.bodyText1,
            ),
          ),
        ),
        Text(value),
      ],
    );
  }
}

Output

example screen recording

Conclusion

By following these steps, you can easily integrate the circular_clip_route package into your Flutter app and create circular clipping effects for route transitions. This can enhance the visual appeal of your app’s navigation.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *