opt: RepaintBoundary (#1840)

* opt: RepaintBoundary

* fix [skip ci]

* opt time width

* opt: video position

* update

---------

Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
My-Responsitories
2026-02-20 21:04:34 +08:00
committed by GitHub
parent a63ca93762
commit d7d9655f81
12 changed files with 226 additions and 128 deletions

View File

@@ -65,7 +65,4 @@ class RenderCustomHeightWidget extends RenderProxyBox {
void paint(PaintingContext context, Offset offset) {
context.paintChild(child!, offset + _offset);
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -95,7 +95,4 @@ class _RenderDecoratedBox extends RenderProxyBox {
@override
bool hitTestSelf(Offset position) => true;
@override
bool get isRepaintBoundary => true;
}

View File

@@ -259,9 +259,6 @@ class _RenderToolTip extends RenderBox
child = childParentData.nextSibling;
}
}
@override
bool get isRepaintBoundary => true;
}
class Triangle extends LeafRenderObjectWidget {
@@ -328,14 +325,11 @@ class RenderTriangle extends RenderBox {
..style = PaintingStyle.fill;
final path = Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width / 2, size.height)
..moveTo(offset.dx, offset.dy)
..lineTo(offset.dx + size.width, offset.dy)
..lineTo(offset.dx + size.width / 2, size.height + offset.dy)
..close();
context.canvas.drawPath(path, paint);
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -121,6 +121,7 @@ class RenderMaskedIcon extends RenderProxyBox {
final canvas = context.canvas;
var rectOffset = offset;
Size size = this.size;
final exceedWidth = size.width > _iconSize;
final exceedHeight = size.height > _iconSize;
@@ -128,14 +129,14 @@ class RenderMaskedIcon extends RenderProxyBox {
final dx = exceedWidth ? (size.width - _iconSize) / 2.0 : 0.0;
final dy = exceedHeight ? (size.height - _iconSize) / 2.0 : 0.0;
size = Size.square(_iconSize);
offset = Offset(dx, dy);
rectOffset += Offset(dx, dy);
} else if (size.width < _iconSize && size.height < _iconSize) {
size = Size.square(_iconSize);
}
final strokeWidth = size.width / 12;
var rect = offset & size;
var rect = rectOffset & size;
final sqrt2Width = strokeWidth * sqrt2; // rotate pi / 4
@@ -155,7 +156,7 @@ class RenderMaskedIcon extends RenderProxyBox {
canvas
..save()
..clipPath(path, doAntiAlias: false);
super.paint(context, .zero);
super.paint(context, offset);
canvas.restore();
@@ -174,7 +175,4 @@ class RenderMaskedIcon extends RenderProxyBox {
linePaint,
);
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -440,5 +440,5 @@ class RenderImageGrid extends RenderBox
}
@override
bool get isRepaintBoundary => true;
bool get isRepaintBoundary => true; // gif repaint
}

View File

@@ -226,13 +226,14 @@ abstract class MarqueeRender extends RenderBox
if (_distance > 0) {
updateSize();
_ticker.initIfNeeded(_onTick);
markNeedsCompositingBitsUpdate();
} else {
_ticker.cancel();
}
}
@override
bool get isRepaintBoundary => true;
bool get isRepaintBoundary => _ticker._ticker != null;
void paintCenter(PaintingContext context, Offset offset) {
if (_direction == Axis.horizontal) {

View File

@@ -123,17 +123,17 @@ class RenderProgressBar extends BaseRenderProgressBar<Segment> {
for (final segment in segments) {
paint.color = segment.color;
final segmentStart = segment.start * size.width;
final segmentEnd = segment.end * size.width;
final segmentStart = offset.dx + segment.start * size.width;
final segmentEnd = offset.dx + segment.end * size.width;
if (segmentEnd > segmentStart ||
(segmentEnd == segmentStart && segmentStart > 0)) {
canvas.drawRect(
Rect.fromLTRB(
segmentStart,
0,
offset.dy,
segmentEnd == segmentStart ? segmentStart + 2 : segmentEnd,
size.height,
size.height + offset.dy,
),
paint,
);
@@ -225,6 +225,12 @@ class RenderViewPointProgressBar
final canvas = context.canvas;
final paint = Paint()..style = PaintingStyle.fill;
if (offset != .zero) {
canvas
..save()
..translate(offset.dx, offset.dy);
}
assert(segments.isSortedBy((i) => i.end));
canvas.drawRect(
@@ -276,6 +282,7 @@ class RenderViewPointProgressBar
}
prevEnd = segmentEnd + _dividerWidth;
}
if (offset != .zero) canvas.restore();
}
ValueSetter<Duration>? _onSeek;
@@ -371,7 +378,4 @@ class BaseRenderProgressBar<T extends BaseSegment> extends RenderBox {
void performLayout() {
size = constraints.constrainDimensions(constraints.maxWidth, height);
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -119,7 +119,9 @@ class RenderProgressBar extends RenderBox {
@override
void paint(PaintingContext context, Offset offset) {
final size = this.size;
final canvas = context.canvas;
final canvas = context.canvas
..save()
..translate(offset.dx, offset.dy);
final paint = Paint()..style = .fill;
canvas.clipRect(
@@ -147,8 +149,6 @@ class RenderProgressBar extends RenderBox {
..drawRect(left, paint..color = _color)
..drawRect(right, paint..color = _backgroundColor);
}
canvas.restore();
}
@override
bool get isRepaintBoundary => true;
}

View File

@@ -997,12 +997,15 @@ class _RenderBorderIndicator extends RenderBox {
final size = this.size;
final canvas = context.canvas;
final width = size.width / 2;
if (!_isLeft) {
canvas.translate(width, 0);
}
BoxBorder.paintNonUniformBorder(
canvas,
Rect.fromLTRB(0, 0, width, size.height),
Rect.fromLTWH(
offset.dx + (_isLeft ? 0 : width),
offset.dy,
width,
size.height,
),
borderRadius: BorderRadius.only(
topLeft: _isLeft ? _radius : .zero,
topRight: _isLeft ? .zero : _radius,
@@ -1012,9 +1015,6 @@ class _RenderBorderIndicator extends RenderBox {
color: Colors.white38,
);
}
@override
bool get isRepaintBoundary => true;
}
class LiveDanmaku extends StatefulWidget {

View File

@@ -66,22 +66,25 @@ class ActionItem extends StatelessWidget {
child = SizedBox.square(dimension: 28, child: child);
}
child = InkWell(
borderRadius: const BorderRadius.all(Radius.circular(6)),
onTap: _isThumbsUp ? null : onTap,
onLongPress: _isThumbsUp ? null : onLongPress,
onSecondaryTap: PlatformUtils.isMobile || _isThumbsUp
? null
: onLongPress,
onTapDown: _isThumbsUp ? (_) => onStartTriple!() : null,
onTapUp: _isThumbsUp ? (_) => onCancelTriple!(true) : null,
onTapCancel: _isThumbsUp ? onCancelTriple : null,
child: expand
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [child, _buildText(theme)],
)
: child,
child = Material(
type: .transparency,
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(6)),
onTap: _isThumbsUp ? null : onTap,
onLongPress: _isThumbsUp ? null : onLongPress,
onSecondaryTap: PlatformUtils.isMobile || _isThumbsUp
? null
: onLongPress,
onTapDown: _isThumbsUp ? (_) => onStartTriple!() : null,
onTapUp: _isThumbsUp ? (_) => onCancelTriple!(true) : null,
onTapCancel: _isThumbsUp ? onCancelTriple : null,
child: expand
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [child, _buildText(theme)],
)
: child,
),
);
return expand ? Expanded(child: child) : child;
}

View File

@@ -66,7 +66,8 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show RenderProxyBox;
import 'package:flutter/rendering.dart'
show RenderProxyBox, SemanticsConfiguration;
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -379,38 +380,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
),
/// 时间进度
BottomControlType.time => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// 播放时间
Obx(
() => Text(
DurationUtils.formatDuration(
plPlayerController.positionSeconds.value,
),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
height: 1.4,
fontFeatures: [FontFeature.tabularFigures()],
),
),
BottomControlType.time => Obx(
() => _VideoTime(
position: DurationUtils.formatDuration(
plPlayerController.positionSeconds.value,
),
Obx(
() => Text(
DurationUtils.formatDuration(
plPlayerController.duration.value.inSeconds,
),
style: const TextStyle(
color: Color(0xFFD0D0D0),
fontSize: 10,
height: 1.4,
fontFeatures: [FontFeature.tabularFigures()],
),
),
duration: DurationUtils.formatDuration(
plPlayerController.duration.value.inSeconds,
),
],
),
),
/// 高能进度条
@@ -1617,39 +1595,41 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
top: -1,
bottom: -1,
child: ClipRect(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppBarAni(
isTop: true,
controller: animationController,
isFullScreen: isFullScreen,
child: plPlayerController.isDesktopPip
? GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (_) => windowManager.startDragging(),
child: widget.headerControl,
)
: widget.headerControl,
),
AppBarAni(
isTop: false,
controller: animationController,
isFullScreen: isFullScreen,
child:
widget.bottomControl ??
BottomControl(
maxWidth: maxWidth,
isFullScreen: isFullScreen,
controller: plPlayerController,
videoDetailController: videoDetailController,
buildBottomControl: () => buildBottomControl(
videoDetailController,
maxWidth > maxHeight,
child: RepaintBoundary(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppBarAni(
isTop: true,
controller: animationController,
isFullScreen: isFullScreen,
child: plPlayerController.isDesktopPip
? GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (_) => windowManager.startDragging(),
child: widget.headerControl,
)
: widget.headerControl,
),
AppBarAni(
isTop: false,
controller: animationController,
isFullScreen: isFullScreen,
child:
widget.bottomControl ??
BottomControl(
maxWidth: maxWidth,
isFullScreen: isFullScreen,
controller: plPlayerController,
videoDetailController: videoDetailController,
buildBottomControl: () => buildBottomControl(
videoDetailController,
maxWidth > maxHeight,
),
),
),
),
],
),
],
),
),
),
),
@@ -2767,12 +2747,7 @@ class _RenderDanmakuTip extends RenderProxyBox {
void paint(PaintingContext context, Offset offset) {
final paint = Paint()
..color = const Color(0xB3000000)
..style = PaintingStyle.fill;
final strokePaint = Paint()
..color = const Color(0x7EFFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 1.25;
..style = .fill;
final radius = size.height / 2;
const triangleBase = _triangleHeight * 2 / 3;
@@ -2803,11 +2778,140 @@ class _RenderDanmakuTip extends RenderProxyBox {
..close();
context.canvas
..save()
..translate(offset.dx, offset.dy)
..drawPath(path, paint)
..drawPath(path, strokePaint);
..drawPath(
path,
paint
..color = const Color(0x7EFFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 1.25,
)
..restore();
super.paint(context, offset);
}
}
class _VideoTime extends LeafRenderObjectWidget {
const _VideoTime({
required this.position,
required this.duration,
});
final String position;
final String duration;
@override
_RenderVideoTime createRenderObject(BuildContext context) => _RenderVideoTime(
position: position,
duration: duration,
);
@override
void updateRenderObject(
BuildContext context,
covariant _RenderVideoTime renderObject,
) {
renderObject
..position = position
..duration = duration;
}
}
class _RenderVideoTime extends RenderBox {
_RenderVideoTime({
required String position,
required String duration,
}) : _position = position,
_duration = duration;
String _duration;
set duration(String value) {
_duration = value;
final paragraph = _buildParagraph(const Color(0xFFD0D0D0), _duration);
if (paragraph.maxIntrinsicWidth != _cache?.maxIntrinsicWidth) {
markNeedsLayout();
}
_cache?.dispose();
_cache = paragraph;
markNeedsSemanticsUpdate();
}
String _position;
set position(String value) {
_position = value;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
ui.Paragraph? _cache;
ui.Paragraph _buildParagraph(Color color, String time) {
final builder =
ui.ParagraphBuilder(
ui.ParagraphStyle(
fontSize: 10,
height: 1.4,
fontFamily: 'Monospace',
),
)
..pushStyle(
ui.TextStyle(
color: color,
fontSize: 10,
height: 1.4,
fontFamily: 'Monospace',
fontFeatures: const [FontFeature.tabularFigures()],
),
)
..addText(time);
return builder.build()
..layout(ui.ParagraphConstraints(width: constraints.maxWidth));
}
@override
ui.Size computeDryLayout(covariant BoxConstraints constraints) {
final paragraph = _cache ??= _buildParagraph(
const Color(0xFFD0D0D0),
_duration,
);
return Size(paragraph.maxIntrinsicWidth, paragraph.height * 2);
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.label = 'position:$_position\nduration:$_duration';
}
@override
void performLayout() {
size = computeDryLayout(constraints);
}
@override
void paint(PaintingContext context, ui.Offset offset) {
final para = _buildParagraph(Colors.white, _position);
context.canvas
..drawParagraph(
para,
Offset(
offset.dx + _cache!.maxIntrinsicWidth - para.maxIntrinsicWidth,
offset.dy,
),
)
..drawParagraph(_cache!, Offset(offset.dx, offset.dy + para.height));
para.dispose();
}
@override
void dispose() {
_cache?.dispose();
_cache = null;
super.dispose();
}
@override
bool get isRepaintBoundary => true;

View File

@@ -105,7 +105,7 @@ class MpvConvertWebp {
final text = log.text.toDartString().trim();
debugPrint('WebpConvert: $level $prefix : $text');
if (kDebugMode) {
_success = level != 'error' && level != 'fatal';
if (level == 'error' || level == 'fatal') _success = false;
} else {
_success = false;
}