You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android SurfaceView视频缩放平移旋转及背景色处理技术问题

视频编辑功能迁移至SurfaceView的旋转问题

我正在开发一个视频编辑上传功能:从相册选择视频,编辑后上传至后端服务器。目前遇到以下瓶颈:

  • 项目依赖第三方SDK,必须使用SurfaceView实例处理视频/图像编辑
  • 需要实现类似Instagram的捏合缩放、全屏拖动视频至任意位置的功能
  • 可为视频帧外部区域设置背景色
  • 最终需导出包含所有上述编辑效果的视频

我已基于TextureView实现了所有功能,但切换到SurfaceView后无法正常工作。以下是TextureView的可运行代码及布局文件:

class VideoEditActivity : ComponentActivity() {
    private lateinit var textureView: TextureView
    private lateinit var mediaPlayer: MediaPlayer
    private lateinit var videoContainer: FrameLayout
    private var scaleGestureDetector: ScaleGestureDetector? = null
    private var gestureDetector: GestureDetector? = null
    private var scaleFactor = 1.0f
    private var translationX = 0f
    private var translationY = 0f
    private var rotationDegrees = 0f
    private var initialRotationDegrees = 0f
    private lateinit var selectedFilePath: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video_edit)

        textureView = findViewById(R.id.textureView)
        videoContainer = findViewById(R.id.videoContainer)
        videoContainer.setBackgroundResource(R.color.purple_200)
        selectedFilePath = getPathFromUri(Uri.parse(intent.getStringExtra(INTENT_DATA)))
        Logger.log("Selected file path: $selectedFilePath")
        findViewById<ImageView>(R.id.ivVideoPlay).setOnClickListener {
//            captureAndCreateVideo()
        }
        setupMediaPlayer()
        setupGestures()
    }

    private fun setupMediaPlayer() {
        textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
                val selectedMediaUri = Uri.parse(intent.getStringExtra(INTENT_DATA))
                selectedMediaUri?.let {
                    mediaPlayer = MediaPlayer.create(this@VideoEditActivity, it) // Adjust the source accordingly
                }
                mediaPlayer.setSurface(Surface(surface))
                mediaPlayer.isLooping = true
                mediaPlayer.start()
            }

            override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                mediaPlayer.release()
                return true
            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
        }
    }

    private fun setupGestures() {
        scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
            override fun onScale(detector: ScaleGestureDetector): Boolean {
                scaleFactor *= detector.scaleFactor
                scaleFactor = max(0.1f, min(scaleFactor, 5.0f)) // Limit scaling
                textureView.scaleX = scaleFactor
                textureView.scaleY = scaleFactor
                return true
            }
        })

        gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
            override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
                translationX -= distanceX
                translationY -= distanceY
                textureView.translationX = translationX
                textureView.translationY = translationY
                return true
            }
        })

        videoContainer.setOnTouchListener { _, event ->
            if (event.pointerCount == 2) {
                if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
                    initialRotationDegrees = rotation(event)
                } else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
                    val currentRotationDegrees = rotation(event)
                    val deltaDegrees = initialRotationDegrees - currentRotationDegrees
                    rotationDegrees -= deltaDegrees
                    textureView.rotation = rotationDegrees
                    initialRotationDegrees = currentRotationDegrees
                }
            }
            scaleGestureDetector?.onTouchEvent(event)
            gestureDetector?.onTouchEvent(event)
            true
        }
    }

    private fun rotation(event: MotionEvent): Float {
        val dx = (event.getX(0) - event.getX(1)).toDouble()
        val dy = (event.getY(0) - event.getY(1)).toDouble()
        return Math.toDegrees(atan2(dy, dx)).toFloat()
    }


    private fun getPathFromUri(uri: Uri): String {
        var filePath: String? = null
        val projection = arrayOf(MediaStore.Video.Media.DATA)
        val cursor = contentResolver.query(uri, projection, null, null, null)
        if (cursor != null && cursor.moveToFirst()) {
            val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
            filePath = cursor.getString(columnIndex)
            cursor.close()
        }
        return filePath ?: ""
    }
}

XML布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/ivSelectedImage"
        android:src="@drawable/app_logo"
        android:layout_centerInParent="true"
        android:foregroundGravity="center"
        android:visibility="gone"
        android:contentDescription="selected_image" />


    <FrameLayout
        android:id="@+id/videoContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"> <!-- Background color -->

        <TextureView
            android:id="@+id/textureView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/play_icon"
        android:layout_centerInParent="true"
        android:visibility="gone"
        android:id="@+id/ivVideoPlay"/>

</RelativeLayout>

更新:我已实现SurfaceView的捏合缩放功能,但旋转效果不符合预期——仅容器旋转,SurfaceView内的视频内容未同步旋转。当前SurfaceView手势实现代码如下:

private fun setupSurfaceGestures() {
    scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            scaleFactor *= detector.scaleFactor
            scaleFactor = max(0.1f, min(scaleFactor, 5.0f)) // Limit scaling
            // Update the SurfaceView scale here if possible or handle via custom drawing
            surfaceView.scaleX = scaleFactor
            surfaceView.scaleY = scaleFactor
            return true
        }
    })

    gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
        override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
            translationX -= distanceX
            translationY -= distanceY
            // Update the SurfaceView translation here if possible or handle via custom drawing
            surfaceView.translationX = translationX
            surfaceView.translationY = translationY
            return true
        }
    })

    videoContainer.setOnTouchListener { _, event ->
        if (event.pointerCount == 2) {
            if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
                initialRotationDegrees = rotation(event)
            } else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
                val currentRotationDegrees = rotation(event)
                val deltaDegrees = initialRotationDegrees - currentRotationDegrees
                rotationDegrees -= deltaDegrees
                surfaceView.rotation = rotationDegrees
                initialRotationDegrees = currentRotationDegrees
            }
        }
        scaleGestureDetector?.onTouchEvent(event)
        gestureDetector?.onTouchEvent(event)
        true
    }
}

内容的提问来源于stack exchange,提问作者CoderDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:59:50