模拟水平图像列表,如 google play app 中所示
Emulating horizontal list of images as seen in google play app
我想创建一个小部件,其行为方式与 google 播放应用程序中的水平滚动列表(例如推荐的应用程序)相同。
我的意思的一个例子:
我曾尝试使用页面查看器小部件,但它的作用并不完全相同。一些值得注意的事实:
1) 3 张完全正方形的图像完全可见,另一张部分显示。第二个(绿色的)就在中间。
2) 最左边的图像(蓝色)与标题完全对齐 "Recommended for you"
3) 所有 4 个方形图像都有圆角。
4) 支持物品对齐
这是我能够使用 PageView 小部件模拟此行为的最佳结果:
- 红色是包含综合浏览量的容器的背景色。
- 绿色是偶数项的颜色,蓝色是奇数项的颜色
- 在每个项目中添加 100x100 的图像。
这是代码:
return Container(
width: double.infinity,
color: Colors.red,
height: 100,
child:
PageView(
pageSnapping: true,
controller:
PageController(initialPage: 1, viewportFraction: 0.315),
children: List<Widget>.generate(10, (index) {
return Container(
color: index%2 ==0 ? Colors.green : Colors.blue,
child:Image.network("http://via.placeholder.com/100x100",
fit: BoxFit.fitHeight)
);
})));
在我的代码中,有 2 件事不起作用:
1) 我无法使右侧显示部分可见的项目保持 3 个完全可见的项目和第二个完全居中的原始图像形式 google 播放,仅通过使用 viewportFraction。
2) 我无法将圆角应用于项目图像,因为如您所见,容器不是方形的,但图像是方形的。因此,当我应用 ClipRRect 时,它没有正确应用。我试图添加另一个容器,将其大小强制为 100x100,并在此容器内的图像上应用 ClipRRect,但当它位于 PageView 内时,似乎给定的 width/height 无效。它们似乎在内部由 PageView 控制。
所以,有人可以帮助解决这个问题,以获得与 google 播放水平滚动列表相同的行为吗?
干杯!
class SO extends StatelessWidget {
@override
Widget build(BuildContext context) {
double edge = 120.0;
double padding = edge / 10.0;
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < 10; i++)
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(padding),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(edge * .2)),
child: Container(
width: edge,
height: edge,
color: Colors.blue,
child: Image.network("http://via.placeholder.com/${edge.round()}x${edge.round()}", fit: BoxFit.fitHeight),
),
),
),
Container(
width: edge + padding,
padding: EdgeInsets.only(left: padding),
child: Text(
'foo app bar baz app apk',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: EdgeInsets.only(left: padding),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('4.2'),
Icon(
Icons.star,
size: 16,
)
],
),
),
],
)
],
),
),
),
);
}
}
这给出了
您可以修改 PageScrollPhysics
以创建对齐效果。
这就是我所做的,
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(
MaterialApp(
home: new Main(),
),
);
}
class Main extends StatefulWidget {
@override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
static const _scrollPhysics =
const ExtentScrollPhysics(itemExtent: 80, separatorSpacing: 10);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Recommended for you",
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () {},
)
],
),
SizedBox.fromSize(
size: Size.fromHeight(130),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 30,
physics: _scrollPhysics,
separatorBuilder: (context, _) =>
SizedBox(width: _scrollPhysics.dividerSpacing),
itemBuilder: (context, index) {
return SizedBox(
width: _scrollPhysics.itemExtent, // set height for vertical
child: CardItem(),
);
},
),
),
],
),
),
);
}
}
class CardItem extends StatefulWidget {
@override
_CardItemState createState() => _CardItemState();
}
class _CardItemState extends State<CardItem> {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 1,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20.0),
),
),
),
const SizedBox(height: 8.0),
Text(
"Tile Name",
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: const TextStyle(fontSize: 12.0),
),
Text(
"1 MB",
style: const TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
],
);
}
}
class ExtentScrollPhysics extends ScrollPhysics {
final double itemExtent;
final double dividerSpacing;
const ExtentScrollPhysics(
{ScrollPhysics parent, this.itemExtent, double separatorSpacing})
: assert(itemExtent != null && itemExtent > 0),
dividerSpacing = separatorSpacing ?? 0,
super(parent: parent);
@override
ExtentScrollPhysics applyTo(ScrollPhysics ancestor) {
return ExtentScrollPhysics(
parent: buildParent(ancestor),
itemExtent: itemExtent,
separatorSpacing: dividerSpacing,
);
}
double _getItem(ScrollPosition position) {
return position.pixels / (itemExtent + dividerSpacing);
}
double _getPixels(ScrollPosition position, double item) {
return item * (itemExtent + dividerSpacing);
}
double _getTargetPixels(
ScrollPosition position, Tolerance tolerance, double velocity) {
double page = _getItem(position);
if (velocity < -tolerance.velocity)
page -= 0.5;
else if (velocity > tolerance.velocity) page += 0.5;
return _getPixels(position, page.roundToDouble());
}
@override
Simulation createBallisticSimulation(
ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels)
return ScrollSpringSimulation(spring, position.pixels, target, velocity,
tolerance: tolerance);
return null;
}
@override
bool get allowImplicitScrolling => false;
}
我想创建一个小部件,其行为方式与 google 播放应用程序中的水平滚动列表(例如推荐的应用程序)相同。
我的意思的一个例子:
我曾尝试使用页面查看器小部件,但它的作用并不完全相同。一些值得注意的事实:
1) 3 张完全正方形的图像完全可见,另一张部分显示。第二个(绿色的)就在中间。
2) 最左边的图像(蓝色)与标题完全对齐 "Recommended for you"
3) 所有 4 个方形图像都有圆角。
4) 支持物品对齐
这是我能够使用 PageView 小部件模拟此行为的最佳结果:
- 红色是包含综合浏览量的容器的背景色。
- 绿色是偶数项的颜色,蓝色是奇数项的颜色
- 在每个项目中添加 100x100 的图像。
这是代码:
return Container(
width: double.infinity,
color: Colors.red,
height: 100,
child:
PageView(
pageSnapping: true,
controller:
PageController(initialPage: 1, viewportFraction: 0.315),
children: List<Widget>.generate(10, (index) {
return Container(
color: index%2 ==0 ? Colors.green : Colors.blue,
child:Image.network("http://via.placeholder.com/100x100",
fit: BoxFit.fitHeight)
);
})));
在我的代码中,有 2 件事不起作用:
1) 我无法使右侧显示部分可见的项目保持 3 个完全可见的项目和第二个完全居中的原始图像形式 google 播放,仅通过使用 viewportFraction。
2) 我无法将圆角应用于项目图像,因为如您所见,容器不是方形的,但图像是方形的。因此,当我应用 ClipRRect 时,它没有正确应用。我试图添加另一个容器,将其大小强制为 100x100,并在此容器内的图像上应用 ClipRRect,但当它位于 PageView 内时,似乎给定的 width/height 无效。它们似乎在内部由 PageView 控制。
所以,有人可以帮助解决这个问题,以获得与 google 播放水平滚动列表相同的行为吗?
干杯!
class SO extends StatelessWidget {
@override
Widget build(BuildContext context) {
double edge = 120.0;
double padding = edge / 10.0;
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < 10; i++)
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(padding),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(edge * .2)),
child: Container(
width: edge,
height: edge,
color: Colors.blue,
child: Image.network("http://via.placeholder.com/${edge.round()}x${edge.round()}", fit: BoxFit.fitHeight),
),
),
),
Container(
width: edge + padding,
padding: EdgeInsets.only(left: padding),
child: Text(
'foo app bar baz app apk',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: EdgeInsets.only(left: padding),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('4.2'),
Icon(
Icons.star,
size: 16,
)
],
),
),
],
)
],
),
),
),
);
}
}
这给出了
您可以修改 PageScrollPhysics
以创建对齐效果。
这就是我所做的,
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(
MaterialApp(
home: new Main(),
),
);
}
class Main extends StatefulWidget {
@override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
static const _scrollPhysics =
const ExtentScrollPhysics(itemExtent: 80, separatorSpacing: 10);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Recommended for you",
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () {},
)
],
),
SizedBox.fromSize(
size: Size.fromHeight(130),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 30,
physics: _scrollPhysics,
separatorBuilder: (context, _) =>
SizedBox(width: _scrollPhysics.dividerSpacing),
itemBuilder: (context, index) {
return SizedBox(
width: _scrollPhysics.itemExtent, // set height for vertical
child: CardItem(),
);
},
),
),
],
),
),
);
}
}
class CardItem extends StatefulWidget {
@override
_CardItemState createState() => _CardItemState();
}
class _CardItemState extends State<CardItem> {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 1,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20.0),
),
),
),
const SizedBox(height: 8.0),
Text(
"Tile Name",
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: const TextStyle(fontSize: 12.0),
),
Text(
"1 MB",
style: const TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
],
);
}
}
class ExtentScrollPhysics extends ScrollPhysics {
final double itemExtent;
final double dividerSpacing;
const ExtentScrollPhysics(
{ScrollPhysics parent, this.itemExtent, double separatorSpacing})
: assert(itemExtent != null && itemExtent > 0),
dividerSpacing = separatorSpacing ?? 0,
super(parent: parent);
@override
ExtentScrollPhysics applyTo(ScrollPhysics ancestor) {
return ExtentScrollPhysics(
parent: buildParent(ancestor),
itemExtent: itemExtent,
separatorSpacing: dividerSpacing,
);
}
double _getItem(ScrollPosition position) {
return position.pixels / (itemExtent + dividerSpacing);
}
double _getPixels(ScrollPosition position, double item) {
return item * (itemExtent + dividerSpacing);
}
double _getTargetPixels(
ScrollPosition position, Tolerance tolerance, double velocity) {
double page = _getItem(position);
if (velocity < -tolerance.velocity)
page -= 0.5;
else if (velocity > tolerance.velocity) page += 0.5;
return _getPixels(position, page.roundToDouble());
}
@override
Simulation createBallisticSimulation(
ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels)
return ScrollSpringSimulation(spring, position.pixels, target, velocity,
tolerance: tolerance);
return null;
}
@override
bool get allowImplicitScrolling => false;
}