How to achieve some smoothness when rotating card in flutter

150 views Asked by At

I want to achieve this kind of swipe image 1

But when I designed I got this kind of output image 2

I want the smoothness while swiping and don't want to use any packages.

Any suggestion or help is appreciated.

I have tried using Transfor.rotate but there is no animation the results I got can be seen above

import 'package:flutter/material.dart';
import 'package:mawada/cardCollections/widgets/card_play_widget.dart';
import 'package:mawada/constants/app_color.dart';
import 'package:mawada/shared/widgets/app_button.dart';

class CardPlay extends StatefulWidget {
  const CardPlay({super.key});

  @override
  State<CardPlay> createState() => _CardPlayState();
}

class _CardPlayState extends State<CardPlay>
    with SingleTickerProviderStateMixin {
  final pageController = PageController(viewportFraction: 0.7);
  int _index = 0;
  late final AnimationController _animationController;

  int currentPage = 0;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: AppColor.background,
        appBar: AppBar(
          actions: [
            Padding(
              padding: const EdgeInsets.only(right: 10.0),
              child: Image.asset(
                'assets/logo2.png',
                fit: BoxFit.cover,
                height: 55,
              ),
            )
          ],
        ),
        body: ListView(
          padding: const EdgeInsets.symmetric(vertical: 30),
          children: [
            // Progress indiator
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 20),
              child: Container(
                decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(30),
                    boxShadow: [
                      BoxShadow(
                          offset: const Offset(0, 2),
                          color: Colors.grey.withOpacity(0.5),
                          spreadRadius: 1,
                          blurRadius: 2)
                    ]),
                child: LinearProgressIndicator(
                    value: 0.2,
                    semanticsLabel: "0.2",
                    backgroundColor: Colors.white,
                    valueColor: const AlwaysStoppedAnimation<Color>(
                      AppColor.primary,
                    ),
                    minHeight: 45,
                    borderRadius: BorderRadius.circular(30)),
              ),
            ),
            const SizedBox(
              height: 50,
            ),
            SizedBox(
              height: 400,
              child: PageView.builder(
                  // physics: PageScrollPhysics(),
                  onPageChanged: (int index) => setState(() => _index = index),
                  controller: pageController,
                  itemCount: 10,
                  itemBuilder: (context, i) {
                    final double rotation = (i - _index) == 1
                        ? 0.1
                        : (i - _index) == -1
                            ? -0.1
                            : 0.0;
                    return Center(
                        child: Transform.rotate(
                            filterQuality: FilterQuality.low,
                            angle: i == _index ? 0 : rotation,
                            // angle: i == currentPage ? _animationController.value * rotation : rotation,
                            child: i == _index
                                ? const Padding(
                                    padding: EdgeInsets.only(bottom: 30),
                                    child: CardPlayWidget(),
                                  )
                                : const CardPlayWidget()));
                  }),
            )
          ],
        ),
        bottomNavigationBar: Container(
          height: 150,
          padding: const EdgeInsets.symmetric(horizontal: 20),
          child: Center(
              child: AppButton(
                  elevation: 0,
                  borderSideColor: AppColor.primary,
                  backgroundColor: AppColor.background,
                  foregroundColor: AppColor.primary,
                  buttonText: "Close",
                  onPressed: () {})),
        ));
  }
}

This is the code I have written

2

There are 2 answers

0
Diwyansh On

You can use PageController along with GestureDetector and make PageView's physics to NeverScrollableScrollPhysics. Try below once it may need some improvements according to your need :

Scaffold(
    appBar: AppBar(
      backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      title: Text(widget.title),
    ),
    body: GestureDetector(
      onHorizontalDragUpdate: (val) {
        if(val.globalPosition.dx < MediaQuery.of(context).size.width / 2) {
          controller.nextPage(duration: Duration(seconds: 3), curve: Curves.elasticOut);
        } else {
          controller.previousPage(duration: Duration(seconds: 3), curve: Curves.elasticOut);
        }
      },
      child: PageView.builder(
        itemCount: 10,
          controller:controller ,
          physics: NeverScrollableScrollPhysics(),
          itemBuilder: (context,index) => AnimatedContainer(
        height: 200, width: 150,
        margin: EdgeInsets.symmetric(horizontal: 20),
        color: Colors.red,
        duration: Duration(milliseconds: 600),
        child: Text("$index"),
      )),
    ),
)
0
pskink On

try this Foo widget, the main idea is to use Flow widget with a delegate that paints the rotated child:

class Foo extends StatelessWidget {
  final ctrl = PageController(
    viewportFraction: 0.6,
  );

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AspectRatio(
        aspectRatio: 1.25,
        child: ColoredBox(
          color: Colors.white,
          child: PageView.builder(
            controller: ctrl,
            clipBehavior: Clip.none,
            itemCount: 10,
            itemBuilder: (ctx, i) {
              return Flow(
                clipBehavior: Clip.none,
                delegate: FooDelegate(i, ctrl),
                children: [
                  Container(
                    margin: const EdgeInsets.all(4),
                    clipBehavior: Clip.antiAlias,
                    decoration: BoxDecoration(
                      color: Colors.grey.shade300,
                      borderRadius: BorderRadius.circular(6),
                      boxShadow: const [BoxShadow(blurRadius: 4, offset: Offset(0, 2))],
                    ),
                    child: Material(
                      type: MaterialType.transparency,
                      child: InkWell(
                        onTap: () => ScaffoldMessenger.of(ctx)..clearSnackBars()..showSnackBar(
                          SnackBar(content: Text('item ${i + 1} clicked'))
                        ),
                        splashColor: Colors.deepPurple,
                        child: Column(
                          children: [
                            Text('item', style: Theme.of(ctx).textTheme.headlineMedium),
                            Expanded(child: FittedBox(child: Text('${i + 1}'))),
                          ],
                        ),
                      ),
                    ),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

class FooDelegate extends FlowDelegate {
  FooDelegate(this.page, this.ctrl) : super(repaint: ctrl);

  final int page;
  final PageController ctrl;

  @override
  void paintChildren(FlowPaintingContext context) {
    final delta = page - ctrl.page!;
    final angle = 0.075 * pi * delta;
    final size = context.getChildSize(0)!;
    final offset = size.bottomCenter(Offset.zero) - Offset(delta * size.width * 0.5, 0);
    final transform = composeMatrixFromOffsets(
      rotation: angle,
      anchor: offset,
      translate: offset,
    );
    context.paintChild(0, transform: transform);
  }

  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) => false;
}

Matrix4 composeMatrixFromOffsets({
  double scale = 1,
  double rotation = 0,
  Offset translate = Offset.zero,
  Offset anchor = Offset.zero,
}) {
  final double c = cos(rotation) * scale;
  final double s = sin(rotation) * scale;
  final double dx = translate.dx - c * anchor.dx + s * anchor.dy;
  final double dy = translate.dy - s * anchor.dx - c * anchor.dy;
  return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}

EDIT

if you dont like that composeMatrixFromOffsets function you can always use an old way of matrix rotating:

final transform = Matrix4.identity()
  ..translate(offset.dx, offset.dy)
  ..rotateZ(angle)
  ..translate(-offset.dx, -offset.dy);