如何在VideoView上实现圆形GLSurfaceView?
我之前遇到过类似的场景,这里帮你分析问题根源并给出几个可行的解决方案:
问题本质
你当前用dispatchDraw+clipPath的方式,其实对GLSurfaceView完全不起作用——因为GLSurfaceView的渲染逻辑和普通View天差地别:它拥有独立的Surface和OpenGL渲染线程,所有GL绘制内容都是直接输出到这个独立Surface上的,和View体系的Canvas没有关联。之前叠加在ImageView上看起来是圆形,大概率是视觉巧合或者系统临时合成表现,并不是真正裁剪了GLSurfaceView的内容。而VideoView本身也是SurfaceView子类,两个Surface叠加时,系统不会再应用View层的Canvas裁剪,所以方形就露出来了。
解决方案
方案1:在OpenGL渲染层面做圆形裁剪(最可靠)
直接在GLRenderer的绘制逻辑里加入圆形裁剪,这是从根源解决问题的方式,因为我们直接控制了GL画面的输出。可以用**模板测试(Stencil Test)**实现:
- 先绘制一个圆形到模板缓冲
- 设置只渲染模板缓冲标记为1的区域
- 再绘制摄像头画面
- 最后关闭模板测试
示例代码修改你的Renderer:
public class CameraGLRenderer implements GLSurfaceView.Renderer { // 省略其他成员变量和初始化逻辑... @Override public void onDrawFrame(GL10 gl) { // 启用模板测试 gl.glEnable(GL10.GL_STENCIL_TEST); // 清除模板缓冲 gl.glClear(GL10.GL_STENCIL_BUFFER_BIT); // 设置模板规则:绘制时将模板值设为1 gl.glStencilFunc(GL10.GL_ALWAYS, 1, 0xFF); gl.glStencilOp(GL10.GL_REPLACE, GL10.GL_REPLACE, GL10.GL_REPLACE); // 绘制圆形模板(用三角形扇形绘制覆盖整个视图的圆) drawCircleMask(gl); // 更新模板规则:只绘制模板值等于1的区域 gl.glStencilFunc(GL10.GL_EQUAL, 1, 0xFF); gl.glStencilOp(GL10.GL_KEEP, GL10.GL_KEEP, GL10.GL_KEEP); // 绘制你的摄像头画面逻辑 drawCameraFrame(gl); // 关闭模板测试 gl.glDisable(GL10.GL_STENCIL_TEST); } private void drawCircleMask(GL10 gl) { gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // 颜色不影响模板,随便设置即可 gl.glBegin(GL10.GL_TRIANGLE_FAN); gl.glVertex2f(0, 0); // 圆心(归一化坐标,对应视图中心) int segments = 36; // 分段数越多圆越平滑 for (int i = 0; i <= segments; i++) { float angle = (float) (i * 2 * Math.PI / segments); float x = (float) Math.cos(angle); float y = (float) Math.sin(angle); gl.glVertex2f(x, y); } gl.glEnd(); } // 省略drawCameraFrame等其他方法... }
方案2:使用ViewOutlineProvider(Android 5.0+ 简单易用)
如果你的最低兼容版本是Android 5.0(API 21)及以上,可以用系统提供的ViewOutlineProvider实现裁剪,代码非常简洁:
修改你的SampleGLView的onSizeChanged方法:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int radius = Math.min(w, h) / 2; // 设置OutlineProvider setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { // 设置圆形轮廓,用setRoundRect实现(宽高相等时就是圆形) outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius); } }); // 开启裁剪到轮廓 setClipToOutline(true); }
注意:部分旧机型可能对SurfaceView子类的ClipToOutline支持不佳,建议测试后使用。
方案3:添加遮罩层(兼容性最好)
如果以上两种方法都不适用,最简单的兼容方案是在GLSurfaceView上方叠加一个遮罩View,挡住方形区域只露出圆形:
- 创建圆形遮罩的Drawable(
res/drawable/circle_mask.xml):
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <!-- 周围区域的颜色,用半透明黑色模拟遮罩 --> <solid android:color="#CC000000" /> <item> <!-- 中间透明的圆形 --> <shape android:shape="oval"> <solid android:color="@android:color/transparent" /> </shape> </item> </shape>
- 修改布局,在GLSurfaceView上方加一个相同大小的遮罩View:
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.yourpackage.SampleGLView android:id="@+id/glView" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center" /> <!-- 遮罩层 --> <View android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center" android:background="@drawable/circle_mask" /> </FrameLayout>
这个方法不需要修改任何GL或View代码,兼容性覆盖所有Android版本,缺点是遮罩层会挡住GLSurfaceView的触摸事件,你可以在遮罩View里重写onTouchEvent,返回false或者直接转发给GLSurfaceView。
内容的提问来源于stack exchange,提问作者jeel raja

