带有圆角和边框的Android VideoView

时间:2018-10-05 09:55:49

标签: android android-videoview rounded-corners

我是Android应用程序开发的新手。我有一个带有GridLayoutManager的RecyclerView,每个项目都是一个带有边框和圆角的单元格。单元内有一个VideoView和带有摄像机名称的标签。我的问题是在圆形单元格内“剪切” VideoView。 这是我所拥有的一些屏幕截图: enter image description here

这是我的单元格的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>

谢谢!

2 个答案:

答案 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,背景和其他属性来实现某种边框样式。