fix performance issues
This commit is contained in:
+50
-21
@@ -18,6 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'
|
||||||
show MdiIcons;
|
show MdiIcons;
|
||||||
@@ -68,6 +69,44 @@ _TopSitesGridLayout _resolveGridLayout(double width) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Responsive grid delegate that resolves the column count from the available
|
||||||
|
/// cross-axis extent at the *render* layer (in [getLayout]), mirroring
|
||||||
|
/// [_resolveGridLayout].
|
||||||
|
///
|
||||||
|
/// This replaces a `SliverLayoutBuilder`, whose builder re-runs (and rebuilds
|
||||||
|
/// the entire grid subtree) on every [SliverConstraints] change — i.e. on every
|
||||||
|
/// scroll and every keyboard-inset animation frame — which rebuilt all
|
||||||
|
/// top-site tiles each frame. The delegate recomputes grid metrics on layout
|
||||||
|
/// without rebuilding any widgets.
|
||||||
|
class _TopSitesGridDelegate extends SliverGridDelegate {
|
||||||
|
const _TopSitesGridDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
SliverGridLayout getLayout(SliverConstraints constraints) {
|
||||||
|
final layout = _resolveGridLayout(constraints.crossAxisExtent);
|
||||||
|
final crossAxisCount = layout.crossAxisCount;
|
||||||
|
|
||||||
|
final usableCrossAxisExtent =
|
||||||
|
(constraints.crossAxisExtent -
|
||||||
|
_gridCrossAxisSpacing * (crossAxisCount - 1))
|
||||||
|
.clamp(0.0, double.infinity);
|
||||||
|
final childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
|
||||||
|
final childMainAxisExtent = childCrossAxisExtent / layout.childAspectRatio;
|
||||||
|
|
||||||
|
return SliverGridRegularTileLayout(
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
mainAxisStride: childMainAxisExtent + _gridMainAxisSpacing,
|
||||||
|
crossAxisStride: childCrossAxisExtent + _gridCrossAxisSpacing,
|
||||||
|
childMainAxisExtent: childMainAxisExtent,
|
||||||
|
childCrossAxisExtent: childCrossAxisExtent,
|
||||||
|
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRelayout(_TopSitesGridDelegate oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
class TopSitesSection extends HookConsumerWidget {
|
class TopSitesSection extends HookConsumerWidget {
|
||||||
final void Function(Uri uri) onUriSelected;
|
final void Function(Uri uri) onUriSelected;
|
||||||
|
|
||||||
@@ -156,27 +195,17 @@ class _TopSitesGrid extends ConsumerWidget {
|
|||||||
|
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||||
sliver: SliverLayoutBuilder(
|
sliver: SliverGrid.builder(
|
||||||
builder: (context, constraints) {
|
gridDelegate: const _TopSitesGridDelegate(),
|
||||||
final layout = _resolveGridLayout(constraints.crossAxisExtent);
|
itemCount: displayItems.length,
|
||||||
return SliverGrid.builder(
|
itemBuilder: (context, index) {
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
final item = displayItems[index];
|
||||||
crossAxisCount: layout.crossAxisCount,
|
return _TopSiteGridTile(
|
||||||
mainAxisSpacing: _gridMainAxisSpacing,
|
item: item,
|
||||||
crossAxisSpacing: _gridCrossAxisSpacing,
|
onTap: () => onUriSelected(item.url),
|
||||||
childAspectRatio: layout.childAspectRatio,
|
onPin: () => _pinItem(context, ref, item),
|
||||||
),
|
onEdit: () => _editItem(context, ref, item),
|
||||||
itemCount: displayItems.length,
|
onRemove: () => _removeItem(context, ref, item),
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final item = displayItems[index];
|
|
||||||
return _TopSiteGridTile(
|
|
||||||
item: item,
|
|
||||||
onTap: () => onUriSelected(item.url),
|
|
||||||
onPin: () => _pinItem(context, ref, item),
|
|
||||||
onEdit: () => _editItem(context, ref, item),
|
|
||||||
onRemove: () => _removeItem(context, ref, item),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -84,6 +84,24 @@ class ContainerColors {
|
|||||||
static const double surfaceHighAlpha = 0.28;
|
static const double surfaceHighAlpha = 0.28;
|
||||||
static const double outlineBorderAlpha = 0.5;
|
static const double outlineBorderAlpha = 0.5;
|
||||||
|
|
||||||
|
/// Cache of seed schemes keyed by `(seed color, brightness)`.
|
||||||
|
///
|
||||||
|
/// [ColorScheme.fromSeed] runs the full HCT tonal-palette solver, which is
|
||||||
|
/// very expensive (the `material_color_utilities` `HctSolver`/`DynamicColor`
|
||||||
|
/// math dominated CPU profiles at hundreds of ms). The result is a pure
|
||||||
|
/// function of its seed colour and brightness, so generating it once per
|
||||||
|
/// distinct `(color, brightness)` and reusing it across rebuilds removes the
|
||||||
|
/// cost entirely — previously seed mode computed a *full* scheme up to four
|
||||||
|
/// times per [palette] call, for every chip, on every rebuild.
|
||||||
|
static final Map<(int, Brightness), ColorScheme> _seedSchemeCache = {};
|
||||||
|
|
||||||
|
static ColorScheme _seedScheme(Color seedColor, Brightness brightness) {
|
||||||
|
return _seedSchemeCache.putIfAbsent(
|
||||||
|
(seedColor.toARGB32(), brightness),
|
||||||
|
() => ColorScheme.fromSeed(seedColor: seedColor, brightness: brightness),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static ContainerColorPalette palette(
|
static ContainerColorPalette palette(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Color color, {
|
Color color, {
|
||||||
@@ -93,30 +111,22 @@ class ContainerColors {
|
|||||||
final appScheme = theme.colorScheme;
|
final appScheme = theme.colorScheme;
|
||||||
final fullColor = fullOpacity(color);
|
final fullColor = fullOpacity(color);
|
||||||
|
|
||||||
|
final seedScheme = useCustomColor
|
||||||
|
? null
|
||||||
|
: _seedScheme(fullColor, theme.brightness);
|
||||||
|
|
||||||
final containerColor = useCustomColor
|
final containerColor = useCustomColor
|
||||||
? fullColor
|
? fullColor
|
||||||
: ColorScheme.fromSeed(
|
: seedScheme!.primaryContainer;
|
||||||
seedColor: fullColor,
|
|
||||||
brightness: theme.brightness,
|
|
||||||
).primaryContainer;
|
|
||||||
final accentColor = useCustomColor
|
final accentColor = useCustomColor
|
||||||
? _shiftTone(fullColor, theme.brightness)
|
? _shiftTone(fullColor, theme.brightness)
|
||||||
: ColorScheme.fromSeed(
|
: seedScheme!.primary;
|
||||||
seedColor: fullColor,
|
|
||||||
brightness: theme.brightness,
|
|
||||||
).primary;
|
|
||||||
final onContainerColor = useCustomColor
|
final onContainerColor = useCustomColor
|
||||||
? _contrastingForeground(containerColor)
|
? _contrastingForeground(containerColor)
|
||||||
: ColorScheme.fromSeed(
|
: seedScheme!.onPrimaryContainer;
|
||||||
seedColor: fullColor,
|
|
||||||
brightness: theme.brightness,
|
|
||||||
).onPrimaryContainer;
|
|
||||||
final onAccentColor = useCustomColor
|
final onAccentColor = useCustomColor
|
||||||
? _contrastingForeground(accentColor)
|
? _contrastingForeground(accentColor)
|
||||||
: ColorScheme.fromSeed(
|
: seedScheme!.onPrimary;
|
||||||
seedColor: fullColor,
|
|
||||||
brightness: theme.brightness,
|
|
||||||
).onPrimary;
|
|
||||||
|
|
||||||
final surfaceColor = Color.alphaBlend(
|
final surfaceColor = Color.alphaBlend(
|
||||||
containerColor.withValues(alpha: surfaceAlpha),
|
containerColor.withValues(alpha: surfaceAlpha),
|
||||||
|
|||||||
Reference in New Issue
Block a user