首页 文章

VideoView匹配父高度并保持纵横比

提问于
浏览
23

我有一个像这样设置的VideoView:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/player" 
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <VideoView
        android:id="@+id/video"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_centerVertical="true" />

    <ProgressBar
        android:id="@+id/loader"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

</RelativeLayout>

但VideoView匹配父容器的宽度,然后根据加载的影片的宽高比设置高度 .
我想做的恰恰相反,我希望VideoView与父级的高度相匹配,同时保持宽高比不变,视频将被剪裁在两侧 .

我设法拉伸VideoView来填充父级,但是不保持宽高比 .

另一件事是,我将MediaController添加到VideoView,如下所示:

MediaController controllers = new MediaController(this) {
    @Override
    public void hide() {
        if (state != State.Hidden) {
            this.show();
        }
        else {
            super.hide();
        }
    }
};
controllers.setAnchorView(videoView);
videoView.setMediaController(controllers);

videoView.setOnPreparedListener(new OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        controllers.show();
    }
});

这很好用,并且控制器始终保持打开状态,但在计算放置视频的位置时(因为它垂直居中),控制器的高度不会被考虑在内 .

我的两个问题是:

  • 如何使VideoView与父级的高度匹配,同时保持宽高比?

  • 如何让VideoView考虑到它的控制器的高度?

谢谢 .

9 回答

  • 1

    您应该从内置视频视图扩展 .

    在显示视频视图之前调用 setVideoSize ,您可以从视频中提取的缩略图中获取视频大小 .

    因此,当调用视频视图的 onMeasure 时, mVideoWidthmVideoHeight 都是> 0 .

    如果要计算控制器的高度,可以在 onMeasure 方法中自行完成 .

    希望会有所帮助 .

    public class MyVideoView extends VideoView {
    
            private int mVideoWidth;
            private int mVideoHeight;
    
            public MyVideoView(Context context, AttributeSet attrs) {
                super(context, attrs);
            }
    
            public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
            }
    
            public MyVideoView(Context context) {
                super(context);
            }
    
            public void setVideoSize(int width, int height) {
                mVideoWidth = width;
                mVideoHeight = height;
            }
    
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                // Log.i("@@@", "onMeasure");
                int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
                int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
                if (mVideoWidth > 0 && mVideoHeight > 0) {
                    if (mVideoWidth * height > width * mVideoHeight) {
                        // Log.i("@@@", "image too tall, correcting");
                        height = width * mVideoHeight / mVideoWidth;
                    } else if (mVideoWidth * height < width * mVideoHeight) {
                        // Log.i("@@@", "image too wide, correcting");
                        width = height * mVideoWidth / mVideoHeight;
                    } else {
                        // Log.i("@@@", "aspect ratio is correct: " +
                        // width+"/"+height+"="+
                        // mVideoWidth+"/"+mVideoHeight);
                    }
                }
                // Log.i("@@@", "setting size: " + width + 'x' + height);
                setMeasuredDimension(width, height);
            }
    }
    
  • 21

    我用布局解决了这个问题 . 看起来它固定在角落时效果很好,但却导致视频歪斜 . 为了测试我将相对布局的背景更改为#990000以查看红色戳穿 .

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/relative_parent"
        android:background="#000000">
        <VideoView
            android:layout_alignParentLeft="true"
            android:layout_alignParentRight="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:id="@+id/videoView" />
    </RelativeLayout>
    
  • 0
    public class MyVideoView extends VideoView {
        private int mVideoWidth;
        private int mVideoHeight;
    
        public MyVideoView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public MyVideoView(Context context) {
            super(context);
        }
    
    
        @Override
        public void setVideoURI(Uri uri) {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(this.getContext(), uri);
            mVideoWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
            mVideoHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
            super.setVideoURI(uri);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // Log.i("@@@", "onMeasure");
            int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
            int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
            if (mVideoWidth > 0 && mVideoHeight > 0) {
                if (mVideoWidth * height > width * mVideoHeight) {
                    // Log.i("@@@", "image too tall, correcting");
                    height = width * mVideoHeight / mVideoWidth;
                } else if (mVideoWidth * height < width * mVideoHeight) {
                    // Log.i("@@@", "image too wide, correcting");
                    width = height * mVideoWidth / mVideoHeight;
                } else {
                    // Log.i("@@@", "aspect ratio is correct: " +
                    // width+"/"+height+"="+
                    // mVideoWidth+"/"+mVideoHeight);
                }
            }
            // Log.i("@@@", "setting size: " + width + 'x' + height);
            setMeasuredDimension(width, height);
        }
    }
    
  • 0

    关于问题1,我很惊讶没有人提到可能使用MediaPlayer的缩放模式 . https://developer.android.com/reference/android/media/MediaPlayer.html#setVideoScalingMode(int)

    它有2种模式 . 它们都总是填满视图区域 . 为了在保持纵横比的同时填充空间,从而裁剪长边,您需要切换到第二模式, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING . 这解决了问题的一部分 . 另一部分是更改其他人使用的VideoView 's measuring behavior, just as some of the other answers demonstrate. This is the way I did it, mostly out of laziness and not familiar with the metadata API',欢迎您使用此方法或其他方法之一来修复视图的大小 . 毯式捕获确保在mMediaPlayer存在之前调用它时的安全性,因为它可能被多次调用,并且如果字段名称发生变化,也会回退到旧行为 .

    class FixedSizeVideoView : VideoView {
        constructor(ctx: Context) : super(ctx)
        constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
    
        // rather than shrink down to fit, stay at the size requested by layout params. Let the scaling mode
        // of the media player shine through. If the scaling mode on the media player is set to the one
        // with cropping, you can make a player similar to AVLayerVideoGravityResizeAspectFill on iOS
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            try {
                val mpField = VideoView::class.java.getDeclaredField("mMediaPlayer")
                mpField.isAccessible = true
                val mediaPlayer: MediaPlayer = mpField.get(this) as MediaPlayer
    
                val width = View.getDefaultSize(mediaPlayer.videoWidth, widthMeasureSpec)
                val height = View.getDefaultSize(mediaPlayer.videoHeight, heightMeasureSpec)
                setMeasuredDimension(width, height)
            }
            catch (ex: Exception) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            }
        }
    }
    

    因此,在布局中使用此类,只需在有机会的地方更改媒体播放器上的缩放模式 . 如:

    video.setOnPreparedListener { mp: MediaPlayer ->
                mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
                mp.isLooping = true
                mp.setScreenOnWhilePlaying(false)
            }
            video.start()
    
  • 5

    我花时间在互联网上搜索这个我没有't find any better solution that can apply to the default video view. So I searched for libraries which will solve my requirement finally I found one ' FullscreenVideoView'(https://github.com/rtoshiro/FullscreenVideoView)这解决了我的要求

  • 1

    Quick and efficient fix:

    无需创建从VideoView扩展的自定义视图 . 只需设置一个足够大的值 android:layout_width . 这会将视频视图的 widthSpecMode 设置为 View.MeasureSpec.AT_MOST ,然后VideoView的 onMeasure() 方法将自动调整其宽度,保持比率 .

    <VideoView
         android:id="@+id/video"
         android:layout_width="2000dp"
         android:layout_height="match_parent" />
    
  • 6

    我尝试了很多解决方案,而我的视频总是1000 * 1000格式,所以我为那些了解其宽高比的人创建了一个简单的解决方案 . 首先在RelativeLayout中创建一个VideoView,如下所示:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/video_holder"
          android:layout_width="wrap_content"
          android:layout_height="fill_parent"
          android:clipToPadding="false">
    
          <VideoView  
              android:id="@+id/videoView"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:layout_alignParentBottom="true"
              android:layout_alignParentEnd="true"
              android:layout_alignParentStart="true"
              android:layout_alignParentTop="true" />
    </RelativeLayout>
    

    然后在加载视频之前更改高度并使用以下编程方式:

    int i = videoView.getHeight() > videoView.getWidth() ? videoView.getHeight() : videoView.getWidth();
        video_holder.setLayoutParams(new ConstraintLayout.LayoutParams(i, i));
    

    当然这仅适用于1:1宽高比,但您可以使用宽高比来改变高度或宽度 .

  • -1

    第一次问题回答了我的问题,而不是答案!我的问题是我在全屏视频下面有一个空白区域 . 我将 layout_height 设置为 match_parent . 解决方案是将其设置为 wrap_content 并为父级提供黑色背景 . 那,并且 VideoView 在其父级中垂直居中 .

    我写这篇评论作为评论,但后来认为有人可能有同样的问题,所以这里也是一个答案 .

  • 6

    Jobbert在Kotlin的回答,万一有人需要它:

    val max = if (videoView.height > videoView.width) videoView.height else videoView.width
    videoView.layoutParams = ConstraintLayout.LayoutParams(max, max)
    

相关问题