用原生VideoView進(jìn)行全屏播放時的問題
之前參加了一個課程,里面有一節(jié)講到了用視頻作為啟動界面。講師用的是自定義VideoView,重寫onMeasure方法,因?yàn)樵腣ideoView在那情況下不能實(shí)現(xiàn)全屏播放。當(dāng)時沒有深入研究,現(xiàn)在補(bǔ)回來。
用的是36氪之前的視頻(608×1080)和Genymotion中的Google Nexus 5(1080×1920)。
一、效果圖
1、原生VideoView的效果,這里沒有讓底部的導(dǎo)航欄也變透明。因?yàn)榻貓D上來很難看到差別,后面會解釋。

xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"/>
</LinearLayout>
java
public class VideoViewActivity extends AppCompatActivity {
private VideoView mVideoView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
mVideoView = (VideoView) findViewById(R.id.video_view);
mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.kr36));
mVideoView.start();
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mVideoView.start();
}
});
}
}
2、自定義的VideoView

布局文件基本同上,除了控件名和id
... <com.example.test.test_fitstatusbar.CustomVideoView android:id="@+id/custom_video_view" ...
Activity.java也是基本同上。這里是自定義VideoView的java代碼,只重寫了onMeasure方法。
public class CustomVideoView extends VideoView {
public CustomVideoView(Context context) {
super(context);
}
public CustomVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
}
}
二、在對比原生VideoView的onMeasure方法之前,先了解一些事情。
1、這里涉及到MeasureSpec類,這個類代碼不多,但很精妙。我也有很多地方?jīng)]弄懂。不過在這里,只需了解它的三種mode就可以了。
/**
* 1、UNSPECIFIED
* 根據(jù)源碼的注釋,其大概意思是parent不對child做出限制.它想要什么size就給什么size.看了一些教程,都說用得很少,或者是系統(tǒng)內(nèi)部才用得上.所以先不管了
* 2、EXACTLY
* 對應(yīng)于match_parent和給出具體的數(shù)值
* 3、AT_MOST
* 對應(yīng)于wrap_content
*/
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
......
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
......
}
而這里,我所有控件的width和height都是mach_parent,所以以下分析都是基于MeasureSpec.EXACTLY這個mode。
2、getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
因?yàn)槎际荕easureSpec.EXACTLY,所以最終返回的結(jié)果是MeasureSpec.getSize(measureSpec),與size,也就是第一個參數(shù)無關(guān)。
3、setMeasuredDimension
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
中間的判斷語句,涉及到ViewGroup的LayoutMode,它有兩個值,一個是默認(rèn)值clipBounds,效果就是保留子view之間的空白,因?yàn)橛行┛丶瓷先ヒ葘?shí)際的小,但它仍然是占了給定的大小,只是系統(tǒng)讓它的一部分邊緣變成留白,這樣的話,不至于子view真的是連接在一起;另一個是opticalBounds,它就是用來消除clipBounds的效果。一般情況下,都不會進(jìn)入判斷語句塊里。
而這里要關(guān)注的其實(shí)是最后一句代碼,setMeasuredDimensionRaw。
4、setMeasuredDimensionRaw
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
這個方法就是將最終的測量結(jié)果賦值給對應(yīng)的view的全局變量,意味著measure部分結(jié)束。
三、對比原生VideoView的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
// + MeasureSpec.toString(heightMeasureSpec) + ")");
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth > 0 && mVideoHeight > 0) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
// the size is fixed
width = widthSpecSize;
height = heightSpecSize;
// for compatibility, we adjust size based on aspect ratio
if ( mVideoWidth * height < width * mVideoHeight ) {
//Log.i("@@@", "image too wide, correcting");
width = height * mVideoWidth / mVideoHeight;
} else if ( mVideoWidth * height > width * mVideoHeight ) {
//Log.i("@@@", "image too tall, correcting");
height = width * mVideoHeight / mVideoWidth;
}
} else if (widthSpecMode == MeasureSpec.EXACTLY) {
......
} else if (heightSpecMode == MeasureSpec.EXACTLY) {
......
} else {
......
}
} else {
// no size yet, just adopt the given spec sizes
}
setMeasuredDimension(width, height);
}
為了方便對比,再貼出onMeasure方法。我在這個方法中,打印過width和height的值,它們的值就是屏幕顯示部分的分辨率。意思是說,按這里的情況來講,當(dāng)狀態(tài)欄和底部的導(dǎo)航欄都是透明時,width是1080,height是1920,正好是Google Nexus 5的分辨率。
當(dāng)?shù)撞康膶?dǎo)航欄不是透明時,height就是1776。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
}
現(xiàn)在對比原生的onMeasure方法來分析。
首先是通過getDefaultSize來得到width和height。上面說過,在我這個例子中,getDefaultSize的返回值只與第二個參數(shù)有關(guān),即widthMeasureSpec和heightMeasureSpec,而這兩個參數(shù)都是從相同的ViewGroup傳進(jìn)來的,所以無論是原生還是重寫,其從getDefaultSize中得到的值都是一樣的。然后進(jìn)入第一層判斷語句塊,在這里通過MeasureSpec.getMode()和getSize(),再次取得控件的mode和size。其實(shí)這在getDefaultSize里也有實(shí)現(xiàn),所以外層的width和widthSpecSize的值是相同的,height也是這種情況。
根據(jù)之前的說明,可以知道進(jìn)入的是第一個判斷語句塊,而其它情況也被我省略了。
再到下面的判斷語句,比較乘積之后,就修改width或height,對比重寫的方法可以判斷,導(dǎo)致效果不同的地方就是這里。代碼的邏輯很清晰簡單。這里直接取具體值來分析。這里的視頻資源的幀寬度是608,幀高度是1080。用來測試的Google Nexus 5是1080×1920。
mVideoWidth * height = 608 × 1920 = 1,167,360,mVideoHeight * width= 1080 × 1080 = 1,166,400,所以修改的是height,等于1,918.4。所以開頭說不讓底部的導(dǎo)航欄變透明,因?yàn)橹徊顑蓚€像素左右,截圖看不清。而當(dāng)?shù)撞繉?dǎo)航欄不是透明的時候,height是1776。這時候修改的就是width,等于999.8,所以如上面的截圖,差別就比較明顯了。這么看來,這部分代碼就是把VideoView的寬或高給修改了,因?yàn)槲沂侵付╩atch_parent的,也就應(yīng)該是屏幕顯示部分的大小。而重寫的方法就是跳過了這部分,讓VideoView的寬高仍然是match_parent。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)多級樹形菜單并支持多選功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多級樹形菜單并支持多選功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07
Android 調(diào)用系統(tǒng)應(yīng)用的方法總結(jié)
這篇文章主要介紹了Android 調(diào)用系統(tǒng)應(yīng)用的方法總結(jié)的相關(guān)資料,這里提供調(diào)用錄像,錄音,拍照等功能,需要的朋友可以參考下2017-08-08
RecyclerView優(yōu)雅實(shí)現(xiàn)復(fù)雜列表布局
這篇文章主要為大家詳細(xì)介紹了RecyclerView優(yōu)雅實(shí)現(xiàn)復(fù)雜列表布局,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-11-11
Android 中ScrollView嵌套GridView,ListView的實(shí)例
這篇文章主要介紹了Android 中ScrollView嵌套GridView,ListView的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-03-03
淺析Android中常見三種彈框在項(xiàng)目中的應(yīng)用
這篇文章主要介紹了淺析Android中常見三種彈框在項(xiàng)目中的應(yīng)用,需要的朋友可以參考下2017-03-03
TabLayout關(guān)聯(lián)ViewPager后不顯示文字的解決方法
這篇文章主要為大家詳細(xì)介紹了TabLayout關(guān)聯(lián)ViewPager后不顯示文字的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11

