【问题标题】:Android VideoView orientation change with buffered videoAndroid VideoView 方向随缓冲视频的变化
【发布时间】:2010-12-13 22:16:27
【问题描述】:

我正在尝试在 Android 市场中复制最新 YouTube 应用的功能。观看视频时,有两种不同的布局,一种是提供附加信息的纵向布局,另一种是提供视频全屏视图的横向布局。


YouTupe 纵向模式应用


横向模式下的 YouTube 应用

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

这通常很容易做到 - 只需在 layout-land 中指定一个备用布局,一切都会好起来的。 YouTube 应用做得很好(以及我试图复制的)是,在方向改变时,视频会继续播放,并且不必从头开始重新缓冲。

我发现重写 onConfigurationChange() 并设置新的 LayoutParameters 将允许我调整视频大小而无需强制重新缓冲 - 但是当多次旋转屏幕时,视频会随机缩放到不同的宽度/高度。我已经尝试在 VideoView 上进行各种 invalidate() 调用,尝试在父 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

也许有人知道“覆盖”是什么以及为什么它没有得到正确的高度值?

【问题讨论】:

  • 终于使用新的 Fragment API 和 TextureView 完成了这项工作,我知道这是一个较老的问题,但我也发布了我的解决方案,因此人们可能会看到更新的方法。
  • 这个答案对我有用 - 一行代码,因为我已经继承了 VideoView stackoverflow.com/a/22101290/1458948

标签: android android-orientation android-videoview


【解决方案1】:

编辑:(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 没有在旋转时调整大小,您需要确保您调整到的尺寸是准确 strong> 等于或小于可见区域。当我在屏幕上仍然有通知栏/标题栏并且根本没有调整 VideoView 的大小时,我遇到了一个非常大的问题,我将 VideoView 的宽度/高度设置为整个显示大小。只需删除通知栏和标题栏即可解决问题。

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

【讨论】:

  • 感谢您的信息!我已经尝试过了,这门课对轮换有很大帮助。但是,在我的设备上进行纵向时,屏幕会损坏。你有什么想法为什么会发生这种情况?我已经开始stackoverflow.com/questions/6524659/… 希望找到答案,但到目前为止还没有任何想法。
  • 我用过,但是视频播放不了。
【解决方案2】:

这是用最少的代码完成您想要的事情的一种非常简单的方法:

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() 函数来完成。
【解决方案3】:

首先,非常感谢您的广泛回答。

我遇到了同样的问题,视频在大多数情况下会变小或变大,或者在旋转后在 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 错误。

【讨论】:

    【解决方案4】:

    VideoView 使用所谓的叠加层,即呈现视频的区域。该叠加层位于包含 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 的布局尺寸相符。

    【讨论】:

      【解决方案5】:

      复制 YouTube 应用

      我设法构建了一个不需要 android:configChanges="orientation" 或自定义VideoView 的示例项目。由此产生的体验与 YouTube 应用在视频播放期间处理旋转的方式相同。换句话说,视频不需要暂停、重新缓冲或重新加载,并且在设备方向改变时不会跳过或丢弃任何音频帧。

      最优方法

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

      我创建了一个full working example,展示了如何以及何时初始化您的 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(我还没有这样做)。

      最后,如果您有兴趣,checkout my initial question 详细说明:我的原始方法,Android 媒体堆栈,为什么它不起作用,以及替代解决方法。

      【讨论】:

      • 保留的Fragment会造成内存泄漏吗?
      • @GZ95 没有。我们将丢弃所有对 Context 的引用并重用我们保留的每个引用。
      • 我正在尝试实施您的解决方案,但无法在轮播时保留视频。视图总是变黑,然后视频从头开始。知道是什么原因造成的吗?
      • 当你不通过TextureView.setSurfaceTexture()设置旧纹理时,对我来说会发生这种情况。
      • @dcow 如何克服活动中的视频缓冲?
      【解决方案6】:

      使用这个:

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

      【讨论】:

        【解决方案7】:

        我从这里的一些答案中举了一些例子,并尝试按照我的方式去做。看起来很简单。

        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>
        

        我在此下方有另一个相对布局,它不在此布局中。但这不会有任何区别。

        【讨论】:

          【解决方案8】:

          查看我的示例代码,它对我有用

          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"
          

          风景

          my source code here

          【讨论】:

          • tx 为您的解决方案提供了很多帮助
          • 嗨 @war_Hero ,我刚刚更新了我的代码,因为当设备横向 -> 打开应用程序 -> 视频不是全屏时出现问题,但现在它可以工作了
          • 是的,它有这个问题,而且紧迫的问题是旧代码不适用于片段
          • 在线出错:Display display = ((Activity) context).getWindowManager().getDefaultDisplay();原因:java.lang.ClassCastException: android.view.ContextThemeWrapper 无法转换为 android.app.Activity
          【解决方案9】:

          虽然 Mark37(非常有用)answer 确实有效,但它需要手动设置尺寸(使用 setDimensions)。这在预先知道所需尺寸的应用程序中可能很好,但如果您希望视图根据视频的参数自动确定大小(例如,确保保持原始纵横比),则需要不同的方法.

          幸运的是,setDimensions 部分实际上并不是必需的。 VideoView 的onMeasure 已经包含了所有必要的逻辑,所以不用依赖别人调用 setDimensions,可以只调用 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 建议的 approach 非常相似,它的作用基本相同,只是使用 onLayout 而不是 onMeasure。

          【讨论】:

          • 我使用了你的代码,但是我对屏幕尺寸的高度匹配有疑问,你能帮我解决一下吗..
          【解决方案10】:

          简单的答案和最新的 2018。

          此答案的积极点:代码如下

          1) 是否设置为 FULL_SCREEN 无关紧要。由于它与父级匹配,因此它将始终有效。在旧接受的答案中,如果您未设置为 FULL_SCREEN,android 将占用整个手机大小,然后 VideoView 将位于屏幕之外;

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

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

          4) 还有一个按钮可以改变方向,就像 youtube 应用一样;

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

          @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();
              }
          }
          

          OBS:我在 ConstraintLayout 中使用 PlayerView。因为我正在使用ExoPlayer Media Library

          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. 设置为相反方向,例如,如果它在 LandScape 中是当前的,则设置为纵向。

          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:旋转设备不再起作用

          您会注意到,如果您单击按钮更改方向,旋转设备将无法再更改播放器大小。发生这种情况是因为您以编程方式将方向设置为 LandScape 或 Portrait,并且它将永远保持这种状态,因此您需要一种将方向设置为 SENSOR 的方法,因此当您旋转设备时,它将检查真正的当前方向。下面的方法会帮你搞定的。

          问题 2:手机的自动旋转关闭,它仍然改变方向

          在下面的方法中,您需要检查手机的自动旋转是打开还是关闭,如果它是打开的,您可以将 ORIENTATION 设置为 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();
          }
          

          如果您使用媒体应用程序,我有一个sample,它将指导您如何强大地使用 Exoplayer Library。

          【讨论】:

            【解决方案11】:

            我尝试使用其他答案的代码,但它并没有解决我的问题,因为如果我快速旋转屏幕,我的视频大小会被颠簸。所以我部分地使用了他们的代码,我把它放在一个可以很快更新窗口的线程中。 所以,这是我的代码:

              new Thread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        while(true)
                        {
                            try
                            {
                                Thread.sleep(1);
                            }
                            catch(InterruptedException e){}
                            handler.post(new Runnable()
                            {
                                @Override
                                public void run()
                                {
                                    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                                            WindowManager.LayoutParams.FLAG_FULLSCREEN);
                                }
                            });
                        }
                    }
                }).start();
            

            通过这种方式,视频大小将始终正确(在我的情况下)。

            【讨论】:

              猜你喜欢
              • 2017-08-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-29
              • 2012-01-09
              • 2013-01-19
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多