问题是这样描述的.

在web环境中,我必须建立一个图像的水平列表(如Netflix),当用户将鼠标光标放在这些图像上时,这些图像应该会增加元素的大小.为了实现这一点,我使用一个堆栈(clipBehavior等于Clip.none)来渲染列表中的每个项目,当我检测到鼠标悬停事件时,我添加一个新的容器(大于原始项目的大小)来绘制一个动画容器,其中将增长以填充它.

Image 1, normal list

动画效果很好,但容器被定位到列表中的下一个右项,然而,我需要它位于该项之上.

Overlapped image

代码如下:


    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: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }

    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key, required this.title}) : super(key: key);

      final String title;

      @override
      State createState() => _MyHomePageState();
    }

    class _MyHomePageState extends State {
      final double zoomTargetHeight = 320;
      final double zoomTargetWidth = 500;
      final double zoomOriginalHeight = 225;
      final double zoomOriginalWidth = 400;

      double _zoomHeight = 225;
      double _zoomWidth = 400;

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: SingleChildScrollView(
            child: Column(
              children: [
                Image.network("https://source.unsplash.com/random/1600x900?cars"),
                Container(
                  color: Colors.black87,
                  child: Padding(
                    padding: const EdgeInsets.all(20.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const SizedBox(
                          height: 12,
                        ),
                        const Text(
                          "List of items",
                          style: TextStyle(color: Colors.white),
                        ),
                        const SizedBox(
                          height: 12,
                        ),
                        SizedBox(
                          height: 235,
                          child: ListView.separated(
                            clipBehavior: Clip.none,
                            scrollDirection: Axis.horizontal,
                            itemBuilder: (context, index) {
                              return buildCard(index);
                            },
                            separatorBuilder: (context, index) {
                              return const SizedBox(
                                width: 12,
                              );
                            },
                            itemCount: 10,
                          ),
                        ),
                        const SizedBox(
                          height: 200,
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }

      Map _showZoom = {};

      Widget buildCard(int index) {
        Stack stack = Stack(
          clipBehavior: Clip.none,
          children: [
            MouseRegion(
              onEnter: (event) {
                setState(() {
                  _showZoom["$index"] = true;
                });
              },
              child: ClipRRect(
                borderRadius: BorderRadius.circular(20),
                child: Stack(
                  children: [
                    Image.network(
                        "https://source.unsplash.com/random/400x225?sig=$index&cars"),
                    Container(
                      color: Colors.black.withAlpha(100),
                      height: zoomOriginalHeight,
                      width: zoomOriginalWidth,
                    ),
                  ],
                ),
              ),
            ),
            if (_showZoom["$index"] != null && _showZoom["$index"]!)
              Positioned(
                left: (zoomOriginalWidth - zoomTargetWidth) / 2,
                top: (zoomOriginalHeight - zoomTargetHeight) / 2,
                child: MouseRegion(
                  onHover: (_) {
                    setState(() {
                      _zoomHeight = zoomTargetHeight;
                      _zoomWidth = zoomTargetWidth;
                    });
                  },
                  onExit: (event) {
                    setState(() {
                      _showZoom["$index"] = false;
                      _zoomHeight = zoomOriginalHeight;
                      _zoomWidth = zoomOriginalWidth;
                    });
                  },
                  child: SizedBox(
                    width: zoomTargetWidth,
                    height: zoomTargetHeight,
                    child: Center(
                      child: AnimatedContainer(
                        duration: const Duration(milliseconds: 400),

                        width: _zoomWidth,
                        height: _zoomHeight,
                        // color: Colors.green.withAlpha(100),
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(20.0),
                          image: DecorationImage(
                            image: NetworkImage(
                                "https://source.unsplash.com/random/400x225?sig=$index&cars"),
                            fit: BoxFit.cover,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
          ],
        );
        return stack;
      }
    }


记住Flutter 配置--启用web

推荐答案

我认为这正是你想要的(也可以勾选live demo on DartPad):

Screenshot

解决方案是:

  1. 使用包裹ListView的外层Stack
  2. Stack中,在其前面再添加ListView个项目,项目数量和项目大小相同;
  3. 然后,忽略这个新ListView上有IgnorePointer的指针事件,这样后面的一个将接收滚动/点击/点击事件;
  4. 通过收听滚动事件NotificationListener<ScrollNotification>,在后ListView和前ListView之间同步滚动;

Here's the code

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: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  final double zoomTargetHeight = 320;
  final double zoomTargetWidth = 500;
  final double zoomOriginalHeight = 225;
  final double zoomOriginalWidth = 400;

  late final ScrollController _controllerBack;
  late final ScrollController _controllerFront;

  @override
  void initState() {
    super.initState();
    _controllerBack = ScrollController();
    _controllerFront = ScrollController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(
          children: [
            Image.network("https://source.unsplash.com/random/1600x900?cars"),
            Container(
              color: Colors.black87,
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const SizedBox(
                      height: 12,
                    ),
                    const Text(
                      "List of items",
                      style: TextStyle(color: Colors.white),
                    ),
                    const SizedBox(
                      height: 12,
                    ),
                    SizedBox(
                      height: 225,
                      child: NotificationListener<ScrollNotification>(
                        onNotification: (notification) {
                          _controllerFront.jumpTo(_controllerBack.offset);
                          return true;
                        },
                        child: Stack(
                          clipBehavior: Clip.none,
                          children: [
                            ListView.separated(
                              controller: _controllerBack,
                              clipBehavior: Clip.none,
                              scrollDirection: Axis.horizontal,
                              itemBuilder: (context, index) {
                                return buildBackCard(index);
                              },
                              separatorBuilder: (context, index) {
                                return const SizedBox(
                                  width: 12,
                                );
                              },
                              itemCount: 10,
                            ),
                            IgnorePointer(
                              child: ListView.separated(
                                controller: _controllerFront,
                                clipBehavior: Clip.none,
                                scrollDirection: Axis.horizontal,
                                itemBuilder: (context, index) {
                                  return buildFrontCard(index);
                                },
                                separatorBuilder: (context, index) {
                                  return const SizedBox(
                                    width: 12,
                                  );
                                },
                                itemCount: 10,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(
                      height: 200,
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  final Map _showZoom = {};

  Widget buildBackCard(int index) {
    return MouseRegion(
      onEnter: (event) {
        setState(() {
          _showZoom["$index"] = true;
        });
      },
      onExit: (event) {
        setState(() {
          _showZoom["$index"] = false;
        });
      },
      child: ClipRRect(
        borderRadius: BorderRadius.circular(20),
        child: Stack(
          children: [
            Image.network(
              "https://source.unsplash.com/random/400x225?sig=$index&cars",
            ),
            Container(
              color: Colors.black.withAlpha(100),
              height: zoomOriginalHeight,
              width: zoomOriginalWidth,
            ),
          ],
        ),
      ),
    );
  }

  Widget buildFrontCard(int index) {
    Widget child;
    double scale;
    if (_showZoom["$index"] == null || !_showZoom["$index"]!) {
      scale = 1;
      child = SizedBox(
        height: zoomOriginalHeight,
        width: zoomOriginalWidth,
      );
    } else {
      scale = zoomTargetWidth / zoomOriginalWidth;
      child = Stack(
        clipBehavior: Clip.none,
        children: [
          Container(
            height: zoomOriginalHeight,
            width: zoomOriginalWidth,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20.0),
              image: DecorationImage(
                image: NetworkImage(
                    "https://source.unsplash.com/random/400x225?sig=$index&cars"),
                fit: BoxFit.cover,
              ),
            ),
          ),
        ],
      );
    }
    return AnimatedScale(
      duration: const Duration(milliseconds: 400),
      scale: scale,
      child: child,
    );
  }
}

Flutter相关问答推荐

为什么小部件会在其父文件中重建,但放在单独文件中时却不会重建?

类型int不是可迭代动态tmdbapi类型的子类型<>

无法将Flutter 连接到Firebase

使用ThemeProvider不在亮模式和暗模式之间切换

功能不起作用的Flutter 块复制

Flutter 图标记移动

在setState之后,Ffltter ListView.Builder未更新

如何从DART中的事件列表中获取即将到来的日期

如何管理枚举类型的阻塞事件?

应为Map String,dynamic类型的值,但得到的值为type()= Map String,dynamic'

导航回上一页会将我带回Ffltter应用程序中的登录页面

在 Flutter 中滑动关闭 PageView.builder 时出现问题

Dart 和 flutter 错误无法在您的 PATH 中找到 git

如何从flutter连接PostgreSQL数据库?

仅在获取(读取)、Provider 时,Firestore 中的数据为空

如何从 Flutter 中的 App2 远程点击 App1 中的按钮

如何在 Flutter 中的 BackdropFilter.blur 中制作一个 100x100 的清晰孔

Agora VideoCall 不显示远程视频网格

http响应在Flutter 中返回307

RangeError(索引):无效值:有效值范围为空:点击文章时为0