ListWheelScrollView 的进入和退出子项的淡入淡出效果

Fade effect on the entering and the exiting child of ListWheelScrollView

提问人:Ramji 提问时间:8/20/2023 更新时间:8/21/2023 访问量:47

问:

从下面的GIF中可以看出,每当孩子退出父母时,孩子就会消失而没有动画,这给用户留下了不好的印象。如何为进入和退出的孩子添加平滑过渡出口?Container

Chat GPT 给出了一个似乎具有正确逻辑的答案,但它给出了同样突然的效果。

聊天GPT代码 : -

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Smooth Transition in ListWheelScrollView')),
        body: TransitionListWheel(),
      ),
    );
  }
}

class TransitionListWheel extends StatefulWidget {
  @override
  _TransitionListWheelState createState() => _TransitionListWheelState();
}

class _TransitionListWheelState extends State<TransitionListWheel> {
  FixedExtentScrollController _scrollController;
  double itemHeight = 100.0; // Height of each item in the list
  int itemCount = 20;

  @override
  void initState() {
    super.initState();
    _scrollController = FixedExtentScrollController();
    _scrollController.addListener(_handleScroll);
  }

  @override
  void dispose() {
    _scrollController.removeListener(_handleScroll);
    _scrollController.dispose();
    super.dispose();
  }

  void _handleScroll() {
    setState(() {
      // Trigger a rebuild to update the transition animations
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: itemHeight * 5, // Visible height of the list
        child: ListWheelScrollView.useDelegate(
          controller: _scrollController,
          itemExtent: itemHeight,
          physics: FixedExtentScrollPhysics(),
          childDelegate: ListWheelChildBuilderDelegate(
            builder: (context, index) {
              final double scrollOffset = _scrollController.offset;
              final double itemScrollOffset = scrollOffset % itemHeight;

              // Calculate transition values for entering and exiting animations
              final double enteringScale = 1.0 - (itemScrollOffset / itemHeight);
              final double exitingScale = itemScrollOffset / itemHeight;

              return AnimatedBuilder(
                animation: _scrollController,
                builder: (context, child) {
                  return Transform.scale(
                    scale: (enteringScale + exitingScale).clamp(0.6, 1.0), // Adjust the range as needed
                    child: child,
                  );
                },
                child: Center(
                  child: Container(
                    width: 200,
                    height: 80,
                    color: Colors.blue,
                    child: Center(
                      child: Text(
                        'Item $index',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              );
            },
            childCount: itemCount,
          ),
        ),
      ),
    );
  }
}

我使用的代码: -

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'List',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const List(),
    );
  }
}

class List extends StatefulWidget {
  const List({Key? key}) : super(key: key);
  @override
  _ListState createState() => _ListState();
}

class _ListState extends State<List> {
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
       
        body: Center(
          child: SizedBox(
            height: 500,
            child: ListWheelScrollView(
                itemExtent: 100,
                physics: const FixedExtentScrollPhysics(),
                onSelectedItemChanged: (value) {
                  
                },
                children: [
                  for (int i = 0; i < 5; i++) ...[
                    Container(
                      color: Colors.green,
                      height: 50,
                      width: 50,
                    )
                  ]
                ]),
          ),
        ));
  }
}
Android 飞镖 列表视图 Flutter-Animation

评论


答:

0赞 MobileDev 8/21/2023 #1

我认为这个包将解决您的问题。https://pub.dev/packages/clickable_list_wheel_view

例:

import 'package:clickable_list_wheel_view/clickable_list_wheel_widget.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Material(
        child: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final _scrollController = FixedExtentScrollController();

  static const double _itemHeight = 60;
  static const int _itemCount = 100;

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: ClickableListWheelScrollView(
          scrollController: _scrollController,
          itemHeight: _itemHeight,
          itemCount: _itemCount,
          onItemTapCallback: (index) {
            print("onItemTapCallback index: $index");
          },
          child: ListWheelScrollView.useDelegate(
            controller: _scrollController,
            itemExtent: _itemHeight,
            physics: FixedExtentScrollPhysics(),
            overAndUnderCenterOpacity: 0.5,
            perspective: 0.002,
            onSelectedItemChanged: (index) {
              print("onSelectedItemChanged index: $index");
            },
            childDelegate: ListWheelChildBuilderDelegate(
              builder: (context, index) => _child(index),
              childCount: _itemCount,
            ),
          ),
        ),
      ),
    );
  }

  Widget _child(int index) {
    return SizedBox(
      height: _itemHeight,
      child: ListTile(
        leading: Icon(
            IconData(int.parse("0xe${index + 200}"),
                fontFamily: 'MaterialIcons'),
            size: 50),
        title: Text('Heart Shaker'),
        subtitle: Text('Description here'),
      ),
    );
  }
}

我不能在这里发布它,因为 gif 大小很高。您可以从我分享的链接中看到此代码的输出: https://raw.githubusercontent.com/cilestal/clickable_list_wheel_view/master/example/example.gif

我希望我有所帮助。享受你的工作。

评论

0赞 Ramji 8/21/2023
在GIF本身中,您可以看到顶部和底部小部件消失得非常快
1赞 Hydra 8/21/2023 #2

我扩展了 ChatGPT 代码,试试看:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Smooth Transition in ListWheelScrollView')),
        body: TransitionListWheel(),
      ),
    );
  }
}

class TransitionListWheel extends StatefulWidget {
  @override
  _TransitionListWheelState createState() => _TransitionListWheelState();
}

class _TransitionListWheelState extends State<TransitionListWheel> {
  late final FixedExtentScrollController _scrollController;
  double itemHeight = 100.0; // Height of each item in the list
  int itemCount = 20;
  int viewCount = 5;

  @override
  void initState() {
    super.initState();
    _scrollController = FixedExtentScrollController();
    _scrollController.addListener(_handleScroll);
  }

  @override
  void dispose() {
    _scrollController.removeListener(_handleScroll);
    _scrollController.dispose();
    super.dispose();
  }

  void _handleScroll() {
    setState(() {
      // Trigger a rebuild to update the transition animations
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        height: itemHeight * viewCount, // Visible height of the list
        child: ListWheelScrollView.useDelegate(
          controller: _scrollController,
          itemExtent: itemHeight,
          physics: FixedExtentScrollPhysics(),
          childDelegate: ListWheelChildBuilderDelegate(
            builder: (context, index) {
              final double scrollOffset = _scrollController.offset;
              final double itemScrollOffset = scrollOffset % itemHeight;

              // Calculate transition values for entering and exiting animations
              final double enteringScale =
                  1.0 - (itemScrollOffset / itemHeight);
              final double exitingScale = itemScrollOffset / itemHeight;

              final centerOffset = index * itemHeight - scrollOffset;
              final itemOpacity = (double x) {
                if (x < viewCount ~/ 2 * itemHeight) return 1.0;
                if (x > (viewCount ~/ 2 + 1) * itemHeight) return 0.0;
                return 1 - (x - viewCount ~/ 2 * itemHeight) / itemHeight;
              }(centerOffset.abs() + 25); // adjust as needed

              return AnimatedBuilder(
                animation: _scrollController,
                builder: (context, child) {
                  // return Text('offset: $centerOffset,  opacity: $itemOpacity');
                  return Transform.scale(
                    scale: (enteringScale + exitingScale)
                        .clamp(0.6, 1.0), // Adjust the range as needed
                    child: Opacity(opacity: itemOpacity, child: child),
                  );
                },
                child: Center(
                  child: Container(
                    width: 200,
                    height: 80,
                    color: Colors.blue,
                    child: Center(
                      child: Text(
                        'Item $index',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              );
            },
            childCount: itemCount,
          ),
        ),
      ),
    );
  }
}

编辑:这就是它的工作原理

此线计算每个索引与中心的偏移量。这意味着如果索引在中心,则偏移量为 0,如果索引在顶部,则为负值,如果索引在底部,则为正值。有点像 (-y) 轴。

final centerOffset = index * itemHeight - scrollOffset;

我们可以用绝对值来判断一个项目离中心有多远 假设我们有 5 个可见的项目,这意味着高度将为 500,因此两个边缘项目将具有价值(-200 和 200) 我们可以做一个像这样的切换案例逻辑:ContainercenterOffset

如果小于 200(项目靠近中心),则不透明度将为 1centerOffset

如果大于 200 +(不在可见区域),则不透明度将为 0centerOffsetitemHeight

else(项目接近边缘,介于 200 和 300 之间)

  1. 首先,我们减去 200 以获得介于 0 和 100 之间的变量(x - viewCount ~/ 2 * itemHeight)
  2. 我们将该变量除以 100(对不起,我在之前的答案中硬编码了 100,应该是)等于 0 和 1itemHeightitemHeight
  3. 我们不需要介于 0 和 之间的值,我们需要介于 1 和 0,所以我们将 1 - x 应用于值

之前的所有数字都与高度 500 () 相关联,这就是我使用viewCountviewCount ~/ 2

取消在构建器中注释以可视化我刚刚解释的内容Text

注意:如果你不喜欢线性不透明度,也可以转换它

final transformedOpacity = Curves.fastEaseInToSlowEaseOut.transform(itemOpacity);

评论

0赞 Ramji 8/21/2023
请简单告诉我代码的逻辑是什么?
0赞 Hydra 8/21/2023
我刚刚编辑了我的答案,看看吧