Android VideoView缓冲视频的方向更改

50

我正在尝试复制 Android 市场上最新的 YouTube 应用程序的功能。在观看视频时,有两个单独的布局,一个是纵向布局,提供额外的信息,另一个是横向布局,提供视频的全屏视图。

YouTube app portrait layout
YouTube 应用程序纵向布局

YouTube app landscape layout
YouTube 应用程序横向布局

(很抱歉照片的随机性,但它们是我能找到的实际布局的第一批图片)

通常情况下,这很容易做到-只需在layout-land中指定一个备用布局即可。YouTube应用程序做得非常好的事情(也是我要复制的内容)是,在方向改变时,视频会继续播放,不必从开头重新缓冲。

我已经想出了覆盖onConfigurationChange()并设置新的LayoutParameters将允许我调整视频大小而不强制重新缓冲 - 但是当旋转屏幕多次时,视频会随机缩放到不同的宽度/高度。我尝试了所有类型的invalidate()调用VideoView,尝试在父RelativeLayout容器上调用RequestLayout()并尝试尽可能多的不同事物,但似乎无法使其正常工作。任何建议都将不胜感激!

这是我的代码:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        questionText.setVisibility(View.GONE);
        respond.setVisibility(View.GONE);
        questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    } else {
        questionText.setVisibility(View.VISIBLE);
        respond.setVisibility(View.VISIBLE);
        Resources r = getResources();
        int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150.0f, r.getDisplayMetrics());
        questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, height));
    }
}

编辑:我在logcat中发现了一些有趣的输出,当我的视频旋转时会出现这些输出,似乎这是罪魁祸首 - 尽管我不知道如何修复它:

当正确调整大小(占据整个窗口)时的logcat输出

注意h=726

12-13 15:37:35.468  1262  1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=210}
12-13 15:37:35.561  1262  1268 I TIOverlay: Position/X0/Y76/W480/H225
12-13 15:37:35.561  1262  1268 I TIOverlay: Adjusted Position/X1/Y0/W403/H225
12-13 15:37:35.561  1262  1268 I TIOverlay: Rotation/90
12-13 15:37:35.561  1262  1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224
12-13 15:37:35.561  1262  1268 I Overlay : v4l2_overlay_set_position:: w=402 h=726
12-13 15:37:35.561  1262  1268 I Overlay : dumping driver state:
12-13 15:37:35.561  1262  1268 I Overlay : output pixfmt:
12-13 15:37:35.561  1262  1268 I Overlay : w: 432
12-13 15:37:35.561  1262  1268 I Overlay : h: 240
12-13 15:37:35.561  1262  1268 I Overlay : color: 7
12-13 15:37:35.561  1262  1268 I Overlay : UYVY
12-13 15:37:35.561  1262  1268 I Overlay : v4l2_overlay window:
12-13 15:37:35.561  1262  1268 I Overlay : window l: 1 
12-13 15:37:35.561  1262  1268 I Overlay : window t: 0 
12-13 15:37:35.561  1262  1268 I Overlay : window w: 402 
12-13 15:37:35.561  1262  1268 I Overlay : window h: 726

当不正确地调整大小时的Logcat输出(占据全屏幕的一小部分)

注意h = 480

12-13 15:43:00.085  1262  1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=216}
12-13 15:43:00.171  1262  1268 I TIOverlay: Position/X0/Y76/W480/H225
12-13 15:43:00.171  1262  1268 I TIOverlay: Adjusted Position/X138/Y0/W266/H225
12-13 15:43:00.171  1262  1268 I TIOverlay: Rotation/90
12-13 15:43:00.179  1262  1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224
12-13 15:43:00.179  1262  1268 I Overlay : v4l2_overlay_set_position:: w=266 h=480
12-13 15:43:00.179  1262  1268 I Overlay : dumping driver state:
12-13 15:43:00.179  1262  1268 I Overlay : output pixfmt:
12-13 15:43:00.179  1262  1268 I Overlay : w: 432
12-13 15:43:00.179  1262  1268 I Overlay : h: 240
12-13 15:43:00.179  1262  1268 I Overlay : color: 7
12-13 15:43:00.179  1262  1268 I Overlay : UYVY
12-13 15:43:00.179  1262  1268 I Overlay : v4l2_overlay window:
12-13 15:43:00.179  1262  1268 I Overlay : window l: 138 
12-13 15:43:00.179  1262  1268 I Overlay : window t: 0 
12-13 15:43:00.179  1262  1268 I Overlay : window w: 266 
12-13 15:43:00.179  1262  1268 I Overlay : window h: 480

也许有人知道'Overlay'是什么,为什么它没有得到正确的高度值?

最终使用新的Fragment API和TextureView解决了这个问题。我知道这是一个旧问题,但我也发布了我的解决方案,以便人们可以看到更新的方法。 - dcow
这个答案对我有用 - 一行代码,因为我已经子类化了VideoView https://dev59.com/p2855IYBdhLWcg3wSSKl#22101290 - William
11个回答

57

编辑:(2016年6月)

这篇回答非常老了(我想是针对Android 2.2/2.3的),可能不如下面的其他回答相关! 除非您正在开发旧版Android应用程序,否则请先参考它们 :)


我能够将问题缩小到VideoView类中的onMeasure函数。 通过创建一个子类并覆盖onMeasure函数,我能够获得所需的功能。

public class VideoViewCustom extends VideoView {

    private int mForceHeight = 0;
    private int mForceWidth = 0;
    public VideoViewCustom(Context context) {
        super(context);
    }

    public VideoViewCustom(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VideoViewCustom(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setDimensions(int w, int h) {
        this.mForceHeight = h;
        this.mForceWidth = w;

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.i("@@@@", "onMeasure");

        setMeasuredDimension(mForceWidth, mForceHeight);
    }
}

然后在我的Activity中,我只是按照以下方式进行:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        questionVideo.setDimensions(displayHeight, displayWidth);
        questionVideo.getHolder().setFixedSize(displayHeight, displayWidth);

    } else {
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

        questionVideo.setDimensions(displayWidth, smallHeight);
        questionVideo.getHolder().setFixedSize(displayWidth, smallHeight);

    }
}
这一行代码:
questionVideo.getHolder().setFixedSize(displayWidth, smallHeight);

为了使这个工作正常,您需要使用该键。如果您在调用setDimensions时没有使用此键,则视频仍然不会调整大小。

您唯一需要做的其他事情是确保在onCreate()方法中也调用setDimensions(),否则您的视频将不会开始缓冲,因为视频不会被设置为绘制在任何大小的表面上。

// onCreate()
questionVideo.setDimensions(initialWidth, initialHeight); 

最后一个关键部分 - 如果您发现VideoView在旋转时无法调整大小,您需要确保调整大小的尺寸要么完全等于可见区域的尺寸,要么小于可见区域的尺寸。我曾遇到过一个很大的问题,当我仍然在屏幕上显示通知栏/标题栏时,我将VideoView的宽度/高度设置为整个显示尺寸时,它根本没有调整VideoView的大小。只需删除通知栏和标题栏即可解决该问题。

希望这对未来的某个人有所帮助!


谢谢提供的信息!我已经尝试了,这个类在旋转方面确实有很大帮助。但是,在我的设备上,当切换到竖屏时,屏幕会出现损坏。你有任何想法为什么会发生这种情况吗?我已经开始在https://dev59.com/_2w15IYBdhLWcg3wkcqq上寻找答案,但目前还没有头绪。 - Grzegorz Adam Hankiewicz
我已经使用它,但它没有播放视频。 - Shajeel Afzal

25

这是一种非常简单的方法,只需使用最少量的代码即可实现您想要的结果:

AndroidManifest.xml:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"
注意:根据您的API进行必要的编辑,此处涵盖10+,但较低的API需要删除此行中的“screenSize|screenLayout|uiMode”部分。
在“OnCreate”方法内,通常在“super.onCreate”下方添加:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);

然后在某个地方,通常是在底部,添加:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
}

每当方向更改而不中断播放时,这将调整视频大小以全屏显示,并且仅需要覆盖配置方法。


在某些设备上,您可能还需要隐藏媒体控制器,否则它会阻止视频调整大小。这可以使用 mediaController.hide() 函数来完成。 - Abandoned Cart

24

首先,非常感谢您提供的详细答案。

我遇到了同样的问题,视频在VideoView旋转后大多数情况下会变得更小或更大或扭曲。

我尝试了您的解决方案,但我也尝试了其他随机的东西,并且偶然发现,如果我的VideoView在其父容器中居中,它自己就可以神奇地运行(不需要自定义VideoView或任何其他东西)。

更具体地说,使用此布局,我大多数情况下都能够复现问题:

<?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" >

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

使用这种布局,我从未遇到过这个问题(而且视频是居中的,这本来就应该是这样的):

<?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" >

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

</RelativeLayout>

使用wrap_content而不是match_parent也可以工作(视频仍然占据所有空间),但我不太理解这样做的意义。

总之,我对此没有任何解释 - 这看起来像是一个VideoView的bug。


简单有效的答案。然而,在某些情况下, android:layout_centerInParent="true" 会添加一些垂直和水平填充,这可能不是理想的选择。在这种情况下,请尝试使用 android:layout_alignParentBottom="true" android:layout_alignParentTop="true" android:layout_alignParentStart="true" android:layout_alignParentEnd="true"。 - Shashank Saxena

11

VideoView使用称为“覆盖层(overlay)”的区域来呈现视频。该覆盖层位于持有VideoView的窗口后面。 VideoView在其窗口中打洞,以便覆盖层可见。然后,它将其与布局保持同步(例如,如果您移动或调整VideoView,则必须移动并调整覆盖层)。

在布局阶段存在某个错误,使覆盖层使用由VideoView设置的先前大小。

要解决此问题,请子类化VideoView并覆盖onLayout:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    getHolder().setSizeFromLayout();
}

同时叠加层将从VideoView的布局尺寸中获得正确的大小。


7

复制YouTube应用程序

我成功构建了一个示例项目,不需要 android:configChanges="orientation" 或自定义的 VideoView。结果与YouTube应用程序在视频播放期间处理旋转的方式完全相同。换句话说,当设备方向改变时,视频无需暂停、重新缓冲或重新加载,并且不会跳过或丢失任何音频帧。

最佳方法

此方法使用TextureView及其附带的SurfaceTexture作为MediaPlayer当前视频帧的接收器。由于SurfaceTexture使用GL纹理对象(仅由GL上下文引用的整数),因此认为通过配置更改保留对SurfaceTexture的引用是可以的。TextureView本身在配置更改期间被销毁和重建(以及支持Activity),并且新创建的TextureView只需在附加之前使用SurfaceTexture引用进行更新。

我创建了一个全面的工作示例,展示了如何初始化您的MediaPlayer和可能的MediaController的时间,并将与此问题相关的有趣部分突出显示如下:

public class VideoFragment {
    
    TextureView mDisplay;
    SurfaceTexture mTexture;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        
        final View rootView = inflater.inflate(R.layout.fragment_main, container, false);
        mDisplay = (TextureView) rootView.findViewById(R.id.texture_view);
        if (mTexture != null) {
            mDisplay.setSurfaceTexture(mTexture);
        }
        mDisplay.setSurfaceTextureListener(mTextureListener);

        return rootView;
    }

    TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            mTexture = surface;
            // Initialize your media now or flag that the SurfaceTexture is available..
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            mTexture = surface;
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            mTexture = surface;
            return false; // this says you are still using the SurfaceTexture..
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            mTexture = surface;
        }
    };

    @Override
    public void onDestroyView() {
        mDisplay = null;
        super.onDestroyView();
    }

    // ...

}
 

由于该解决方案使用保留的 Fragment 而不是手动处理配置更改的 Activity,因此您可以像自然情况下一样充分利用特定于配置的资源膨胀系统。不幸的是,如果您的最低 SDK 版本低于 API 16,则基本上需要回退 TextureView(我尚未完成)。
最后,如果您感兴趣,请查看我的初始问题,其中详细介绍了我的原始方法、Android 媒体堆栈、为什么它不起作用以及备用解决方法。

我正在尝试实现您的解决方案,但无法在旋转时保留视频。视图总是变黑,然后视频从头开始播放。有什么想法是什么原因引起的吗? - Micky
@dcow 怎样克服活动中的视频缓冲问题? - Erum
@dcow,你如何初始化媒体/视频? - Lv99Zubat
@Rai 你有什么媒体? - dcow
@dcow 是一个 VideoView,但我最终找到了这个 - Lv99Zubat
显示剩余3条评论

3
使用这个:
@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {

            getActivity().getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
            getActivity().getWindow().clearFlags(
                    WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {

            getActivity().getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            getActivity().getWindow().clearFlags(
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);

        }
    }

此外,请不要忘记将以下行添加到您在清单文件中的活动中:
android:configChanges="orientation|keyboard|keyboardHidden|screenSize"

2
我从这里的一些答案中选了几个例子,试着用自己的方式来做。看起来是一个简单的解决方案。
videoLayout = (RelativeLayout) videoView.findViewById(R.id.videoFrame);

在片段内的onConfigurationChange。当视频以横向全屏模式显示时,请注意我隐藏了操作栏。

@Override
public void onConfigurationChanged(Configuration newConfig) {

    int          height     = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200.0f,getResources().getDisplayMetrics());
    ActionBar    actionBar  = weatherActivity.getSupportActionBar();
    LayoutParams params     = videoLayout.getLayoutParams();

    if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
    {
        if(actionBar.isShowing())
            actionBar.hide();


        params.width  = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.MATCH_PARENT;

        videoLayout.requestLayout();
    }
    else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        if(!actionBar.isShowing())
            actionBar.show();

        params.width  = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = height;

        videoLayout.requestLayout();
    }

    super.onConfigurationChanged(newConfig);
}   

这是我的布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<RelativeLayout 
    android:id="@+id/videoFrame"
    android:layout_height="200dp"
    android:layout_width="match_parent">

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

</RelativeLayout>

我有另一个相对布局在这个布局下面,但这不会有任何影响。


2

看看我的示例代码,它适合我

public class CustomVideoView extends android.widget.VideoView {
private int width;
private int height;
private Context context;
private VideoSizeChangeListener listener;
private boolean isFullscreen;

public CustomVideoView(Context context) {
    super(context);
    init(context);
}

public CustomVideoView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

/**
 * get video screen width and height for calculate size
 *
 * @param context Context
 */
private void init(Context context) {
    this.context = context;
    setScreenSize();
}

/**
 * calculate real screen size
 */
private void setScreenSize() {
    Display display = ((Activity) context).getWindowManager().getDefaultDisplay();

    if (Build.VERSION.SDK_INT >= 17) {
        //new pleasant way to get real metrics
        DisplayMetrics realMetrics = new DisplayMetrics();
        display.getRealMetrics(realMetrics);
        width = realMetrics.widthPixels;
        height = realMetrics.heightPixels;

    } else if (Build.VERSION.SDK_INT >= 14) {
        //reflection for this weird in-between time
        try {
            Method mGetRawH = Display.class.getMethod("getRawHeight");
            Method mGetRawW = Display.class.getMethod("getRawWidth");
            width = (Integer) mGetRawW.invoke(display);
            height = (Integer) mGetRawH.invoke(display);
        } catch (Exception e) {
            //this may not be 100% accurate, but it's all we've got
            width = display.getWidth();
            height = display.getHeight();
        }

    } else {
        //This should be close, as lower API devices should not have window navigation bars
        width = display.getWidth();
        height = display.getHeight();
    }

    // when landscape w > h, swap it
    if (width > height) {
        int temp = width;
        width = height;
        height = temp;
    }
}

/**
 * set video size change listener
 *
 */
public void setVideoSizeChangeListener(VideoSizeChangeListener listener) {
    this.listener = listener;
}

public interface VideoSizeChangeListener {
    /**
     * when landscape
     */
    void onFullScreen();

    /**
     * when portrait
     */
    void onNormalSize();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // full screen when landscape
        setSize(height, width);
        if (listener != null) listener.onFullScreen();
        isFullscreen = true;
    } else {
        // height = width * 9/16
        setSize(width, width * 9 / 16);
        if (listener != null) listener.onNormalSize();
        isFullscreen = false;
    }
}

/**
 * @return true: fullscreen
 */
public boolean isFullscreen() {
    return isFullscreen;
}

/**
 * set video sie
 *
 * @param w Width
 * @param h Height
 */
private void setSize(int w, int h) {
    setMeasuredDimension(w, h);
    getHolder().setFixedSize(w, h);
}
}

不要在更改方向时重新创建视图。
// AndroidManifest.xml
android:configChanges="screenSize|orientation|keyboardHidden"

头像

横向 横向

我的源代码在这里


谢谢你的解决方案,它帮了我很多。 - Mightian
嗨@war_Hero,我刚刚更新了我的代码,因为当设备横向时->打开应用程序->视频不是全屏时会出现问题,但现在它可以正常工作了。 - vuhung3990
是的,它有那个问题,而且现在最紧迫的问题是旧代码不能与片段搭配使用。 - Mightian
在这一行代码上出现了错误:Display display = ((Activity) context).getWindowManager().getDefaultDisplay();原因是:java.lang.ClassCastException: android.view.ContextThemeWrapper无法转换为android.app.Activity。 - ErShani

1

简单明了的答案,更新至2018年。

此答案的优点:代码如下

1)无论您是否设置为FULL_SCREEN,都没有关系。由于其与父级匹配,因此它始终有效。在旧的被接受的答案中,如果您不设置为FULL_SCREEN,则Android将占用整个手机尺寸,然后VideoView将位于屏幕外;

2)您不需要创建自定义VideoView或PlayerView;

3)它不会多次调用该方法。在旧的接受的答案中,它会多次不必要地调用onMeasure;

4)还有一个按钮可以更改方向,就像YouTube应用程序一样;

5)将手机的自动旋转设置为OFF也会阻止应用程序方向更改。

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    ...
    setPlayerViewLayoutParams();
    ...
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        setPlayerViewLayoutParamsForLandScape();
    } else {
        setPlayerViewLayoutParamsForPortrait();
    }
}

private void setPlayerViewLayoutParamsForLandScape() {
    ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT);
    mPlayerView.setLayoutParams(lp);
}

private void setPlayerViewLayoutParamsForPortrait() {
    Display display = myContext.getWindowManager().getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int width = size.x;
    Double doubleHeight = width / 1.5;
    Integer height = doubleHeight.intValue();

    ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.MATCH_PARENT, height);
    mPlayerView.setLayoutParams(lp);
}

private void setPlayerViewLayoutParams() {
    if (myContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        setPlayerViewLayoutParamsForLandScape();
    } else {
        setPlayerViewLayoutParamsForPortrait();
    }
}

注意:我正在使用 ConstraintLayout 中的 PlayerView。因为我正在使用 ExoPlayer 媒体库

fragment_player.xml

<android.support.constraint.ConstraintLayout   
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/player_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorBlack"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintVertical_bias="0.0"/>

</android.support.constraint.ConstraintLayout>

额外信息

我还想要一个按钮来改变方向

为了完全复制Youtube应用程序,您可能需要一个按钮来将播放器设置为横向或纵向,这对于用户关闭手机自动旋转时非常有用。

首先,您需要按照以下步骤进行操作:

  1. 创建将更改方向的按钮;
  2. 当用户单击该按钮时,请检查当前方向;
  3. 设置为相反的方向,例如,如果当前处于横向,则设置为纵向。

PlayerFragment.java

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.image_button_full_screen:
            if (myContext.getResources().getConfiguration().orientation 
                == Configuration.ORIENTATION_LANDSCAPE) {
                myContext.setRequestedOrientation(
                        ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
            } else {
                myContext.setRequestedOrientation(
                        ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
            }
            break;
    }
}

问题1:设备旋转不再起作用

您会注意到,如果点击按钮更改方向,旋转设备将不再起作用以更改播放器大小。这是因为您通过编程设置了方向为横屏或竖屏,并且它将永远保持这样,因此您需要一种方法将方向设置为SENSOR,这样当您旋转设备时,它将检查实际当前方向。下面的方法将为您完成此操作。

问题2:手机自动旋转关闭,但仍然更改方向

在下面的方法中,您需要检查手机的自动旋转是否打开或关闭,如果打开,您可以将方向设置为SENSOR,这样当用户旋转设备时,手机将能够更改方向。如果自动旋转关闭,则不执行任何操作,因此方向将被锁定为竖屏或横屏,具体取决于用户是否按下全屏按钮。

private void setOrientationListener() {
    OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
        @Override
        public void onOrientationChanged(int orientation) {
            // 94 > 90 - 10 and 94 < 90 + 10     //   80 < orientation < 100.  Rotating to the LEFT.
            // 278 > 270 - 10 and 278 < 270 + 10 //   260 < orientation < 280. Rotating to the RIGHT.
            int epsilon = 10;
            int leftLandscape = 90;
            int rightLandscape = 270;
            if (epsilonCheck(orientation, leftLandscape, epsilon) ||
                    epsilonCheck(orientation, rightLandscape, epsilon)) {
                if (android.provider.Settings.System.getInt(getContentResolver(),
                        Settings.System.ACCELEROMETER_ROTATION, 0) == 1) {
                    // Phone's auto rotation is ON
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
                }
            }
        }

        private boolean epsilonCheck(int a, int b, int epsilon) {
            return a > b - epsilon && a < b + epsilon;
        }
    };
    orientationEventListener.enable();
}

如果您正在使用媒体应用程序,我有一个示例,可以指导您如何强大地使用Exoplayer Library。

1
虽然Mark37的(非常有用的)答案确实有效,但它需要手动设置尺寸(使用setDimensions)。这在应用程序中可能没问题,因为所需的尺寸是预先知道的,但如果您希望视图根据视频参数自动确定大小(例如,确保保留原始宽高比),则需要采用不同的方法。
幸运的是,事实证明setDimensions部分实际上并不是必要的。VideoView的onMeasure已经包含了所有必要的逻辑,因此可以只调用super.onMeasure,然后使用视图的getMeasuredWidth/getMeasuredHeight来获取固定大小。
因此,VideoViewCustom类变得非常简单:
public class VideoViewCustom extends VideoView {

    public VideoViewCustom(Context context) {
        super(context);
    }

    public VideoViewCustom(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VideoViewCustom(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        getHolder().setFixedSize(getMeasuredWidth(), getMeasuredHeight());
    }
}

这个版本不需要在调用方添加任何额外的代码,并充分利用了VideoView现有的onMeasure实现。

事实上,这与Zapek建议的方法非常相似,只是使用onLayout而不是onMeasure。


我已经使用了你的代码,但是我在屏幕尺寸匹配方面遇到了问题,你能帮我解决吗? - Sagar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接