如何扩展列表项而不影响其他项。 (扑)

发布于 2025-01-17 04:56:08 字数 10030 浏览 4 评论 0原文

我想在 flutter 中创建 Netflix 主页,但在创建此悬停状态时陷入困境。

输入图片此处描述

我创建了两个基本小部件。一个用于数字加缩略图,另一个用于悬停小部件时的展开视图。然后我将它们放入带有 Inkwellstack 中,其中 onHover 更改状态以显示展开的小部件。

当我将鼠标悬停在小部件上时,它确实在正常状态和展开状态之间切换,当我尝试将这些小部件的列表放在一起时,问题就出现了。

  1. 当使用row(或ListView)将它们放在一起时,悬停后​​,展开的小部件会使其他小部件移动。 (这不是想要的行为,我希望它们重叠)

在此处输入图像描述

  1. 当我将其与 stack 一起使用时,小部件确实重叠,但现在它不再可滚动。

输入图像这里的描述

我已经为任何想要克隆它并尝试自己运行它的人添加了存储库的链接,我正在 flutter web 上运行它。 https://github.com/Advait1306/netflix-flutter

带有缩略图和数量:

class TopListItem extends StatelessWidget {
  final int index;
  const TopListItem({Key? key, required this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const double height = 250;

    return SizedBox(

      height: height,
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          SvgPicture.asset("assets/numbers/$index.svg",
              fit: BoxFit.fitHeight, height: height),
          Transform.translate(
              offset: const Offset(-30, 0),
              child: Image.asset("assets/thumbnails/thumb1.jpg"))
        ],
      ),
    );
  }
}

扩展视图小部件:

import 'package:flutter/material.dart';

class HoverMovieTrailer extends StatelessWidget {
  const HoverMovieTrailer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const textTheme = TextStyle(color: Colors.white);

    return SizedBox(
      width: 400,
      height: 400,
      child: Container(
        clipBehavior: Clip.hardEdge,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            color: const Color(0xFF242424)),
        child: Column(
          children: [
            Image.asset("assets/backgrounds/background1.jpg"),
            const SizedBox(
              height: 20,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 18),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Row(
                    children: const [
                      RoundIconButton(icon: Icons.play_arrow_outlined),
                      SizedBox(width: 5),
                      RoundIconButton(icon: Icons.add_outlined),
                      SizedBox(width: 5),
                      RoundIconButton(icon: Icons.thumb_up_alt_outlined),
                      SizedBox(width: 5),
                    ],
                  ),
                  Row(
                    children: const [
                      RoundIconButton(icon: Icons.keyboard_arrow_down_outlined),
                    ],
                  ),
                ],
              ),
            ),
            const SizedBox(
              height: 20,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 18),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children:  [
                  const Text(
                    "98% Match",
                    style: TextStyle(
                      color: Colors.green,
                      fontWeight: FontWeight.bold
                    ),
                  ),
                  const SizedBox(width: 5),
                  Container(
                    padding: const EdgeInsets.all(1),
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.white, width: 1)
                    ),
                    child: const Text(
                      "18+",
                      style: textTheme,
                    ),
                  ),
                  const SizedBox(width: 5),
                  const Text(
                    "4 Seasons",
                    style: textTheme,
                  ),
                  const SizedBox(width: 5),
                  Container(
                    decoration: BoxDecoration(
                        border: Border.all(color: Colors.white, width: 1)
                    ),
                    child: const Text(
                      "HD",
                      style: textTheme,
                    ),
                  )
                ],
              ),
            ),
            const SizedBox(
              height: 5,
            ),
            Padding(
              padding: const EdgeInsets.all(18.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  const Text(
                    "Captivating",
                    style: textTheme,
                  ),
                  const SizedBox(width: 5),
                  Container(
                    width: 5,
                    height: 5,
                    decoration: const BoxDecoration(
                      shape: BoxShape.circle,
                      color: Colors.white54
                    ),
                  ),
                  const SizedBox(width: 5),
                  const Text(
                    "Exciting",
                    style: textTheme,
                  ),
                  const SizedBox(width: 5),
                  Container(
                    width: 5,
                    height: 5,
                    decoration: const BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.white54
                    ),
                  ),
                  const SizedBox(width: 5),
                  const Text(
                    "Docuseries",
                    style: textTheme,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class RoundIconButton extends StatelessWidget {
  final IconData icon;
  const RoundIconButton({Key? key, required this.icon}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.transparent,
          border: Border.all(width: 2, color: Colors.white)),
      margin: const EdgeInsets.all(1),
      child: IconButton(
        onPressed: () {},
        icon: Icon(icon),
        color: Colors.white,
      ),
    );
  }
}

在单个小部件中组合小部件:

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:netflix_flutter/widgets/hover_movie_trailer.dart';
import 'package:netflix_flutter/widgets/top_list_item.dart';

class TopListItemWithHover extends StatefulWidget {
  const TopListItemWithHover({Key? key}) : super(key: key);

  @override
  State<TopListItemWithHover> createState() => _TopListItemWithHoverState();
}

class _TopListItemWithHoverState extends State<TopListItemWithHover> {

  bool hover = false;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: (){},
      onHover: (value){
        log("Hover value: $value");
        setState(() {
          hover = value;
        });
      },
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          TopListItem(index: 1),
          if(hover) HoverMovieTrailer(),
        ],
      ),
    );
  }
}

列表:

import 'package:flutter/material.dart';
import 'package:netflix_flutter/widgets/hover_movie_trailer.dart';
import 'package:netflix_flutter/widgets/top_list_item.dart';
import 'package:netflix_flutter/widgets/top_list_item_with_hover.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<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: SizedBox(
              height: 400,
              child: ListView.builder(
                shrinkWrap: true,
                scrollDirection: Axis.horizontal,
                clipBehavior: Clip.none,
                itemCount: 8,
                itemBuilder: (context, index) {
                  return TopListItemWithHover();
                },
              ),
            ),
          ),
          const SizedBox(height: 50),
          SingleChildScrollView(
            child: SizedBox(
              height: 400,
              child: Stack(
                fit: StackFit.passthrough,
                children: [
                  for (var i = 10; i >= 0; i--)
                    Positioned(
                      left: (i) * 300,
                      child: TopListItemWithHover(),
                    )
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

I want to create the Netflix home page in flutter, and I'm stuck while creating this hover state.

enter image description here

I have created two base widgets. One for the number plus the thumbnail and the other one for the expanded view when the widget is hovered. Then I put them in a stack with an Inkwell where the onHover changes the state to show the expanded widget.

When I hover on the widget, it does switch between the normal state an expanded state, the problem comes when I try to put a list of these widgets together.

  1. When using row (or ListView) to put them together, after hovering, the expanded widget makes the other widgets move. (which is not the wanted behaviour, I want them to overlap)

enter image description here

  1. When I use it with stack, the widgets do overlap but now it isn't scrollable anymore.

enter image description here

I have added the link to the repo for anyone that wants to clone it and try running it themselves, I'm running it on flutter web.
https://github.com/Advait1306/netflix-flutter

Widget with thumbnail and number:

class TopListItem extends StatelessWidget {
  final int index;
  const TopListItem({Key? key, required this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const double height = 250;

    return SizedBox(

      height: height,
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          SvgPicture.asset("assets/numbers/$index.svg",
              fit: BoxFit.fitHeight, height: height),
          Transform.translate(
              offset: const Offset(-30, 0),
              child: Image.asset("assets/thumbnails/thumb1.jpg"))
        ],
      ),
    );
  }
}

Expanded view widget:

import 'package:flutter/material.dart';

class HoverMovieTrailer extends StatelessWidget {
  const HoverMovieTrailer({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const textTheme = TextStyle(color: Colors.white);

    return SizedBox(
      width: 400,
      height: 400,
      child: Container(
        clipBehavior: Clip.hardEdge,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            color: const Color(0xFF242424)),
        child: Column(
          children: [
            Image.asset("assets/backgrounds/background1.jpg"),
            const SizedBox(
              height: 20,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 18),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Row(
                    children: const [
                      RoundIconButton(icon: Icons.play_arrow_outlined),
                      SizedBox(width: 5),
                      RoundIconButton(icon: Icons.add_outlined),
                      SizedBox(width: 5),
                      RoundIconButton(icon: Icons.thumb_up_alt_outlined),
                      SizedBox(width: 5),
                    ],
                  ),
                  Row(
                    children: const [
                      RoundIconButton(icon: Icons.keyboard_arrow_down_outlined),
                    ],
                  ),
                ],
              ),
            ),
            const SizedBox(
              height: 20,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 18),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children:  [
                  const Text(
                    "98% Match",
                    style: TextStyle(
                      color: Colors.green,
                      fontWeight: FontWeight.bold
                    ),
                  ),
                  const SizedBox(width: 5),
                  Container(
                    padding: const EdgeInsets.all(1),
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.white, width: 1)
                    ),
                    child: const Text(
                      "18+",
                      style: textTheme,
                    ),
                  ),
                  const SizedBox(width: 5),
                  const Text(
                    "4 Seasons",
                    style: textTheme,
                  ),
                  const SizedBox(width: 5),
                  Container(
                    decoration: BoxDecoration(
                        border: Border.all(color: Colors.white, width: 1)
                    ),
                    child: const Text(
                      "HD",
                      style: textTheme,
                    ),
                  )
                ],
              ),
            ),
            const SizedBox(
              height: 5,
            ),
            Padding(
              padding: const EdgeInsets.all(18.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  const Text(
                    "Captivating",
                    style: textTheme,
                  ),
                  const SizedBox(width: 5),
                  Container(
                    width: 5,
                    height: 5,
                    decoration: const BoxDecoration(
                      shape: BoxShape.circle,
                      color: Colors.white54
                    ),
                  ),
                  const SizedBox(width: 5),
                  const Text(
                    "Exciting",
                    style: textTheme,
                  ),
                  const SizedBox(width: 5),
                  Container(
                    width: 5,
                    height: 5,
                    decoration: const BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.white54
                    ),
                  ),
                  const SizedBox(width: 5),
                  const Text(
                    "Docuseries",
                    style: textTheme,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class RoundIconButton extends StatelessWidget {
  final IconData icon;
  const RoundIconButton({Key? key, required this.icon}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.transparent,
          border: Border.all(width: 2, color: Colors.white)),
      margin: const EdgeInsets.all(1),
      child: IconButton(
        onPressed: () {},
        icon: Icon(icon),
        color: Colors.white,
      ),
    );
  }
}

Combining the widgets in the single widget:

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:netflix_flutter/widgets/hover_movie_trailer.dart';
import 'package:netflix_flutter/widgets/top_list_item.dart';

class TopListItemWithHover extends StatefulWidget {
  const TopListItemWithHover({Key? key}) : super(key: key);

  @override
  State<TopListItemWithHover> createState() => _TopListItemWithHoverState();
}

class _TopListItemWithHoverState extends State<TopListItemWithHover> {

  bool hover = false;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: (){},
      onHover: (value){
        log("Hover value: $value");
        setState(() {
          hover = value;
        });
      },
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          TopListItem(index: 1),
          if(hover) HoverMovieTrailer(),
        ],
      ),
    );
  }
}

Lists:

import 'package:flutter/material.dart';
import 'package:netflix_flutter/widgets/hover_movie_trailer.dart';
import 'package:netflix_flutter/widgets/top_list_item.dart';
import 'package:netflix_flutter/widgets/top_list_item_with_hover.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<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: SizedBox(
              height: 400,
              child: ListView.builder(
                shrinkWrap: true,
                scrollDirection: Axis.horizontal,
                clipBehavior: Clip.none,
                itemCount: 8,
                itemBuilder: (context, index) {
                  return TopListItemWithHover();
                },
              ),
            ),
          ),
          const SizedBox(height: 50),
          SingleChildScrollView(
            child: SizedBox(
              height: 400,
              child: Stack(
                fit: StackFit.passthrough,
                children: [
                  for (var i = 10; i >= 0; i--)
                    Positioned(
                      left: (i) * 300,
                      child: TopListItemWithHover(),
                    )
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情未る 2025-01-24 04:56:08

所以终于找到了解决这个问题的办法,前进的办法就是在SingleChildScrollView中使用栈。

我犯的一个错误是,我没有将 SingleChildScrollView 的方向设置为水平。所以我添加了这一点。

然后还需要添加一个空大小的盒子,其宽度等于堆栈中所有项目的总和。

          Stack(
            clipBehavior: Clip.none,
            children: [
              const SizedBox(
                width: 300*10,
              ),
              for (var i = 10; i >= 0; i--)
                Positioned(
                  left: (i) * 300,
                  child: TopListItemWithHover(),
                )
            ],
          )

这最终将堆栈扩展到所需的宽度,然后也可以滚动。

输入图片此处描述

So finally found the solution to this problem, the way to move forward is to use a stack in SingleChildScrollView.

A mistake that I made is, I did not set the SingleChildScrollView's direction to horizontal. So I added that.

And then one more addition that's needed is -- A empty sized box which has the width of sum of all the items in the stack.

          Stack(
            clipBehavior: Clip.none,
            children: [
              const SizedBox(
                width: 300*10,
              ),
              for (var i = 10; i >= 0; i--)
                Positioned(
                  left: (i) * 300,
                  child: TopListItemWithHover(),
                )
            ],
          )

This finally expanded the stack to the required width and then made is scrollable also.

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文