1
+ import 'dart:math' as math;
2
+
3
+ import 'package:flutter/foundation.dart' ;
1
4
import 'package:flutter/material.dart' ;
2
5
import 'package:flutter/rendering.dart' ;
3
6
@@ -322,6 +325,8 @@ class MessageListViewport extends CustomPaintOrderViewport {
322
325
323
326
/// The version of [RenderViewport] that underlies [MessageListViewport]
324
327
/// and [MessageListScrollView] .
328
+ // TODO(upstream): Devise upstream APIs to obviate the duplicated code here;
329
+ // use `git log -L` to see what edits we've made locally.
325
330
class RenderMessageListViewport extends RenderCustomPaintOrderViewport {
326
331
RenderMessageListViewport ({
327
332
super .axisDirection,
@@ -335,4 +340,211 @@ class RenderMessageListViewport extends RenderCustomPaintOrderViewport {
335
340
super .clipBehavior,
336
341
required super .paintOrder_,
337
342
});
343
+
344
+ double ? _calculatedCacheExtent;
345
+
346
+ @override
347
+ Rect describeSemanticsClip (RenderSliver ? child) {
348
+ if (_calculatedCacheExtent == null ) {
349
+ return semanticBounds;
350
+ }
351
+
352
+ switch (axis) {
353
+ case Axis .vertical:
354
+ return Rect .fromLTRB (
355
+ semanticBounds.left,
356
+ semanticBounds.top - _calculatedCacheExtent! ,
357
+ semanticBounds.right,
358
+ semanticBounds.bottom + _calculatedCacheExtent! ,
359
+ );
360
+ case Axis .horizontal:
361
+ return Rect .fromLTRB (
362
+ semanticBounds.left - _calculatedCacheExtent! ,
363
+ semanticBounds.top,
364
+ semanticBounds.right + _calculatedCacheExtent! ,
365
+ semanticBounds.bottom,
366
+ );
367
+ }
368
+ }
369
+
370
+ static const int _maxLayoutCyclesPerChild = 10 ;
371
+
372
+ // Out-of-band data computed during layout.
373
+ late double _minScrollExtent;
374
+ late double _maxScrollExtent;
375
+ bool _hasVisualOverflow = false ;
376
+
377
+ @override
378
+ void performLayout () {
379
+ // Ignore the return value of applyViewportDimension because we are
380
+ // doing a layout regardless.
381
+ switch (axis) {
382
+ case Axis .vertical:
383
+ offset.applyViewportDimension (size.height);
384
+ case Axis .horizontal:
385
+ offset.applyViewportDimension (size.width);
386
+ }
387
+
388
+ if (center == null ) {
389
+ assert (firstChild == null );
390
+ _minScrollExtent = 0.0 ;
391
+ _maxScrollExtent = 0.0 ;
392
+ _hasVisualOverflow = false ;
393
+ offset.applyContentDimensions (0.0 , 0.0 );
394
+ return ;
395
+ }
396
+ assert (center! .parent == this );
397
+
398
+ final (double mainAxisExtent, double crossAxisExtent) = switch (axis) {
399
+ Axis .vertical => (size.height, size.width),
400
+ Axis .horizontal => (size.width, size.height),
401
+ };
402
+
403
+ final double centerOffsetAdjustment = center! .centerOffsetAdjustment;
404
+ final int maxLayoutCycles = _maxLayoutCyclesPerChild * childCount;
405
+
406
+ double correction;
407
+ int count = 0 ;
408
+ do {
409
+ correction = _attemptLayout (
410
+ mainAxisExtent,
411
+ crossAxisExtent,
412
+ offset.pixels + centerOffsetAdjustment,
413
+ );
414
+ if (correction != 0.0 ) {
415
+ offset.correctBy (correction);
416
+ } else {
417
+ if (offset.applyContentDimensions (
418
+ math.min (0.0 , _minScrollExtent + mainAxisExtent * anchor),
419
+ math.max (0.0 , _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),
420
+ )) {
421
+ break ;
422
+ }
423
+ }
424
+ count += 1 ;
425
+ } while (count < maxLayoutCycles);
426
+ assert (() {
427
+ if (count >= maxLayoutCycles) {
428
+ assert (count != 1 );
429
+ throw FlutterError (
430
+ 'A RenderViewport exceeded its maximum number of layout cycles.\n '
431
+ 'RenderViewport render objects, during layout, can retry if either their '
432
+ 'slivers or their ViewportOffset decide that the offset should be corrected '
433
+ 'to take into account information collected during that layout.\n '
434
+ 'In the case of this RenderViewport object, however, this happened $count '
435
+ 'times and still there was no consensus on the scroll offset. This usually '
436
+ 'indicates a bug. Specifically, it means that one of the following three '
437
+ 'problems is being experienced by the RenderViewport object:\n '
438
+ ' * One of the RenderSliver children or the ViewportOffset have a bug such'
439
+ ' that they always think that they need to correct the offset regardless.\n '
440
+ ' * Some combination of the RenderSliver children and the ViewportOffset'
441
+ ' have a bad interaction such that one applies a correction then another'
442
+ ' applies a reverse correction, leading to an infinite loop of corrections.\n '
443
+ ' * There is a pathological case that would eventually resolve, but it is'
444
+ ' so complicated that it cannot be resolved in any reasonable number of'
445
+ ' layout passes.' ,
446
+ );
447
+ }
448
+ return true ;
449
+ }());
450
+ }
451
+
452
+ double _attemptLayout (double mainAxisExtent, double crossAxisExtent, double correctedOffset) {
453
+ assert (! mainAxisExtent.isNaN);
454
+ assert (mainAxisExtent >= 0.0 );
455
+ assert (crossAxisExtent.isFinite);
456
+ assert (crossAxisExtent >= 0.0 );
457
+ assert (correctedOffset.isFinite);
458
+ _minScrollExtent = 0.0 ;
459
+ _maxScrollExtent = 0.0 ;
460
+ _hasVisualOverflow = false ;
461
+
462
+ // centerOffset is the offset from the leading edge of the RenderViewport
463
+ // to the zero scroll offset (the line between the forward slivers and the
464
+ // reverse slivers).
465
+ final double centerOffset = mainAxisExtent * anchor - correctedOffset;
466
+ final double reverseDirectionRemainingPaintExtent = clampDouble (
467
+ centerOffset,
468
+ 0.0 ,
469
+ mainAxisExtent,
470
+ );
471
+ final double forwardDirectionRemainingPaintExtent = clampDouble (
472
+ mainAxisExtent - centerOffset,
473
+ 0.0 ,
474
+ mainAxisExtent,
475
+ );
476
+
477
+ _calculatedCacheExtent = switch (cacheExtentStyle) {
478
+ CacheExtentStyle .pixel => cacheExtent,
479
+ CacheExtentStyle .viewport => mainAxisExtent * cacheExtent! ,
480
+ };
481
+
482
+ final double fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent! ;
483
+ final double centerCacheOffset = centerOffset + _calculatedCacheExtent! ;
484
+ final double reverseDirectionRemainingCacheExtent = clampDouble (
485
+ centerCacheOffset,
486
+ 0.0 ,
487
+ fullCacheExtent,
488
+ );
489
+ final double forwardDirectionRemainingCacheExtent = clampDouble (
490
+ fullCacheExtent - centerCacheOffset,
491
+ 0.0 ,
492
+ fullCacheExtent,
493
+ );
494
+
495
+ final RenderSliver ? leadingNegativeChild = childBefore (center! );
496
+
497
+ if (leadingNegativeChild != null ) {
498
+ // negative scroll offsets
499
+ final double result = layoutChildSequence (
500
+ child: leadingNegativeChild,
501
+ scrollOffset: math.max (mainAxisExtent, centerOffset) - mainAxisExtent,
502
+ overlap: 0.0 ,
503
+ layoutOffset: forwardDirectionRemainingPaintExtent,
504
+ remainingPaintExtent: reverseDirectionRemainingPaintExtent,
505
+ mainAxisExtent: mainAxisExtent,
506
+ crossAxisExtent: crossAxisExtent,
507
+ growthDirection: GrowthDirection .reverse,
508
+ advance: childBefore,
509
+ remainingCacheExtent: reverseDirectionRemainingCacheExtent,
510
+ cacheOrigin: clampDouble (mainAxisExtent - centerOffset, - _calculatedCacheExtent! , 0.0 ),
511
+ );
512
+ if (result != 0.0 ) {
513
+ return - result;
514
+ }
515
+ }
516
+
517
+ // positive scroll offsets
518
+ return layoutChildSequence (
519
+ child: center,
520
+ scrollOffset: math.max (0.0 , - centerOffset),
521
+ overlap: leadingNegativeChild == null ? math.min (0.0 , - centerOffset) : 0.0 ,
522
+ layoutOffset:
523
+ centerOffset >= mainAxisExtent ? centerOffset : reverseDirectionRemainingPaintExtent,
524
+ remainingPaintExtent: forwardDirectionRemainingPaintExtent,
525
+ mainAxisExtent: mainAxisExtent,
526
+ crossAxisExtent: crossAxisExtent,
527
+ growthDirection: GrowthDirection .forward,
528
+ advance: childAfter,
529
+ remainingCacheExtent: forwardDirectionRemainingCacheExtent,
530
+ cacheOrigin: clampDouble (centerOffset, - _calculatedCacheExtent! , 0.0 ),
531
+ );
532
+ }
533
+
534
+ @override
535
+ bool get hasVisualOverflow => _hasVisualOverflow;
536
+
537
+ @override
538
+ void updateOutOfBandData (GrowthDirection growthDirection, SliverGeometry childLayoutGeometry) {
539
+ switch (growthDirection) {
540
+ case GrowthDirection .forward:
541
+ _maxScrollExtent += childLayoutGeometry.scrollExtent;
542
+ case GrowthDirection .reverse:
543
+ _minScrollExtent -= childLayoutGeometry.scrollExtent;
544
+ }
545
+ if (childLayoutGeometry.hasVisualOverflow) {
546
+ _hasVisualOverflow = true ;
547
+ }
548
+ }
549
+
338
550
}
0 commit comments