50

Android マーケットプレイスで最新の YouTube アプリの機能を複製しようとしています。ビデオを視聴する場合、2 つの個別のレイアウトがあります。1 つは追加情報を提供する縦向きで、もう 1 つはビデオの全画面表示を提供する横向きです。

YouTube アプリの縦向きレイアウト
縦向きモードの YouTube アプリ

YouTube アプリの横向きレイアウト
横向きモードの 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

おそらく誰かが「オーバーレイ」とは何か、そしてなぜ正しい高さの値を取得していないのか知っていますか?

4

11 に答える 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);
    }
}

次に、アクティビティ内で次のことを行いました。

@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の幅/高さをディスプレイ全体のサイズに設定していたという非常に大きな問題がありました。通知バーとタイトルバーを削除するだけで問題が解決しました。

うまくいけば、これは将来誰かを助けるでしょう!

于 2010-12-15T16:52:39.333 に答える
25

最小限のコードで目的を達成するための非常に簡単な方法を次に示します。

AndroidManifest.xml:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"

注: API の必要に応じて編集します。これは 10 以上をカバーしますが、下位の API では、この行の「screenSize|screenLayout|uiMode」部分を削除する必要があります。

通常は「super.onCreate」の下にある「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);
}

これにより、再生を中断することなく方向が変更されるたびにビデオのサイズがフルスクリーンに変更され、構成メソッドをオーバーライドするだけで済みます。

于 2013-01-01T18:55:41.837 に答える
23

まず、あなた自身の広範な回答に感謝します。

私は同じ問題を抱えていました。ほとんどの場合、ビデオは回転後に 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>

この 1 つのレイアウトでは、問題が発生することはありません (さらに、ビデオは中央に配置されます。とにかく、これが本来あるべき状態です ;):

<?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 のバグのように見えます。

于 2011-11-01T23:35:45.417 に答える
11

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 のレイアウト ディメンションから正しいサイズになります。

于 2014-02-28T16:59:17.887 に答える
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"
于 2013-10-28T10:48:13.290 に答える
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"

肖像画

風景 風景

ここに私のソースコード

于 2015-06-26T19:32:59.270 に答える
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>

この下に、このレイアウトにない別の相対レイアウトがあります。しかし、それは何の違いもありません。

于 2015-04-08T17:53:14.577 に答える
1

Mark37 の (非常に便利な)回答は機能しますが、(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());
    }
}

This version doesn't require any additional code in the caller, and takes full advantage of VideoView's existing onMeasure implementation.

This is in fact rather similar to the approach suggested by Zapek, which does basically the same thing, only with onLayout instead of onMeasure.

于 2014-04-09T11:27:17.233 に答える