我是Android应用程序开发的新手。我有一个带有GridLayoutManager的RecyclerView,每个项目都是一个带有边框和圆角的单元格。单元内有一个VideoView和带有摄像机名称的标签。我的问题是在圆形单元格内“剪切” VideoView。 这是我所拥有的一些屏幕截图:
这是我的单元格的XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="@android:color/transparent"
android:backgroundTint="@android:color/transparent"
android:clipChildren="true"
android:orientation="vertical">
<android.support.constraint.ConstraintLayout
android:id="@+id/videoCellView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:background="@drawable/border_rounded_corners"
android:clipChildren="true">
<TextView
android:id="@+id/videoName"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_gravity="center|start"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:text="Camera"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="5dp"
android:paddingStart="1dp"
android:paddingTop="1dp"
android:paddingEnd="1dp"
app:layout_constraintBottom_toTopOf="@+id/videoName" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
谢谢!
答案 0 :(得分:0)
我通过创建自定义VideoSurfaceView找到了该解决方案
public class VideoSurfaceView extends GLSurfaceView {
private static final String TAG = "VideoSurfaceView";
private static final boolean USE_MULTI_SAMPLING = true;
VideoRenderer mRenderer;
MediaPlayer mMediaPlayer = null;
MultiSampleEGLConfigChooser mMultiSamplingConfigChooser;
public VideoSurfaceView(Context context) {
super(context);
init(new VideoRenderer(this));
}
public VideoSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(new VideoRenderer(this));
}
VideoSurfaceView(Context context, @NonNull VideoRenderer videoRender) {
super(context);
init(videoRender);
}
private void init(@NonNull VideoRenderer videoRender) {
setEGLContextClientVersion(2);
setupEGLConfig(true, USE_MULTI_SAMPLING);
if (USE_MULTI_SAMPLING && mMultiSamplingConfigChooser != null) {
videoRender.setUsesCoverageAa(mMultiSamplingConfigChooser.usesCoverageAa());
}
mRenderer = videoRender;
setRenderer(mRenderer);
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
/**
* Make sure the {@link android.view.SurfaceHolder} pixel format matches your EGL configuration.
*
* @param translucent true if the view should show views below if parts of the view area are
* transparent. Has performance implications.
* @param multisampling true if the GL Surface should perform multi-sampling. This avoids hard
* edges on the geometry. Has performance implications.
*/
private void setupEGLConfig(boolean translucent, boolean multisampling) {
if (translucent) {
setZOrderOnTop(true);
if (multisampling) {
mMultiSamplingConfigChooser = new MultiSampleEGLConfigChooser();
setEGLConfigChooser(mMultiSamplingConfigChooser);
} else {
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
}
this.getHolder().setFormat(PixelFormat.RGBA_8888);
} else {
if (multisampling) {
mMultiSamplingConfigChooser = new MultiSampleEGLConfigChooser();
setEGLConfigChooser(mMultiSamplingConfigChooser);
} else {
setEGLConfigChooser(5, 6, 5, 0, 16, 0);
}
this.getHolder().setFormat(PixelFormat.RGB_565);
}
}
public void setCornerRadius(float radius) {
setCornerRadius(radius, radius, radius, radius);
}
public void setCornerRadius(float topLeft, float topRight, float bottomRight,
float bottomLeft) {
mRenderer.setCornerRadius(topLeft, topRight, bottomRight, bottomLeft);
}
// TODO
public void setVideoAspectRatio(float aspectRatio) {
mRenderer.setVideoAspectRatio(aspectRatio);
}
@Override
public void onResume() {
queueEvent(new Runnable(){
public void run() {
mRenderer.setMediaPlayer(mMediaPlayer);
}});
super.onResume();
}
public void setMediaPlayer(@Nullable MediaPlayer mediaPlayer) {
mMediaPlayer = mediaPlayer;
if (mRenderer != null) {
mRenderer.setMediaPlayer(mediaPlayer);
}
}
private static class VideoRenderer
implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
private static String TAG = "VideoRender";
private static final int FLOAT_SIZE_BYTES = 4;
private static final int SHORT_SIZE_BYTES = 2;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
private final boolean USE_DRAW_ELEMENTS = true;
private final String mVertexShader =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
private final String mFragmentShader =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
private float[] mMVPMatrix = new float[16];
private float[] mSTMatrix = new float[16];
private int mProgram;
private int mTextureID;
private int muMVPMatrixHandle;
private int muSTMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
private final GLSurfaceView mGLSurfaceView;
private MediaPlayer mMediaPlayer;
private SurfaceTexture mSurfaceTexture;
private boolean mUpdateSurface = false;
private float[] mTriangleVerticesData;
private short[] mTriangleIndicesData;
private FloatBuffer mTriangleVertices;
private ShortBuffer mTriangleIndices;
private RectF mRoundRadius = new RectF();
private GLRoundedGeometry mRoundedGeometry;
private final Point mViewPortSize = new Point();
private final RectF mViewPortGLBounds;
private boolean mUsesCoverageAa = false;
public VideoRenderer(@NonNull GLSurfaceView view) {
this(view, new GLRoundedGeometry(), new RectF(-1, 1, 1, -1));
}
public VideoRenderer(@NonNull GLSurfaceView view,
@NonNull GLRoundedGeometry roundedGeometry,
@NonNull RectF viewPortGLBounds) {
mGLSurfaceView = view;
mRoundedGeometry = roundedGeometry;
mViewPortGLBounds = viewPortGLBounds;
mViewPortSize.set(1, 1); // init this with a non-zero size
Matrix.setIdentityM(mSTMatrix, 0);
}
public void setUsesCoverageAa(boolean usesCoverageAa) {
mUsesCoverageAa = usesCoverageAa;
}
public void setCornerRadius(float topLeft, float topRight, float bottomRight,
float bottomLeft) {
mRoundRadius.left = topLeft;
mRoundRadius.top = topRight;
mRoundRadius.right = bottomRight;
mRoundRadius.bottom = bottomLeft;
if (mViewPortSize.x > 1) {
updateVertexData();
}
}
private void updateVertexData() {
final GLRoundedGeometry.GeometryArrays arrays =
mRoundedGeometry.generateVertexData(
mRoundRadius,
mViewPortGLBounds,
mViewPortSize);
mTriangleVerticesData = arrays.triangleVertices;
mTriangleIndicesData = arrays.triangleIndices;
if (mTriangleVertices != null) {
mTriangleVertices.clear();
} else {
mTriangleVertices = ByteBuffer.allocateDirect(
mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
}
if (mTriangleIndices != null) {
mTriangleIndices.clear();
} else {
mTriangleIndices = ByteBuffer.allocateDirect(
mTriangleIndicesData.length * SHORT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asShortBuffer();
}
mTriangleVertices.put(mTriangleVerticesData).position(0);
mTriangleIndices.put(mTriangleIndicesData).position(0);
}
public void setMediaPlayer(MediaPlayer player) {
mMediaPlayer = player;
if (mSurfaceTexture != null) {
Surface surface = new Surface(mSurfaceTexture);
mMediaPlayer.setSurface(surface);
surface.release();
try {
mMediaPlayer.prepare();
} catch (IOException t) {
Log.e(TAG, "media player prepare failed");
}
}
}
public void onDrawFrame(GL10 glUnused) {
synchronized(this) {
if (mUpdateSurface) {
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mSTMatrix);
mUpdateSurface = false;
}
}
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
int clearMask = GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT;
if (mUsesCoverageAa) { // Tegra weirdness
final int GL_COVERAGE_BUFFER_BIT_NV = 0x8000;
clearMask |= GL_COVERAGE_BUFFER_BIT_NV;
}
GLES20.glClear(clearMask);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glUseProgram(mProgram);
checkGlError("glUseProgram");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maTextureHandle");
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
Matrix.setIdentityM(mMVPMatrix, 0);
Matrix.scaleM(mMVPMatrix, 0, 1f, 1f, 1f);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
// Alternatively we can use
//
// GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mTriangleVerticesData.length / 5);
//
// but with the current geometry setup it ends up drawing a lot of 'degenerate'
// triangles which represents more work for our shaders, especially the fragment one.
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mTriangleIndicesData.length,
GL10.GL_UNSIGNED_SHORT, mTriangleIndices);
checkGlError("glDrawElements");
GLES20.glFinish();
}
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
mViewPortSize.set(width, height);
updateVertexData();
}
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
mProgram = createProgram(mVertexShader, mFragmentShader);
if (mProgram == 0) {
return;
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new RuntimeException("Could not get attrib location for aPosition");
}
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) {
throw new RuntimeException("Could not get attrib location for aTextureCoord");
}
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uMVPMatrix");
}
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (muSTMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uSTMatrix");
}
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
mTextureID = textures[0];
GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);
checkGlError("glBindTexture mTextureID");
GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
/*
* Create the SurfaceTexture that will feed this textureID,
* and pass it to the MediaPlayer
*/
mSurfaceTexture = new SurfaceTexture(mTextureID);
mSurfaceTexture.setOnFrameAvailableListener(this);
if (mMediaPlayer != null) {
Surface surface = new Surface(mSurfaceTexture);
mMediaPlayer.setSurface(surface);
surface.release();
try {
mMediaPlayer.prepare();
} catch (IOException t) {
Log.e(TAG, "media player prepare failed");
}
}
synchronized(this) {
mUpdateSurface = false;
}
}
synchronized public void onFrameAvailable(SurfaceTexture surface) {
mUpdateSurface = true;
mGLSurfaceView.requestRender();
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader " + shaderType + ":");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
int program = GLES20.glCreateProgram();
if (program != 0) {
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
private void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
public void setVideoAspectRatio(float aspectRatio) {
// TODO
}
}
}
布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:orientation="vertical"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.abrantix.roundedvideo.VideoSurfaceView
android:id="@+id/video_surface_view1"
android:layout_weight="3"
android:layout_height="0dp"
android:layout_width="match_parent"/>
<com.abrantix.roundedvideo.VideoSurfaceView
android:id="@+id/video_surface_view2"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:layout_weight="5"
android:layout_height="0dp"
android:layout_width="match_parent"/>
<com.abrantix.roundedvideo.VideoSurfaceView
android:id="@+id/video_surface_view3"
android:layout_weight="3"
android:layout_height="0dp"
android:layout_width="match_parent"/>
</LinearLayout>
使用
public class MainActivity extends ActionBarActivity {
private VideoSurfaceView[] mVideoSurfaceView = new VideoSurfaceView[3];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final int radius = getResources()
.getDimensionPixelOffset(R.dimen.corner_radius_video);
final String[] dataSources = new String[] {
"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
};
mVideoSurfaceView[0] = (VideoSurfaceView) findViewById(R.id.video_surface_view1);
mVideoSurfaceView[1] = (VideoSurfaceView) findViewById(R.id.video_surface_view2);
mVideoSurfaceView[2] = (VideoSurfaceView) findViewById(R.id.video_surface_view3);
mVideoSurfaceView[0].setCornerRadius(radius);
mVideoSurfaceView[1].setCornerRadius(radius);
mVideoSurfaceView[2].setCornerRadius(radius);
for (int i = 0; i < mVideoSurfaceView.length; i++) {
final MediaPlayer mediaPlayer = new MediaPlayer();
final VideoSurfaceView surfaceView = mVideoSurfaceView[i];
final String dataSource = dataSources[i];
try {
mediaPlayer.setDataSource(dataSource);
// the video view will take care of calling prepare and attaching the surface once
// it becomes available
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
surfaceView.setVideoAspectRatio((float) mediaPlayer.getVideoWidth() /
(float) mediaPlayer.getVideoHeight());
}
});
surfaceView.setMediaPlayer(mediaPlayer);
} catch (IOException e) {
e.printStackTrace();
mediaPlayer.release();
}
}
// Draw a smooth background gradient that is always changing
getWindow().getDecorView().setBackgroundDrawable(new WickedGradientDrawable());
// Animate the top surface up and down so we're sure animations work
mVideoSurfaceView[0].animate()
.translationY(600f)
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) {
final float targetY = mVideoSurfaceView[0].getTranslationY() == 0 ?
600f : 0;
mVideoSurfaceView[0].animate()
.translationY(targetY)
.setDuration(1999)
.setListener(this)
.start();
}
@Override
public void onAnimationCancel(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
})
.start();
}
}
您可以在此仓库中查看更多代码:https://github.com/fabrantes/videoroundedcorners
答案 1 :(得分:0)
我知道问题是关于VideoView
,
但是VideoView
是基于SurfaceView
的,它以特定的方式呈现,因此很难实现圆角。
(到目前为止)实现此效果的最简单方法是:
TextureView
(例如与MediaPlayer
一起处理播放),TextureView
放在CardView
内app:cardCornerRadius
上使用CardView
设置半径您还可以使用CardView
'app:cardElevation
,背景和其他属性来实现某种边框样式。