Player_Flutter icon indicating copy to clipboard operation
Player_Flutter copied to clipboard

TXPlayerVideo 中双指放大5-7倍,播放器会卡,画面会糊,甚至停止播放。

Open Downey-Tang opened this issue 5 months ago • 3 comments

flutter 中双指放大5-7倍,播放器会卡,甚至停止播放,画面会糊。

使用flutter 推荐的包 video_player 就很流畅,不会卡。

Downey-Tang avatar Jun 24 '25 07:06 Downey-Tang

@Downey-Tang 辛苦帮忙确认下,放大是什么什么组件和逻辑放大的,我们看看能否优化

Kongdy avatar Jul 11 '25 06:07 Kongdy

import 'dart:math';
import 'package:flutter/material.dart';

class ZoomableWidget extends StatefulWidget {
  final Widget child;
  final double minScale;
  final double maxScale;

  const ZoomableWidget({
    super.key,
    required this.child,
    this.minScale = 0.2,
    this.maxScale = 7.0,
  });

  @override
  State createState() => _ZoomableWidgetState();
}

class _ZoomableWidgetState extends State with TickerProviderStateMixin {
  double _scale = 1.0;
  Offset _translate = Offset.zero;
  final Map _pointers = {};
  Size _containerSize = Size.zero;
  Offset? _previousCenter;
  double? _previousDistance;

  late AnimationController _animationController;
  late Animation _scaleAnimation;
  late Animation _translateAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  void _handleZoom(List points) {
    final currentCenter = (points[0] + points[1]) / 2;
    final currentDistance = (points[0] - points[1]).distance;

    if (_previousCenter != null && _previousDistance != null) {
      final scaleFactor = (currentDistance / _previousDistance!) - 1.0;
      final limitedScaleFactor = scaleFactor.clamp(-0.5, 0.5);
      final targetScale = _scale * (1 + limitedScaleFactor);
      final newScale = targetScale.clamp(widget.minScale, widget.maxScale);

      final localPoint = (currentCenter -
              Offset(
                _containerSize.width / 2,
                _containerSize.height / 2,
              )) /
          _scale;

      final matrix = Matrix4.identity()
        ..translate(localPoint.dx, localPoint.dy)
        ..scale(newScale / _scale)
        ..translate(-localPoint.dx, -localPoint.dy);

      final updatedTransform = matrix
        ..multiply(Matrix4.identity()
          ..translate(_translate.dx, _translate.dy)
          ..scale(_scale));

      setState(() {
        _scale = newScale;
        _translate = Offset(
          updatedTransform.getTranslation().x,
          updatedTransform.getTranslation().y,
        );
        _setOffsetLimits();
      });
    }

    _previousCenter = currentCenter;
    _previousDistance = currentDistance;
  }

  void _setOffsetLimits() {
    final size = _containerSize;
    final double maxX = max(0, size.width * (_scale - 1) / 2);
    final double maxY = max(0, size.height * (_scale - 1) / 2);

    final dx = _translate.dx.clamp(-maxX, maxX);
    final dy = _translate.dy.clamp(-maxY, maxY);
    _translate = Offset(dx, dy);
  }

  void _resetZoomState() {
    _previousCenter = null;
    _previousDistance = null;
    if (_scale != 1.0) {
      _animateReset();
    } else {
      setState(() {
        _scale = 1.0;
        _translate = Offset.zero;
      });
    }
  }

  void _animateReset() {
    _animationController.reset();
    double curScale = _scale;
    Offset curTranslate = _translate;

    _scaleAnimation = Tween(
      begin: curScale,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeInOut,
    ));

    _translateAnimation = Tween(
      begin: curTranslate,
      end: Offset.zero,
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeInOut,
    ));

    void updateState() {
      if (mounted) {
        setState(() {
          _scale = _scaleAnimation.value;
          _translate = _translateAnimation.value;
        });
      }
    }

    _animationController.addListener(updateState);
    _animationController.forward().whenCompleteOrCancel(() {
      _animationController.removeListener(updateState);
      setState(() {
        _scale = 1.0;
        _translate = Offset.zero;
      });
    });
  }

  Widget _buildScaleWidget() {
    return Listener(
      onPointerDown: (event) {
        _pointers[event.pointer] = event.position;
      },
      onPointerMove: (event) {
        if (_pointers.containsKey(event.pointer)) {
          _pointers[event.pointer] = event.position;
          if (_pointers.length == 2) {
            _handleZoom(_pointers.values.toList());
          }
        }
      },
      onPointerUp: (event) {
        _pointers.remove(event.pointer);
        if (_pointers.isEmpty) {
          _resetZoomState();
        }
        setState(() {});
      },
      onPointerCancel: (event) {
        _pointers.remove(event.pointer);
        if (_pointers.isEmpty) {
          _resetZoomState();
        }
        setState(() {});
      },
      child: RepaintBoundary(
        child: Transform(
          alignment: Alignment.center,
          transform: Matrix4.identity()
            ..translate(_translate.dx, _translate.dy)
            ..scale(_scale),
          child: widget.child,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        _containerSize = Size(constraints.maxWidth, constraints.maxHeight);
        return _buildScaleWidget();
      },
    );
  }
}

使用:

ZoomableWidget(
    child: Image.asset('assets/your_image.png'),
)

上面是去除业务逻辑的缩放代码实现; 包装 TXPlayerVideo 和 flutter 推荐的包 video_player,设置循环播放 ,对比 双指放大效果(不要松手),

video_player 很丝滑 , TXPlayerVideo 会糊,会卡,会停止播放

Downey-Tang avatar Jul 14 '25 02:07 Downey-Tang

@Downey-Tang 感谢提供复现代码,这里是系统或者 flutter 对 TextureView 做了限制,放大后,view 超出屏幕的不可见部分到一定程度,纹理的更新就会停掉,来优化性能,这里可以将 renderType 切为 SurfaceView 试试

Kongdy avatar Nov 04 '25 08:11 Kongdy