Flutter:使剪辑区域透明以滚动 ListView
Flutter: Making clipped area transparent for scrolling ListView
我有一个 ListView
,我想在它碰到另一个小部件的剪辑时“消失”。
这是我的代码
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TopWidget(),
Expanded(
child: ListView(
itemExtent: 100,
children: <Widget>[
Card(color: Colors.green,),
],
),
),
],
),
);
}
}
class TopWidget extends StatelessWidget {
TopWidget();
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: ShadowPainter(),
child: ClipPath(
clipper: TopWidgetClipper(),
child: Container(
height: 370,
color: Colors.blue,
),
),
);
}
}
class TopWidgetClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Offset controllPoint1 = Offset(0, size.height - 100);
Offset endPoint1 = Offset(100, size.height - 100);
Offset controllPoint2 = Offset(size.width, size.height - 100);
Offset endPoint2 = Offset(size.width, size.height - 200);
Path path = Path()
..lineTo(0, size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(size.width - 100, size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(size.width, 0);
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
class ShadowPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
Offset controllPoint1 = Offset(0, size.height - 100);
Offset endPoint1 = Offset(100, size.height - 100);
Offset controllPoint2 = Offset(size.width, size.height - 100);
Offset endPoint2 = Offset(size.width, size.height - 200);
Path path = Path()
..lineTo(0, size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(size.width - 100, size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(size.width, 0);
canvas.drawShadow(path, Colors.grey[50], 3.0, false);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
到目前为止,当我向下滚动时,当列表(绿色框)到达我剪辑的容器(黄色边框)的底部时,它就看不见了 TopWidget
。但我希望列表仅在到达我的剪辑边缘时才平滑消失(即蓝色区域 - 如第二个屏幕截图所示)。
有什么办法可以做到这一点吗?谢谢!
正如我从@pskink 那里学到的(感谢),在这样的用例中,您需要小部件实际调整其边界(剧透:形状),您应该使用 shape
属性 的不同小部件,并在扩展 ShapeBorder
的自定义 class 中使用您用于此示例的 Path
。最简单的方法是:
Container(
height: 370,
decoration: ShapeDecoration(
color: Colors.blue,
shape: AppBarBorder(),
/// You can also specify some neat shadows to cast on widgets scrolling under this one
shadows: [
BoxShadow(
color: Colors.black.withOpacity(0.7),
blurRadius: 18.0,
spreadRadius: 2.0,
),
],
),
),
和自定义 class:
class AppBarBorder extends ShapeBorder {
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
Offset controllPoint1 = Offset(0, rect.size.height - 100);
Offset endPoint1 = Offset(100, rect.size.height - 100);
Offset controllPoint2 = Offset(rect.size.width, rect.size.height - 100);
Offset endPoint2 = Offset(rect.size.width, rect.size.height - 200);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - 100, rect.size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => this;
}
与声明 CustomClipper
或 CustomPainter
的方法几乎相同,因为您不需要实现其中的大部分方法,基本上只需要关心 getOuterPath
.
最后我们需要重新构建布局本身,因为目前您有一个 Column
具有此自定义 Container
形状和其下方的 ListView
。由于 Container
不是 ListView
的一部分,因此无法在下方滚动或其他内容。最简单的方法是使用 Stack
:
Stack(
children: [
Expanded(
child: ListView(
padding: EdgeInsets.only(top: 370.0),
itemExtent: 100,
children: <Widget>[
Card(
color: Colors.green,
),
],
),
),
Container(
height: 370,
decoration: ShapeDecoration(
color: Colors.blue,
shape: AppBarBorder(),
shadows: [
BoxShadow(
color: Colors.black.withOpacity(0.7),
blurRadius: 18.0,
spreadRadius: 2.0,
),
],
),
),
],
),
我有一个 ListView
,我想在它碰到另一个小部件的剪辑时“消失”。
这是我的代码
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TopWidget(),
Expanded(
child: ListView(
itemExtent: 100,
children: <Widget>[
Card(color: Colors.green,),
],
),
),
],
),
);
}
}
class TopWidget extends StatelessWidget {
TopWidget();
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: ShadowPainter(),
child: ClipPath(
clipper: TopWidgetClipper(),
child: Container(
height: 370,
color: Colors.blue,
),
),
);
}
}
class TopWidgetClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Offset controllPoint1 = Offset(0, size.height - 100);
Offset endPoint1 = Offset(100, size.height - 100);
Offset controllPoint2 = Offset(size.width, size.height - 100);
Offset endPoint2 = Offset(size.width, size.height - 200);
Path path = Path()
..lineTo(0, size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(size.width - 100, size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(size.width, 0);
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
class ShadowPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
Offset controllPoint1 = Offset(0, size.height - 100);
Offset endPoint1 = Offset(100, size.height - 100);
Offset controllPoint2 = Offset(size.width, size.height - 100);
Offset endPoint2 = Offset(size.width, size.height - 200);
Path path = Path()
..lineTo(0, size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(size.width - 100, size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(size.width, 0);
canvas.drawShadow(path, Colors.grey[50], 3.0, false);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
到目前为止,当我向下滚动时,当列表(绿色框)到达我剪辑的容器(黄色边框)的底部时,它就看不见了 TopWidget
。但我希望列表仅在到达我的剪辑边缘时才平滑消失(即蓝色区域 - 如第二个屏幕截图所示)。
有什么办法可以做到这一点吗?谢谢!
正如我从@pskink 那里学到的(感谢),在这样的用例中,您需要小部件实际调整其边界(剧透:形状),您应该使用 shape
属性 的不同小部件,并在扩展 ShapeBorder
的自定义 class 中使用您用于此示例的 Path
。最简单的方法是:
Container(
height: 370,
decoration: ShapeDecoration(
color: Colors.blue,
shape: AppBarBorder(),
/// You can also specify some neat shadows to cast on widgets scrolling under this one
shadows: [
BoxShadow(
color: Colors.black.withOpacity(0.7),
blurRadius: 18.0,
spreadRadius: 2.0,
),
],
),
),
和自定义 class:
class AppBarBorder extends ShapeBorder {
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
Offset controllPoint1 = Offset(0, rect.size.height - 100);
Offset endPoint1 = Offset(100, rect.size.height - 100);
Offset controllPoint2 = Offset(rect.size.width, rect.size.height - 100);
Offset endPoint2 = Offset(rect.size.width, rect.size.height - 200);
return Path()
..lineTo(0, rect.size.height)
..quadraticBezierTo(
controllPoint1.dx, controllPoint1.dy, endPoint1.dx, endPoint1.dy)
..lineTo(rect.size.width - 100, rect.size.height - 100)
..quadraticBezierTo(
controllPoint2.dx, controllPoint2.dy, endPoint2.dx, endPoint2.dy)
..lineTo(rect.size.width, 0);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: 0);
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
@override
ShapeBorder scale(double t) => this;
}
与声明 CustomClipper
或 CustomPainter
的方法几乎相同,因为您不需要实现其中的大部分方法,基本上只需要关心 getOuterPath
.
最后我们需要重新构建布局本身,因为目前您有一个 Column
具有此自定义 Container
形状和其下方的 ListView
。由于 Container
不是 ListView
的一部分,因此无法在下方滚动或其他内容。最简单的方法是使用 Stack
:
Stack(
children: [
Expanded(
child: ListView(
padding: EdgeInsets.only(top: 370.0),
itemExtent: 100,
children: <Widget>[
Card(
color: Colors.green,
),
],
),
),
Container(
height: 370,
decoration: ShapeDecoration(
color: Colors.blue,
shape: AppBarBorder(),
shadows: [
BoxShadow(
color: Colors.black.withOpacity(0.7),
blurRadius: 18.0,
spreadRadius: 2.0,
),
],
),
),
],
),