【问题标题】:Animate listitem switch from one list to another and inside the list itself动画 listitem 从一个列表切换到另一个列表并在列表本身内部
【发布时间】:2022-08-20 16:32:08
【问题描述】:

我正在尝试在列表视图(可滚动)和另一个永远不可滚动的列表视图/行之间实现动画列表项切换。我试过使用local heroflutter sidekick 但没有成功。我需要初始列表小部件是可滚动的,而本地英雄不支持它。 Flutter Sidekick 很棒,但它与 gridview 一起使用,这是不可能自定义 UI 的。

这是我试图实现的用户界面:

    标签: flutter dart flutter-layout flutter-animation


    【解决方案1】:

    只是为了给你一个开始,我已经编写了一个演示代码。我使用了一个 Stack 小部件,将小部件设置为动画到它的位置。使用全局键查找小部件在点击时的位置和最终位置。

    import 'package:flutter/material.dart';
    
    void main() async {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: Center(child: DemoClass()));
      }
    }
    
    class DemoClass extends StatefulWidget {
      DemoClass({Key? key}) : super(key: key);
    
      @override
      State<DemoClass> createState() => _DemoClassState();
    }
    
    class _DemoClassState extends State<DemoClass> {
      List<Widget> profiles = [];
      int selectedIndex = -1;
      double firstItemLeftPos = -200;
      double firstItemTopPos = 0;
      GlobalKey finalWidgetKey = GlobalKey();
      List<GlobalKey> profileKey = [];
      int animDuration = 0;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        profiles = List.generate(20, (index) {
          GlobalKey itemKey = GlobalKey();
    
          profileKey.add(itemKey);
          return InkWell(
            key: profileKey[index],
            onTap: () {
              RenderBox box =
                  profileKey[index].currentContext?.findRenderObject() as RenderBox;
              Offset position = box.localToGlobal(Offset.zero);
              selectedIndex = index;
              firstItemTopPos = position.dy;
              firstItemLeftPos = position.dx;
              animDuration = 0;
              setState(() {});
              Future.delayed(Duration(milliseconds: 10), () {
                RenderBox FinalRenderbox =
                    finalWidgetKey.currentContext?.findRenderObject() as RenderBox;
                Offset finalPos = FinalRenderbox.localToGlobal(Offset.zero);
                firstItemLeftPos = finalPos.dx;
                firstItemTopPos = finalPos.dy;
                animDuration = 500;
                setState(() {});
              });
            },
            child: CircleAvatar(
              child: Text("$index"),
            ),
          );
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            children: [
              Expanded(
                child: Stack(
                  children: [
                    Positioned(
                      left: 20,
                      top: 30,
                      child: Container(
                        key: finalWidgetKey,
                        height: 40,
                        width: 40,
                        decoration: BoxDecoration(
                            shape: BoxShape.circle, color: Colors.red),
                      ),
                    ),
                    AnimatedPositioned(
                        left: firstItemLeftPos,
                        top: firstItemTopPos,
                        child: CircleAvatar(
                          child: Text("$selectedIndex"),
                        ),
                        duration: Duration(milliseconds: animDuration))
                  ],
                ),
              ),
              SizedBox(
                height: 100,
                width: MediaQuery.of(context).size.width,
                child: ListView(
                  scrollDirection: Axis.horizontal,
                  children: profiles,
                ),
              )
            ],
          ),
        );
      }
    }
    
    

    【讨论】:

      【解决方案2】:

      我的概念是这个 UI 是一切都可以放在 Stack 上。每个圆圈都包含 userData 及其位置参数。虽然我们需要放置在两个不同的点上,但我们可以使用两个 bool 检查该位置是否为空。会有两个动画,AnimatedPositionedAnimatedContainer(用于调整大小),你可以选择不同的动画。而对于滚动事件,我们可以使用Gesturedetector delta。在这里,您需要控制选择和滚动的列表排列。

      //https://gist.github.com/yeasin50/4e0bad0a1fd66dd2b1e5fd1b37d1fdc4
      class AnimatedUI extends StatefulWidget {
        const AnimatedUI({Key? key}) : super(key: key);
      
        @override
        State<AnimatedUI> createState() => _AnimatedUIState();
      }
      
      enum PosOn { a, b, bottom }
      
      class UserX {
        final Color color;
        final String name;
        PosOn posOn;
        UserX({
          required this.color,
          required this.name,
          this.posOn = PosOn.bottom,
        });
      
        UserX copyWith({
          Color? color,
          String? name,
          PosOn? posOn,
        }) {
          return UserX(
            posOn: posOn ?? this.posOn,
            color: color ?? this.color,
            name: name ?? this.name,
          );
        }
      }
      
      class _AnimatedUIState extends State<AnimatedUI> {
        final smallSize = 64.0;
        late final usersList = List.generate(
            24,
            (index) => UserX(
                color: index.isEven ? Colors.red : Colors.orange,
                name: "name $index"));
      
        bool aIsEmpty = true;
        bool bIsEmpty = true;
      
        double deltaX = 0.0;
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            backgroundColor: Colors.deepPurple,
            body: LayoutBuilder(
              builder: (context, constraints) {
                final width = constraints.maxWidth;
                final height = constraints.maxHeight;
      
                final maxAvatarSize = constraints.maxWidth * .25;
      
                final destinationA = Offset(width * .3, height * .2);
                final destinationB =
                    Offset(destinationA.dx + maxAvatarSize, height * .3);
      
                const Duration duration = Duration(milliseconds: 200);
      
                return Stack(
                  children: [
                    Positioned(
                      left: destinationA.dx,
                      top: destinationA.dy,
                      child: borderRight(maxAvatarSize),
                    ),
                    Positioned(
                      left: destinationB.dx,
                      top: destinationB.dy,
                      child: borderRight(maxAvatarSize),
                    ),
                    for (int i = 0; i < usersList.length; i++)
                      AnimatedPositioned(
                        duration: duration,
                        top: () {
                          if (usersList[i].posOn == PosOn.a) {
                            return destinationA.dy;
                          } else if (usersList[i].posOn == PosOn.b) {
                            return destinationB.dy;
                          }
      
                          return height - smallSize - 16;
                        }(),
                        left: () {
                          if (usersList[i].posOn == PosOn.a) {
                            return destinationA.dx;
                          } else if (usersList[i].posOn == PosOn.b) {
                            return destinationB.dx;
                          }
                          return deltaX + (smallSize + 8) * i;
                        }(),
                        child: AnimatedContainer(
                          duration: duration,
                          width: usersList[i].posOn == PosOn.bottom
                              ? smallSize
                              : maxAvatarSize,
                          height: usersList[i].posOn == PosOn.bottom
                              ? smallSize
                              : maxAvatarSize,
                          child: Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Material(
                              color: usersList[i].color,
                              shape: const CircleBorder(),
                              child: InkWell(
                                customBorder: const CircleBorder(),
                                onTap: () {
                                  final currentPos = usersList[i].posOn;
                                  if (currentPos == PosOn.a) {
                                    usersList[i] =
                                        usersList[i].copyWith(posOn: PosOn.bottom);
                                    aIsEmpty = true;
                                  } else if (currentPos == PosOn.b) {
                                    usersList[i] =
                                        usersList[i].copyWith(posOn: PosOn.bottom);
                                    bIsEmpty = true;
                                  } else {
                                    if (aIsEmpty) {
                                      usersList[i] =
                                          usersList[i].copyWith(posOn: PosOn.a);
                                      aIsEmpty = false;
                                    } else if (bIsEmpty) {
                                      usersList[i] =
                                          usersList[i].copyWith(posOn: PosOn.b);
                                      bIsEmpty = false;
                                    }
                                  }
                                  setState(() {});
                                },
                                child: SizedBox(
                                  width: smallSize,
                                  height: smallSize,
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    Align(
                      alignment: Alignment.bottomCenter,
                      child: SizedBox(
                        height: smallSize,
                        child: GestureDetector(
                          onHorizontalDragUpdate: (details) {
                            deltaX += details.delta.dx;
      
                            setState(() {});
                          },
                        ),
                      ),
                    )
                  ],
                );
              },
            ),
          );
        }
      
        Container borderRight(double maxAvatarSize) {
          return Container(
            width: maxAvatarSize,
            height: maxAvatarSize,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              border: Border.all(
                color: Colors.cyanAccent,
                width: 4,
              ),
            ),
            alignment: Alignment.center,
            child: Text(
              "Add",
              style: TextStyle(color: Colors.white),
            ),
          );
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多