Skip to content

Commit

Permalink
Use SliverList and cache content height (#1802)
Browse files Browse the repository at this point in the history
  • Loading branch information
QuncCccccc committed May 12, 2023
1 parent 3dc607a commit 1d6e5a0
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 62 deletions.
177 changes: 151 additions & 26 deletions experimental/material_3_demo/lib/component_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

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

const rowDivider = SizedBox(width: 20);
Expand All @@ -26,26 +27,44 @@ class FirstComponentList extends StatelessWidget {

@override
Widget build(BuildContext context) {
List<Widget> children = [
const Actions(),
colDivider,
const Communication(),
colDivider,
const Containment(),
if (!showSecondList) ...[
colDivider,
Navigation(scaffoldKey: scaffoldKey),
colDivider,
const Selection(),
colDivider,
const TextInputs()
],
];
List<double?> heights = List.filled(children.length, null);

// Fully traverse this list before moving on.
return FocusTraversalGroup(
child: ListView(
padding: showSecondList
? const EdgeInsetsDirectional.only(end: smallSpacing)
: EdgeInsets.zero,
children: [
const Actions(),
colDivider,
const Communication(),
colDivider,
const Containment(),
if (!showSecondList) ...[
colDivider,
Navigation(scaffoldKey: scaffoldKey),
colDivider,
const Selection(),
colDivider,
const TextInputs()
],
child: CustomScrollView(
slivers: [
SliverPadding(
padding: showSecondList
? const EdgeInsetsDirectional.only(end: smallSpacing)
: EdgeInsets.zero,
sliver: SliverList(
delegate: BuildSlivers(
heights: heights,
builder: (context, index) {
return _CacheHeight(
heights: heights,
index: index,
child: children[index],
);
},
),
),
),
],
),
);
Expand All @@ -62,22 +81,128 @@ class SecondComponentList extends StatelessWidget {

@override
Widget build(BuildContext context) {
List<Widget> children = [
Navigation(scaffoldKey: scaffoldKey),
colDivider,
const Selection(),
colDivider,
const TextInputs(),
];
List<double?> heights = List.filled(children.length, null);

// Fully traverse this list before moving on.
return FocusTraversalGroup(
child: ListView(
padding: const EdgeInsetsDirectional.only(end: smallSpacing),
children: <Widget>[
Navigation(scaffoldKey: scaffoldKey),
colDivider,
const Selection(),
colDivider,
const TextInputs(),
child: CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsetsDirectional.only(end: smallSpacing),
sliver: SliverList(
delegate: BuildSlivers(
heights: heights,
builder: (context, index) {
return _CacheHeight(
heights: heights,
index: index,
child: children[index],
);
},
),
),
),
],
),
);
}
}

// If the content of a CustomScrollView does not change, then it's
// safe to cache the heights of each item as they are laid out. The
// sum of the cached heights are returned by an override of
// `SliverChildDelegate.estimateMaxScrollOffset`. The default version
// of this method bases its estimate on the average height of the
// visible items. The override ensures that the scrollbar thumb's
// size, which depends on the max scroll offset, will shrink smoothly
// as the contents of the list are exposed for the first time, and
// then remain fixed.
class _CacheHeight extends SingleChildRenderObjectWidget {
const _CacheHeight({
super.child,
required this.heights,
required this.index,
});

final List<double?> heights;
final int index;

@override
RenderObject createRenderObject(BuildContext context) {
return _RenderCacheHeight(
heights: heights,
index: index,
);
}

@override
void updateRenderObject(
BuildContext context, _RenderCacheHeight renderObject) {
renderObject
..heights = heights
..index = index;
}
}

class _RenderCacheHeight extends RenderProxyBox {
_RenderCacheHeight({
required List<double?> heights,
required int index,
}) : _heights = heights,
_index = index,
super();

List<double?> _heights;
List<double?> get heights => _heights;
set heights(List<double?> value) {
if (value == _heights) {
return;
}
_heights = value;
markNeedsLayout();
}

int _index;
int get index => _index;
set index(int value) {
if (value == index) {
return;
}
_index = value;
markNeedsLayout();
}

@override
void performLayout() {
super.performLayout();
heights[index] = size.height;
}
}

// The heights information is used to override the `estimateMaxScrollOffset` and
// provide a more accurate estimation for the max scroll offset.
class BuildSlivers extends SliverChildBuilderDelegate {
BuildSlivers({
required NullableIndexedWidgetBuilder builder,
required this.heights,
}) : super(builder, childCount: heights.length);

final List<double?> heights;

@override
double? estimateMaxScrollOffset(int firstIndex, int lastIndex,
double leadingScrollOffset, double trailingScrollOffset) {
return heights.reduce((sum, height) => (sum ?? 0) + (height ?? 0))!;
}
}

class Actions extends StatelessWidget {
const Actions({super.key});

Expand Down
5 changes: 0 additions & 5 deletions experimental/material_3_demo/lib/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,6 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
return const TypographyScreen();
case ScreenSelected.elevation:
return const ElevationScreen();
default:
return FirstComponentList(
showNavBottomBar: showNavBarExample,
scaffoldKey: scaffoldKey,
showSecondList: showMediumSizeLayout || showLargeSizeLayout);
}
}

Expand Down

0 comments on commit 1d6e5a0

Please sign in to comment.