Flutter:使剪切区域透明以滚动 ListView

Posted

技术标签:

【中文标题】Flutter:使剪切区域透明以滚动 ListView【英文标题】:Flutter: Making clipped area transparent for scrolling ListView 【发布时间】:2021-02-19 05:30:52 【问题描述】:

我有一个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 的容器(黄色边框)底部时,它就看不见了。但我希望列表只有在到达我的剪辑边缘时才会平滑消失(即蓝色区域 - 就像在第二个屏幕截图中一样)。

有什么想法可以做到这一点吗?谢谢!

【问题讨论】:

【参考方案1】:

正如我从 @pskink 那里学到的(谢谢),在这样的用例中,您需要小部件实际调整其边界(剧透:形状),您应该使用不同小部件的 shape 属性和在扩展ShapeBorder 的自定义类中使用您用于此示例的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 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,
          ),
        ],
      ),
    ),
  ],
),

【讨论】:

太棒了!太感谢了!我只需要删除 ListView 周围的 Expanded 小部件,因为它引发了异常......否则它可以完美运行!

以上是关于Flutter:使剪切区域透明以滚动 ListView的主要内容,如果未能解决你的问题,请参考以下文章

如何让 Flutter 应用在​​ android 导航栏后面绘制并使导航栏完全透明?

Flutter监听滚动动作 控制组件 透明度渐变 ( 移除顶部状态栏空白 | 帧布局组件 | 透明度组件 | 监听滚动组件 )

Flutter:在滚动时更改小部件不透明度和颜色的最佳方法

堆栈中 ListView 顶部的 GestureDetector 阻止滚动 - Flutter Web

使上传区域上的选定区域透明

滚动时如何使透明导航栏可见