mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Use SliverList and cache content height (#1802)
This commit is contained in:
@@ -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);
|
||||
@@ -26,13 +27,7 @@ class FirstComponentList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Fully traverse this list before moving on.
|
||||
return FocusTraversalGroup(
|
||||
child: ListView(
|
||||
padding: showSecondList
|
||||
? const EdgeInsetsDirectional.only(end: smallSpacing)
|
||||
: EdgeInsets.zero,
|
||||
children: [
|
||||
List<Widget> children = [
|
||||
const Actions(),
|
||||
colDivider,
|
||||
const Communication(),
|
||||
@@ -46,6 +41,30 @@ class FirstComponentList extends StatelessWidget {
|
||||
colDivider,
|
||||
const TextInputs()
|
||||
],
|
||||
];
|
||||
List<double?> heights = List.filled(children.length, null);
|
||||
|
||||
// Fully traverse this list before moving on.
|
||||
return FocusTraversalGroup(
|
||||
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],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -62,22 +81,128 @@ class SecondComponentList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Fully traverse this list before moving on.
|
||||
return FocusTraversalGroup(
|
||||
child: ListView(
|
||||
padding: const EdgeInsetsDirectional.only(end: smallSpacing),
|
||||
children: <Widget>[
|
||||
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: 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});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -26,13 +27,7 @@ class FirstComponentList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Fully traverse this list before moving on.
|
||||
return FocusTraversalGroup(
|
||||
child: ListView(
|
||||
padding: showSecondList
|
||||
? const EdgeInsetsDirectional.only(end: smallSpacing)
|
||||
: EdgeInsets.zero,
|
||||
children: [
|
||||
List<Widget> children = [
|
||||
const Actions(),
|
||||
colDivider,
|
||||
const Communication(),
|
||||
@@ -46,6 +41,30 @@ class FirstComponentList extends StatelessWidget {
|
||||
colDivider,
|
||||
const TextInputs()
|
||||
],
|
||||
];
|
||||
List<double?> heights = List.filled(children.length, null);
|
||||
|
||||
// Fully traverse this list before moving on.
|
||||
return FocusTraversalGroup(
|
||||
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],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -62,22 +81,128 @@ class SecondComponentList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Fully traverse this list before moving on.
|
||||
return FocusTraversalGroup(
|
||||
child: ListView(
|
||||
padding: const EdgeInsetsDirectional.only(end: smallSpacing),
|
||||
children: <Widget>[
|
||||
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: 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});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user