Flutter:如何处理 SliverPersistentHeader 中的 Renderflex 溢出

Phe*_*ter 6 flutter flutter-sliver flutter-renderflex-error

我有一个包含视频的 SliverPersistentHeader。此视图所需的行为是,当用户向上滚动时,视图应覆盖或最小化视频的大小。视频标题是一个包含 Chewie 视频播放器的小部件。所需的行为会达到某个点,此时我会出现像素溢出,如该动画所示:

在此输入图像描述

当滚动到达某个点时,视频无法再调整大小,从而导致渲染溢出。所需的行为是视频继续调整大小直到消失,或者捕获错误并从视图中隐藏或删除视频。呈现此滚动视图的代码是:

  Widget buildScollView(GenericScreenModel model) {
    return CustomScrollView(
      slivers: [
        StandardHeader(),
        SliverFillRemaining(
          child: Container(
            // color: Colors.transparent,
              decoration: BoxDecoration(
                  border: Border.all(
                    color: Colors.white,
                  ),
                  borderRadius: BorderRadius.only(topRight: radius, topLeft: radius)),
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Text(model.model?.getContentText ?? 'Empty'),
              )),
        )
      ],
    );
  }
Run Code Online (Sandbox Code Playgroud)

StandardHeader 类是一个包含 Chewie 视频的简单小部件。

class _StandardHeaderState extends State<StandardHeader> {
  @override
  Widget build(BuildContext context) {
    return SliverPersistentHeader(
      floating: true,
      delegate: Delegate(
        Colors.blue,
        'Header Title',
      ),
      pinned: true,
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

有没有办法捕获此错误并隐藏视频播放器?任何人都可以帮忙解决这个问题或向我指出资源吗?谢谢!

off*_*ome 1

问题似乎与 Chewie 和/或视频播放器小部件有关。如果标题的高度小于播放器所需的高度,则会发生溢出。

您可以通过使用 来达到所需的效果SingleChildRenderObjectWidget。我添加了一个不透明因素,您可以轻松删除它,这给了它(在我看来)额外的触感。

我将这个小部件命名为:ClipBelowHeight

输出:

渲染输出.gif

来源:

ClipBelowHeight是通过使用参数将子项的高度限制为不会溢出的高度来SingleChildRenderObjectWidget添加所需的效果。clipHeight它将其子项垂直居中(在本例中为 Chewie 播放器)。

performLayout要了解更多信息,请阅读and方法内的注释paint

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class ClipBelowHeight extends SingleChildRenderObjectWidget {
  const ClipBelowHeight({
    super.key,
    super.child,
    required this.clipHeight,
    required this.opacityFactor,
  });

  /// The minimum height the [child] must have, as well as the height at which
  /// clipping begins.
  final double clipHeight;

  /// The opacity factor to apply when the height decreases.
  final double opacityFactor;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderClipBelowHeight(clipHeight: clipHeight, factor: opacityFactor);
  }

  @override
  void updateRenderObject(
    BuildContext context,
    RenderClipBelowHeight renderObject,
  ) {
    renderObject
      ..clipHeight = clipHeight
      ..factor = opacityFactor;
  }
}

class RenderClipBelowHeight extends RenderBox with RenderObjectWithChildMixin {
  RenderClipBelowHeight({required double clipHeight, required double factor})
      : _clipHeight = clipHeight,
        _factor = factor;

  double _clipHeight;
  double get clipHeight => _clipHeight;
  set clipHeight(double value) {
    assert(value >= .0);
    if (_clipHeight == value) return;
    _clipHeight = value;
    markNeedsLayout();
  }

  double _factor;
  double get factor => _factor;
  set factor(double value) {
    assert(value >= .0);
    if (_factor == value) return;
    _factor = value;
    markNeedsLayout();
  }

  @override
  bool get sizedByParent => false;

  @override
  void performLayout() {
    /// The child contraints depend on whether [constraints.maxHeight] is less
    /// than [clipHeight]. This RenderObject's responsibility is to ensure that
    /// the child's height is never below [clipHeight], because when the
    /// child's height is below [clipHeight], then there will be visual
    /// overflow.
    final childConstraints = constraints.maxHeight < _clipHeight
        ? BoxConstraints.tight(Size(constraints.maxWidth, _clipHeight))
        : constraints;

    (child as RenderBox).layout(childConstraints, parentUsesSize: true);

    size = Size(constraints.maxWidth, constraints.maxHeight);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final theChild = child as RenderBox;

    /// Clip the painted area to [size], which allows the [child] height to
    /// be greater than [size] without overflowing.
    context.pushClipRect(
      true,
      offset,
      Offset.zero & size,
      (PaintingContext context, Offset offset) {
        /// (optional) Set the opacity by applying the specified factor.
        context.pushOpacity(
          offset,
          /// The opacity begins to take effect at approximately half [size].
          ((255.0 + 128.0) * _factor).toInt(),
          (context, offset) {
            /// Ensure the child remains centered vertically based on [size].
            final centeredOffset =
                Offset(.0, (size.height - theChild.size.height) / 2.0);

            context.paintChild(theChild, centeredOffset + offset);
          },
        );
      },
    );
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    final theChild = child as RenderBox;
    var childParentData = theChild.parentData as BoxParentData;

    final isHit = result.addWithPaintOffset(
      offset: childParentData.offset,
      position: position,
      hitTest: (BoxHitTestResult result, Offset transformed) {
        assert(transformed == position - childParentData.offset);
        return theChild.hitTest(result, position: transformed);
      },
    );

    return isHit;
  }

  @override
  Size computeDryLayout(BoxConstraints constraints) => constraints.biggest;

  @override
  double computeMinIntrinsicWidth(double height) =>
      (child as RenderBox).getMinIntrinsicWidth(height);

  @override
  double computeMaxIntrinsicWidth(double height) =>
      (child as RenderBox).getMaxIntrinsicWidth(height);

  @override
  double computeMinIntrinsicHeight(double width) =>
      (child as RenderBox).getMinIntrinsicHeight(width);

  @override
  double computeMaxIntrinsicHeight(double width) =>
      (child as RenderBox).getMaxIntrinsicHeight(width);
}
Run Code Online (Sandbox Code Playgroud)

使用该ClipBelowHeight小部件的小部件是您的标头委托。这个小部件应该是不言自明的,我认为您能够理解它。

class Delegate extends SliverPersistentHeaderDelegate {
  Delegate(this.color, this.player);

  final Color color;
  final Chewie player;

  @override
  Widget build(
    BuildContext context,
    double shrinkOffset,
    bool overlapsContent,
  ) {
    return Container(
      color: color,
      child: ClipBelowHeight(
        clipHeight: 80.0,
        opacityFactor: 1.0 - shrinkOffset / maxExtent,
        child: player,
      ),
    );
  }

  @override
  double get maxExtent => 150.0;

  @override
  double get minExtent => .0;

  @override
  bool shouldRebuild(Delegate oldDelegate) {
    return color != oldDelegate.color || player != oldDelegate.player;
  }
}
Run Code Online (Sandbox Code Playgroud)