首页 > 解决方案 > 如何在轻按小部件后滑动小部件?

问题描述

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

步骤如下:

  1. 单击任何项​​目
  2. 该项目将被取代
  3. 将打开相应页面
  4. 返回上一页时,该项目将保持位移,直到单击另一个项目。单击同一项目不会执行任何操作。

目前,如果单击任何项​​目,它会登陆到没有动画的新页面

在此处输入图像描述

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

GIF:在 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',
                                       ),
                                    ),
                             ),
                        ],
                    ),
             ],
       ),
)

标签: flutterflutter-animation

解决方案


底部的代码示例包括:

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

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

代码作用的说明:

运行中的代码,显示圆形扇形动画到它们的对角线

代码示例


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;
}


推荐阅读