ExoPlayer视频缩放平移优化:无需销毁Activity实现流畅操作
解决方案:采用Matrix实现流畅的视频缩放平移(无需销毁Activity)
核心原理
直接设置scaleX/scaleY和translationX/translationY会触发View的布局重计算,导致操作卡顿。而Matrix变换是在渲染阶段直接对画布进行操作,跳过了冗余的布局流程——这也是你的ZoomableTextureView流畅度更高的核心原因。无需销毁Activity,你可以在现有View上通过Matrix实现同样的流畅效果。
修改后的Kotlin实现代码
替换原有基于scaleX/translationX的逻辑,改用Matrix管理缩放和平移:
// 新增Matrix相关核心变量 private val transformMatrix = Matrix() private val matrixValues = FloatArray(9) private var saveScale = 1f private val MIN_ZOOM = 1f private val MAX_ZOOM = 5f private var mode = Mode.NONE private val lastPoint = PointF() private val startPoint = PointF() private var rightBound = 0f private var bottomBound = 0f override fun onTouch(v: View?, event: MotionEvent?): Boolean { event ?: return true scaleGestureDetector?.onTouchEvent(event) gestureDetector?.onTouchEvent(event) transformMatrix.getValues(matrixValues) val currentX = matrixValues[Matrix.MTRANS_X] val currentY = matrixValues[Matrix.MTRANS_Y] val currPoint = PointF(event.x, event.y) when (event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_DOWN -> { lastPoint.set(event.x, event.y) startPoint.set(lastPoint) mode = Mode.DRAG } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { mode = Mode.NONE } MotionEvent.ACTION_POINTER_DOWN -> { lastPoint.set(event.x, event.y) startPoint.set(lastPoint) mode = Mode.ZOOM } MotionEvent.ACTION_MOVE -> { if (mode == Mode.ZOOM || (mode == Mode.DRAG && saveScale > MIN_ZOOM)) { val deltaX = currPoint.x - lastPoint.x val deltaY = currPoint.y - lastPoint.y // 边界限制:防止视频移出可视区域 val newX = currentX + deltaX val newY = currentY + deltaY val clampedDeltaX = when { newX > 0 -> -currentX newX < -rightBound -> -(currentX + rightBound) else -> deltaX } val clampedDeltaY = when { newY > 0 -> -currentY newY < -bottomBound -> -(currentY + bottomBound) else -> deltaY } transformMatrix.postTranslate(clampedDeltaX, clampedDeltaY) lastPoint.set(currPoint.x, currPoint.y) } } } applyMatrixTransform() return true } @RequiresApi(Build.VERSION_CODES.TIRAMISU) override fun onScale(detector: ScaleGestureDetector): Boolean { val scaleFactor = detector.scaleFactor val origScale = saveScale saveScale *= scaleFactor saveScale = saveScale.coerceIn(MIN_ZOOM, MAX_ZOOM) val actualScaleFactor = saveScale / origScale // 更新可视边界 rightBound = child().width * saveScale - child().width bottomBound = child().height * saveScale - child().height // 以双指中心点为基准缩放 transformMatrix.postScale(actualScaleFactor, actualScaleFactor, detector.focusX, detector.focusY) // 缩放后修正边界,避免视频偏移出屏幕 if (scaleFactor < 1) { transformMatrix.getValues(matrixValues) val x = matrixValues[Matrix.MTRANS_X] val y = matrixValues[Matrix.MTRANS_Y] if (x > 0) transformMatrix.postTranslate(-x, 0) else if (x < -rightBound) transformMatrix.postTranslate(-(x + rightBound), 0) if (y > 0) transformMatrix.postTranslate(0, -y) else if (y < -bottomBound) transformMatrix.postTranslate(0, -(y + bottomBound)) } // 更新缩放比例显示文本 val scaleVal = String.format("%.1f", saveScale) binding.txtScaleVideo.text = "$scaleVal x" // 保留原有BoundingBox绘制逻辑 val height = binding.layoutPlayerView.measuredHeight val width = binding.layoutPlayerView.measuredWidth val drawBB = saveScale == 1.0f binding.layoutPlayerView.removeAllViews() if (drawBB && (seekToPositions.toDouble() == videoStartseekToPositions)) { startBbox?.let { bBoxList -> val drawView = PaintView(requireActivity(), bBoxList, height, width) binding.layoutPlayerView.addView(drawView) } } applyMatrixTransform() return true } override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { mode = Mode.ZOOM return true } override fun onScaleEnd(detector: ScaleGestureDetector) {} private fun applyMatrixTransform() { child().setTransform(transformMatrix) child().invalidate() } // 加载新视频时调用,重置缩放状态 fun resetZoomState() { transformMatrix.reset() saveScale = 1f rightBound = 0f bottomBound = 0f mode = Mode.NONE applyMatrixTransform() binding.txtScaleVideo.text = "1.0 x" } // 保留原有Mode枚举和GestureDetector类 enum class Mode { DRAG, ZOOM, NONE } private inner class GestureDetector : android.view.GestureDetector.SimpleOnGestureListener() { override fun onSingleTapConfirmed(e: MotionEvent): Boolean { isEnable = !isEnable return super.onSingleTapConfirmed(e) } override fun onDoubleTap(event: MotionEvent): Boolean { intLeft = event.x < (sWidth / 2) intRight = !intLeft return super.onDoubleTap(event) } } // 保留原有child()和zoomLayout()方法 private fun child(): View { return zoomLayout(0) } private fun zoomLayout(i: Int): View { return binding.videoView }
关键优化点说明
- Matrix替代scale/translation属性:使用
setTransform(Matrix)直接控制View的渲染矩阵,避免布局重计算,大幅提升操作流畅度。 - 边界限制逻辑优化:在平移和缩放时实时计算边界,确保视频始终在可视区域内。
- 新视频加载处理:加载新视频前调用
resetZoomState()即可重置缩放状态,无需销毁Activity——只需释放旧播放器资源并初始化新播放器即可。 - 缩放基准点优化:以双指触摸的中心点为缩放中心,符合用户直观操作习惯。
注意事项
- 确保你的
videoView是TextureView:系统TextureView原生支持setTransform方法,若使用自定义View需重写该方法并将Matrix应用到画布。 - 新视频加载流程:重置Matrix状态 → 释放旧播放器 → 初始化新播放器并绑定到
videoView,全程无需销毁Activity或View。
内容的提问来源于stack exchange,提问作者Usama Wajid
相关产品推荐
相关产品推荐

