【问题标题】:How to mask-out the overlaped section, visible through the "translucent header sliver" in the NestedScrollView?如何屏蔽重叠部分,通过 NestedScrollView 中的“半透明标题条”可见?
【发布时间】:2020-09-21 19:20:09
【问题描述】:

以下代码生成一个可滚动列表以及一个“半透明固定条头”。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return [
              SliverPersistentHeader(
                delegate: _SliverPersistentHeaderDelegate(),
                pinned: true,
              ),
            ];
          },
          body: ListView.builder(
            itemBuilder: (context, index) {
              return ListTile(
                title: Container(
                  color: Colors.amber.withOpacity(0.3),
                  child: Text('Item $index'),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      color: Colors.blue.withOpacity(0.75),
      child: Placeholder(),
    );
  }

  @override double get maxExtent => 300;
  @override double get minExtent => 200;
  @override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}

一切都好;除了,我需要“标题”是透明的,但是让它半透明会导致下面的列表项被显示出来(如下面的屏幕截图所示)。

那么,如何“屏蔽”通过“半透明标题”可见的“列表项”?

【问题讨论】:

  • 如果你希望它是“半透明的”,如果不是列表项,你希望看到什么?
  • @matehat “标题”将包含一些小部件;此外,整个事物将被“覆盖”在背景层上。但是,代码都很好,除了我希望 "list of items" 在它自己的 view"framed" (到不能通过“标题”观察到)。

标签: flutter mask flutter-sliver


【解决方案1】:

CustomClipper 用于 List 本身怎么样?因为滚动时列表高度是动态的,所以剪辑高度必须动态计算。所以我将 clipHeight 传递给自定义剪裁器。

为了获得剪辑高度,我使用MediaQuery.of(context).size.height - 标题高度。所以我创建了另一个类来获取这个值。

      ...
      body: CustomWidget (
        child: ListView.builder(
        ...


class CustomWidget extends StatelessWidget {

 final Widget child;

 CustomWidget({this.child,Key key}):super(key:key);

  @override
  Widget build(BuildContext context) {
    return ClipRect(
      clipper: MyCustomClipper(clipHeight: MediaQuery.of(context).size.height-200),
      child: child,
    );
  }
}

class MyCustomClipper extends CustomClipper<Rect>{

  final double clipHeight;

  MyCustomClipper({this.clipHeight});

  @override
  getClip(Size size) {
    double top = math.max(size.height - clipHeight,0) ;
    Rect rect = Rect.fromLTRB(0.0, top, size.width, size.height);
    return rect;
  }

  @override
  bool shouldReclip(CustomClipper oldClipper) {
    return false;
  }
}

【讨论】:

  • 这是迄今为止最好的解决方案;直观且高效!非常感谢。
【解决方案2】:

固定SliverPersistentHeader 的作用类似于“CSS position: absolute。 所以你的身体小部件不知道上面有什么东西。 一种选择是不使用SliverPersistentHeader

import 'package:flutter/material.dart';
import 'dart:math' as math;

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  ScrollController controller;

  @override
  void initState() {
    currentHeight = _maxExtent;
    controller = ScrollController();
    controller.addListener(() {
      _updateHeaderHeight();
    });
    super.initState();
  }

  _updateHeaderHeight() {
    double offset = controller.offset;
    if (offset <= _maxExtent - _minExtent) {
      setState(() {
        currentHeight = math.max(_maxExtent - offset, _minExtent);
      });
    }
  }

  double currentHeight;
  final double _maxExtent = 300;
  final double _minExtent = 200;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: DecoratedBox(
          // only to prove transparency
          decoration: BoxDecoration(
            image: DecorationImage(
              colorFilter: ColorFilter.mode(Colors.white, BlendMode.color),
              image: NetworkImage(
                'https://picsum.photos/720/1280',
              ),
              fit: BoxFit.cover,
            ),
          ),
          child: Stack(
            children: [
              Header(currentHeight: currentHeight),
              Padding(
                padding: EdgeInsets.only(top: currentHeight),
                child: Container(
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.blueAccent),
                  ),
                  child: ListView.builder(
                    controller: controller,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Container(
                          color: Colors.amber.withOpacity(0.3),
                          child: Text('Item $index'),
                        ),
                      );
                    },
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class Header extends StatelessWidget {
  const Header({Key key, this.currentHeight}) : super(key: key);

  final double currentHeight;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: currentHeight,
      color: Colors.blue.withOpacity(0.75),
      child: Placeholder(),
    );
  }
}

【讨论】:

  • 使用固定SliverPersistentHeader 的原因是它非常高效;如果只有“项目列表”可以在其“领土”中以某种方式“框架”,那么它也将是高效的。但是,您的代码确实提供了正确的解决方案,但是该方法在 scroll 事件上重建了 Header 小部件以及列表的“容器”小部件;滚动时更新每一帧的currentHeight,它们共同导致一些性能瓶颈。您能否优化您的解决方案以获得更好的性能。感谢您为此付出的努力。
  • 我们只在滚动偏移的小范围内触发setState方法,当我们需要改变header widget的高度时。
  • 这正是瓶颈发生的地方(“重建”)!这就是为什么我想以某种方式在他们的“领土”中“框定”“条子项目列表”!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-24
  • 2021-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多