Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SliverList and cache content height #1802

Merged
merged 4 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
172 changes: 146 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,123 @@ 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],
);
},
),
),
),
],
),
);
}
}

// Based on the fact that the list content is fixed, we cache
// the height of the content during the first-time scrolling.
// The cached height can be used to override
// `SliverChildDelegate.estimateMaxScrollOffset`, to avoid a shaking scrollbar.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Based on the fact that the list content is fixed, we cache
// the height of the content during the first-time scrolling.
// The cached height can be used to override
// `SliverChildDelegate.estimateMaxScrollOffset`, to avoid a shaking scrollbar.
// 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