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;
}

与声明 CustomClipperCustomPainter 的方法几乎相同,因为您不需要实现其中的大部分方法,基本上只需要关心 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,
          ),
        ],
      ),
    ),
  ],
),