用原生VideoView進(jìn)行全屏播放時(shí)的問(wèn)題
之前參加了一個(gè)課程,里面有一節(jié)講到了用視頻作為啟動(dòng)界面。講師用的是自定義VideoView,重寫(xiě)onMeasure方法,因?yàn)樵腣ideoView在那情況下不能實(shí)現(xiàn)全屏播放。當(dāng)時(shí)沒(méi)有深入研究,現(xiàn)在補(bǔ)回來(lái)。
用的是36氪之前的視頻(608×1080)和Genymotion中的Google Nexus 5(1080×1920)。
一、效果圖
1、原生VideoView的效果,這里沒(méi)有讓底部的導(dǎo)航欄也變透明。因?yàn)榻貓D上來(lái)很難看到差別,后面會(huì)解釋。
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代碼,只重寫(xiě)了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); } }
二、在對(duì)比原生VideoView的onMeasure方法之前,先了解一些事情。
1、這里涉及到MeasureSpec類(lèi),這個(gè)類(lèi)代碼不多,但很精妙。我也有很多地方?jīng)]弄懂。不過(guò)在這里,只需了解它的三種mode就可以了。
/** * 1、UNSPECIFIED * 根據(jù)源碼的注釋,其大概意思是parent不對(duì)child做出限制.它想要什么size就給什么size.看了一些教程,都說(shuō)用得很少,或者是系統(tǒng)內(nèi)部才用得上.所以先不管了 * 2、EXACTLY * 對(duì)應(yīng)于match_parent和給出具體的數(shù)值 * 3、AT_MOST * 對(duì)應(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這個(gè)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,也就是第一個(gè)參數(shù)無(wú)關(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); }
中間的判斷語(yǔ)句,涉及到ViewGroup的LayoutMode,它有兩個(gè)值,一個(gè)是默認(rèn)值clipBounds,效果就是保留子view之間的空白,因?yàn)橛行┛丶瓷先ヒ葘?shí)際的小,但它仍然是占了給定的大小,只是系統(tǒng)讓它的一部分邊緣變成留白,這樣的話,不至于子view真的是連接在一起;另一個(gè)是opticalBounds,它就是用來(lái)消除clipBounds的效果。一般情況下,都不會(huì)進(jìn)入判斷語(yǔ)句塊里。
而這里要關(guān)注的其實(shí)是最后一句代碼,setMeasuredDimensionRaw。
4、setMeasuredDimensionRaw
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
這個(gè)方法就是將最終的測(cè)量結(jié)果賦值給對(duì)應(yīng)的view的全局變量,意味著measure部分結(jié)束。
三、對(duì)比原生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); }
為了方便對(duì)比,再貼出onMeasure方法。我在這個(gè)方法中,打印過(guò)width和height的值,它們的值就是屏幕顯示部分的分辨率。意思是說(shuō),按這里的情況來(lái)講,當(dāng)狀態(tài)欄和底部的導(dǎo)航欄都是透明時(shí),width是1080,height是1920,正好是Google Nexus 5的分辨率。
當(dāng)?shù)撞康膶?dǎo)航欄不是透明時(shí),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)在對(duì)比原生的onMeasure方法來(lái)分析。
首先是通過(guò)getDefaultSize來(lái)得到width和height。上面說(shuō)過(guò),在我這個(gè)例子中,getDefaultSize的返回值只與第二個(gè)參數(shù)有關(guān),即widthMeasureSpec和heightMeasureSpec,而這兩個(gè)參數(shù)都是從相同的ViewGroup傳進(jìn)來(lái)的,所以無(wú)論是原生還是重寫(xiě),其從getDefaultSize中得到的值都是一樣的。然后進(jìn)入第一層判斷語(yǔ)句塊,在這里通過(guò)MeasureSpec.getMode()和getSize(),再次取得控件的mode和size。其實(shí)這在getDefaultSize里也有實(shí)現(xiàn),所以外層的width和widthSpecSize的值是相同的,height也是這種情況。
根據(jù)之前的說(shuō)明,可以知道進(jìn)入的是第一個(gè)判斷語(yǔ)句塊,而其它情況也被我省略了。
再到下面的判斷語(yǔ)句,比較乘積之后,就修改width或height,對(duì)比重寫(xiě)的方法可以判斷,導(dǎo)致效果不同的地方就是這里。代碼的邏輯很清晰簡(jiǎn)單。這里直接取具體值來(lái)分析。這里的視頻資源的幀寬度是608,幀高度是1080。用來(lái)測(cè)試的Google Nexus 5是1080×1920。
mVideoWidth * height = 608 × 1920 = 1,167,360,mVideoHeight * width= 1080 × 1080 = 1,166,400,所以修改的是height,等于1,918.4。所以開(kāi)頭說(shuō)不讓底部的導(dǎo)航欄變透明,因?yàn)橹徊顑蓚€(gè)像素左右,截圖看不清。而當(dāng)?shù)撞繉?dǎo)航欄不是透明的時(shí)候,height是1776。這時(shí)候修改的就是width,等于999.8,所以如上面的截圖,差別就比較明顯了。這么看來(lái),這部分代碼就是把VideoView的寬或高給修改了,因?yàn)槲沂侵付╩atch_parent的,也就應(yīng)該是屏幕顯示部分的大小。而重寫(xiě)的方法就是跳過(guò)了這部分,讓VideoView的寬高仍然是match_parent。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
Flutter 構(gòu)建一個(gè)常用的頁(yè)面框架
大多數(shù) App 中都會(huì)有底部導(dǎo)航欄,通過(guò)底部導(dǎo)航欄切換實(shí)現(xiàn)不同頁(yè)面之間的切換。在Flutter 中提供了 BottomNavigationBar組件實(shí)現(xiàn)底部導(dǎo)航。本篇介紹通過(guò) BottomNavigationBar和 IndexedStack構(gòu)建最為常見(jiàn)的 App 頁(yè)面框架。2021-05-05Android的OkHttp包中的HTTP攔截器Interceptor用法示例
攔截器是OkHttp處理HTTP請(qǐng)求方面所具有的一個(gè)強(qiáng)大特性,這里我們就來(lái)看一下Android的OkHttp包中的HTTP攔截器Interceptor用法示例,需要的朋友可以參考下2016-07-07Android實(shí)現(xiàn)計(jì)步進(jìn)度的環(huán)形Progress
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)計(jì)步進(jìn)度的環(huán)形Progress,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android中實(shí)現(xiàn)多行、水平滾動(dòng)的分頁(yè)的Gridview實(shí)例源碼
如果單行水平滾動(dòng),可以用Horizontalscrollview實(shí)現(xiàn)。如果是多行水平滾動(dòng),則結(jié)合Gridview(一般是垂直滾動(dòng)的)和Horizontalscrollview實(shí)現(xiàn)2013-06-06Android入門(mén)之Style與Theme用法實(shí)例解析
這篇文章主要介紹了Android入門(mén)之Style與Theme用法,非常實(shí)用的功能,需要的朋友可以參考下2014-08-08Android應(yīng)用開(kāi)發(fā)中Fragment與Activity間通信示例講解
這篇文章主要介紹了Android應(yīng)用開(kāi)發(fā)中Fragment與Activity間通信實(shí)例講解,需要的朋友可以參考下2016-02-02Flutter實(shí)現(xiàn)固定header底部滑動(dòng)頁(yè)效果示例
這篇文章主要為大家介紹了Flutter實(shí)現(xiàn)固定header底部滑動(dòng)頁(yè)效果示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12安卓輸入框被虛擬鍵盤(pán)擋住的問(wèn)題(微信開(kāi)發(fā))
這篇文章主要介紹了安卓輸入框被虛擬鍵盤(pán)擋住的問(wèn)題(微信開(kāi)發(fā))的相關(guān)資料,需要的朋友可以參考下2016-04-04Gradle配置教程之自定義APK名稱(chēng)與輸出路徑
Gradle是一個(gè)基于JVM的富有突破性構(gòu)建工具,下面這篇文章主要給大家介紹了關(guān)于Gradle配置教程之自定義APK名稱(chēng)與輸出路徑的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03