【问题标题】:Flutter: Making clipped area transparent for scrolling ListViewFlutter:使剪切区域透明以滚动 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 的容器(黄色边框)底部时,它就看不见了。但我希望列表只有在到达我的剪辑边缘时才会平滑消失(即蓝色区域 - 就像在第二个屏幕截图中一样)。

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

【问题讨论】:

    标签: flutter listview clipping


    【解决方案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 小部件,因为它引发了异常......否则它可以完美运行!
    猜你喜欢
    • 2011-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-01
    • 2010-11-22
    • 2015-03-28
    • 2021-08-12
    • 1970-01-01
    相关资源
    最近更新 更多