如何扩展列表项而不影响其他项。 (扑)
我想在 flutter 中创建 Netflix 主页,但在创建此悬停状态时陷入困境。
我创建了两个基本小部件。一个用于数字加缩略图,另一个用于悬停小部件时的展开视图。然后我将它们放入带有 Inkwell
的 stack
中,其中 onHover
更改状态以显示展开的小部件。
当我将鼠标悬停在小部件上时,它确实在正常状态和展开状态之间切换,当我尝试将这些小部件的列表放在一起时,问题就出现了。
- 当使用
row
(或ListView)将它们放在一起时,悬停后,展开的小部件会使其他小部件移动。 (这不是想要的行为,我希望它们重叠)
- 当我将其与
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.
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.
- 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)
- When I use it with
stack
, the widgets do overlap but now it isn't scrollable anymore.
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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
所以终于找到了解决这个问题的办法,前进的办法就是在SingleChildScrollView中使用栈。
我犯的一个错误是,我没有将 SingleChildScrollView 的方向设置为水平。所以我添加了这一点。
然后还需要添加一个空大小的盒子,其宽度等于堆栈中所有项目的总和。
这最终将堆栈扩展到所需的宽度,然后也可以滚动。
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.
This finally expanded the stack to the required width and then made is scrollable also.