在颤动中点击小部件后如何滑动小部件?

时间:2021-06-11 14:19:14

标签: flutter flutter-animation

我想在 Flutter 中实现以下动画。我已经为此使用 GestureDetector 创建了容器,但不知道如何实现动画。

步骤如下:

  1. 点击任何项目
  2. 物品将被置换
  3. 将打开相应的页面
  4. 返回上一页后,该项目将保持位移,直到单击另一个项目。单击同一项目不会执行任何操作。

目前,如果任何一个项目被点击,它会登陆到没有动画的新页面

enter image description here

以下是我现在使用的代码。我需要像附加的 gif 一样的精确输出。

GIF:See in Google Drive(请在新标签页中打开,否则此页面将被导航)

代码:

double width = MediaQuery.of(context).size.width;
Container(
       height: width * 0.6,
       width: width * 0.6,
       alignment: Alignment.center,
       child: Row(
             children: [
                    Column(
                       children: [
                            GestureDetector(
                                 onTap: () {
                                      Methods.navigationToDetailsPage(context, 'Category');
                                 },
                                 child: Container(
                                      height: width * 0.6 * 0.5,
                                      width: width * 0.6 * 0.5,
                                      alignment: Alignment.center,
                                      padding: EdgeInsets.fromLTRB(width * 0.06, width * 0.06, 2, 2),
                                      child: Image.asset(
                                            'assets/images/category.png',
                                      ),
                                  ),
                            ),
                            GestureDetector(
                                  onTap: () {
                                     Methods.navigationToDetailsPage(context, 'Segment');
                                  },
                                  child: Container(
                                      height: width * 0.6 * 0.5,
                                      width: width * 0.6 * 0.5,
                                      alignment: Alignment.center,
                                      padding: EdgeInsets.fromLTRB(width * 0.06, 2, 2, width * 0.06),
                                      child: Image.asset(
                                        'assets/images/segment.png',
                                      ),
                                  ),
                              ),
                       ],
                    ),
                    Column(
                        children: [
                            GestureDetector(
                                  onTap: () {
                                      Methods.navigationToDetailsPage(context, 'Division');
                                  },
                                  child: Container(
                                       height: width * 0.6 * 0.5,
                                       width: width * 0.6 * 0.5,
                                       alignment: Alignment.center,
                                       padding: EdgeInsets.fromLTRB(2, width * 0.06, width * 0.06, 2),
                                       child: Image.asset(
                                          'assets/images/division.png',
                                       ),
                                   ),
                            ),
                            GestureDetector(
                                   onTap: () {
                                       Methods.navigationToDetailsPage(context, 'Brand');
                                   },
                                   child: Container(
                                       height: width * 0.6 * 0.5,
                                       width: width * 0.6 * 0.5,
                                       alignment: Alignment.center,
                                       padding: EdgeInsets.fromLTRB(2, 2, width * 0.06, width * 0.06),
                                       child: Image.asset(
                                         'assets/images/brand.png',
                                       ),
                                    ),
                             ),
                        ],
                    ),
             ],
       ),
)

1 个答案:

答案 0 :(得分:0)

底部的代码示例涵盖:

  • 如何设置 AnimationController,请参阅创建的 AnimatedSectorButton 小部件。
  • 使用 Tween 动画使用 Curves 来平滑动画流,请参阅创建的 SectorTile 小部件。
  • 如何创建形状与您使用的图像相似的小部件,这是通过 ClipPathCustomClipper 完成的。
注意:

通常应避免使用带有文本的图像进行布局,因为它们的可定制性较差,并且它们使 f.ex 变得更加困难。翻译每个资产给定的应用程序,您需要每种语言的图像。最好是潜在地导入字体并自己创建一个漂亮的小部件。

代码作用的说明:

Code in action, showing circle sectors animate out to their diagonal

代码示例


import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart';

main() {
  runApp(StackOverflowExampleApp());
}

class StackOverflowExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                AnimatedSectorButton(radius: 150, sectorQuadrant: SectorQuadrant.TopLeft),
                AnimatedSectorButton(radius: 150, sectorQuadrant: SectorQuadrant.TopRight),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                AnimatedSectorButton(radius: 150, sectorQuadrant: SectorQuadrant.BottomLeft),
                AnimatedSectorButton(radius: 150, sectorQuadrant: SectorQuadrant.BottomRight),
              ],
            )
          ]),
        ),
      ),
    );
  }
}

class AnimatedSectorButton extends StatefulWidget {
  final double radius;
  final SectorQuadrant sectorQuadrant;

  const AnimatedSectorButton({required this.radius, required this.sectorQuadrant});

  _AnimatedSectorButtonState createState() => _AnimatedSectorButtonState();
}

class _AnimatedSectorButtonState extends State<AnimatedSectorButton> with TickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: const Duration(milliseconds: 1000), vsync: this)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          Future.delayed(Duration(milliseconds: 1500)).then((value) async {
            _controller.reverse();
          });
        }
      });
  }

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

  Widget build(BuildContext context) {
    return SectorTile(
      radius: widget.radius,
      quadrant: widget.sectorQuadrant,
      controller: _controller.view,
      onTap: () {
        _controller.forward();
      },
    );
  }
}

enum SectorQuadrant {
  TopRight,
  TopLeft,
  BottomLeft,
  BottomRight
}

class SectorTile extends StatelessWidget {
  final double radius;
  final SectorQuadrant quadrant;
  final Animation<double> controller;
  final Function() onTap;
  late final Animation<double> offsetValue;
  late final double xSign;
  late final double ySign;

  SectorTile({
    Key? key,
    required this.radius,
    required this.quadrant,
    required this.controller,
    required this.onTap,
  }) : super(key: key) {
    // Here we define the specific of the animation.
    offsetValue = Tween(begin: 0.0, end: 1.0)
        .chain(CurveTween(curve: Curves.fastOutSlowIn))
        .animate(controller);
    
    // Primarily used for the direction of the animation
    xSign = quadrant == SectorQuadrant.TopLeft || quadrant == SectorQuadrant.BottomLeft ? -1 : 1;
    ySign = quadrant == SectorQuadrant.TopLeft || quadrant == SectorQuadrant.TopRight ? -1 : 1;
  }

  Widget _buildAnimation(BuildContext context, Widget? widget) {
    double value = offsetValue.value * (radius / 3);

    return Transform.translate(
      offset: Offset(value * xSign, value * ySign), 
      child: ClipPath(
        clipper: SectorClipper(quadrant),
        child: Material(
          color: Colors.red,
          child: InkWell(
            splashColor: Colors.black87,
            onTap: onTap,
            child: Container(
              width: radius,
              height: radius,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Align(
                  alignment: Alignment(-xSign, -ySign),
                  child: Text(
                    "StackOverflow",
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(animation: controller, builder: _buildAnimation);
  }
}

// Needed for constraining the material splash effect.
class SectorClipper extends CustomClipper<Path> {
  final SectorQuadrant sectorQuadrant;
  
  SectorClipper(this.sectorQuadrant);

  @override
  Path getClip(Size size) {
    switch (sectorQuadrant) {
      case SectorQuadrant.TopRight:
        return Path()..addOval(Rect.fromCircle(center: Offset(0, size.height), radius: size.height));
      case SectorQuadrant.TopLeft:
        return Path()..addOval(Rect.fromCircle(center: Offset(size.width, size.height), radius: size.height));
      case SectorQuadrant.BottomLeft:
        return Path()..addOval(Rect.fromCircle(center: Offset(size.width, 0), radius: size.height));
      case SectorQuadrant.BottomRight:
        return Path()..addOval(Rect.fromCircle(center: Offset(0, 0), radius: size.height));
    }
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true;
}