首發(fā)自: ANDROID視頻引導(dǎo)滑動(dòng)黑屏掃雷以及解決方案
前一段時(shí)間,公司項(xiàng)目需要做一個(gè)視頻引導(dǎo)的功能聊品,剛開始以為用個(gè) ViewPager+Fragment+VideoView 不就實(shí)現(xiàn)了嗎赐俗,很快就弄好了。不過后來測(cè)試發(fā)現(xiàn)在滑動(dòng)切換頁面時(shí)會(huì)出現(xiàn)黑屏秆乳,比較影響用戶體驗(yàn),然后在網(wǎng)上找了各種“可行”的方案,都未能完全解決屹堰,最后嘗試了一種巧妙的方法才解決這個(gè)問題肛冶。
首先說明下,這里視頻引導(dǎo)用到的技術(shù)點(diǎn)是 ViewPager+Fragment+VideoView(當(dāng)然也使用過 SurfaceView 來實(shí)現(xiàn)扯键,不過原理基本一致)睦袖,產(chǎn)品提供四個(gè)單獨(dú)的視頻(不是一個(gè)視頻)+ 引導(dǎo)的圓點(diǎn)和進(jìn)入主頁的按鈕(不是直接添加在視頻上的)。另外限制條件是荣刑,產(chǎn)品未提供每個(gè)視頻的第一幀的圖片馅笙。
解決滑動(dòng)切換頁面黑屏的問題
出現(xiàn)黑屏的解釋:videoview加載資源需要一定的耗時(shí),無內(nèi)容時(shí)會(huì)繪制黑色背景厉亏。
1.用遮罩方式掩蓋黑屏
用第一幀的圖片作為 videoview 的遮罩董习,當(dāng)視頻加載好,再隱藏掉這個(gè)遮罩爱只。以下例子并不能完全解決黑屏:
- a. Android VideoView black screen - Stack Overflow
可以看到評(píng)論皿淋,滑動(dòng)還是看會(huì)有閃爍,而且視頻第一幀不一定會(huì)在 onPrepared() 被調(diào)用后出現(xiàn)虱颗,此方案并不可行沥匈,但網(wǎng)上最多的就是這種方案 - b. 手把手教你炫酷慕課網(wǎng)視頻啟動(dòng)導(dǎo)航的完美實(shí)現(xiàn) - Losileeya- CSDN.NET
這是一種折中的遮罩方法,就是遮罩的圖片是帶有文字的透明背景圖片忘渔,切換效果還是有輕微的黑屏高帖,不過不是特別明顯,尚可以接受畦粮;但由于視頻未能提供這樣的素材散址,無法實(shí)現(xiàn);網(wǎng)上也不少解決方案是采取這種方法宣赔。
2.用PageTransformer設(shè)置滑動(dòng)時(shí)切換的動(dòng)畫
當(dāng)頁面比較多時(shí)预麸,快速滑動(dòng)切換,ViewPager 會(huì)閃一下儒将,可以添加切換動(dòng)畫作為緩沖吏祸。
了解自定義 PageTransformer 動(dòng)畫可以看下這個(gè)庫: GitHub - ToxicBakery/ViewPagerTransforms: Library containing common animations needed for transforming ViewPager scrolling for Android v13+.
3.在每個(gè) page 頁增加一個(gè)寬高都為0的 SurfaceView
無效
4.入坑:使用videoView.setZOrderOnTop(true)避免黑屏
在視頻加載前設(shè)置一張圖片作為過渡圖片,之后調(diào)用videoView.setZOrderOnTop(true)钩蚊,確實(shí)可以解決滑動(dòng)黑屏問題贡翘,不過調(diào)用了該方法,會(huì)使其他控件被 VideoView 覆蓋砰逻。前面的幾種方案由于條件限制效果都不是很好鸣驱,這種方法基本看不到黑屏,但卻出現(xiàn)了另一個(gè)問題:如何將圓點(diǎn)和按鈕置于 VideoView 上面蝠咆?
解決調(diào)用videoView.setZOrderOnTop(true)踊东,其他控件被覆蓋的問題
由于 VideoView 是繼承 SurfaceView 的北滥,也查了相關(guān)解決方案,遇到不少坑
坑1:
解決SurfaceView調(diào)用setZOrderOnTop(true)遮擋其他控件的問題
調(diào)用setZOrderOnTop(true)之后調(diào)用了setZOrderMediaOverlay(true)再設(shè)置控件顯示闸翅,解決遮擋問題再芋,但是又出現(xiàn)了黑屏問題,也就是說調(diào)用setZOrderMediaOverlay(true)會(huì)使前面設(shè)置的setZOrderOnTop(true)失效
坑2:
解決SurfaceView設(shè)置透明造成覆蓋其他組件的替代方案 - jwzhangjie的專欄CSDN.NET
里面提到的兩種在 SurfaceView設(shè)置了setZOrderOnTop(true)后缎脾,添加其他組件的方法:使用 PopupWindow 作為容器承載其他控件祝闻,考慮到setZOrderOnTop(true)能覆蓋其他控件占卧,所以也嘗試了用SurfaceView 繪制圓點(diǎn)和按鈕(在 videoview調(diào)用setZOrderOnTop(true) 后調(diào)用自身的setZOrderOnTop(true)覆蓋在上面)遗菠。在我的實(shí)踐中,
a.用 PopupWindow 作為容器华蜒,大部分手機(jī)可以使圓點(diǎn)和按鈕置于上面辙纬,但小米手機(jī)第一屏不行,home 鍵后也會(huì)圓點(diǎn)也會(huì)被覆蓋掉;
b.用 SurfaceView 作為容器小米手機(jī)正常了叭喜,其他手機(jī)異常贺拣,圓點(diǎn)和按鈕不能顯示在 VideoView 上面。捂蕴。譬涡。
以上兩種方法部分手機(jī)異常都找不到具體原因。
解決方案
最終使用了Dialog 作為圓點(diǎn)和按鈕的容器才解決控件被覆蓋的問題啥辨。不過 Dialog 會(huì)使 ViewPager 的滑動(dòng)失效涡匀,需要重寫 Dialog 的 onTouch 事件,將 TouchEvent 傳遞給 ViewPager 處理溉知,同時(shí)要設(shè)置Dialog.setCancelable(false); 避免按返回鍵陨瘩,對(duì)話框消失掉。
不完整代碼如下:
public class ContainerDialog extends Dialog {
private OnTouchOutsideListener onTouchOutsideListener;
public ContainerDialog(Context context, int theme) {
super(context, theme);
}
public void setOnTouchOutsideListener(OnTouchOutsideListener onTouchOutsideListener){
this.onTouchOutsideListener = onTouchOutsideListener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(onTouchOutsideListener!=null){
return onTouchOutsideListener.onTouchOutside(event);
}
return super.onTouchEvent(event);
}
@Override
public void show() {
super.show();
Window window = this.getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
window.setGravity(Gravity.BOTTOM);
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(layoutParams);
window.setBackgroundDrawableResource(android.R.color.transparent);
}
public interface OnTouchOutsideListener{
boolean onTouchOutside(MotionEvent event);
}
}
對(duì)話框主題
<style name="FeatureDialogTheme" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
使用方法
//……
ContainerDialog mDialog = new ContainerDialog(this, R.style.FeatureDialogTheme);
mDialog.setContentView(dotsAndBtnView);
mDialog.setCancelable(false);
mDialog.setOnTouchOutsideListener(new ContainerDialog.OnTouchOutsideListener() {
@Override
public boolean onTouchOutside(MotionEvent event) {
mViewPager.onTouchEvent(event);
return true;
}
});
mDialog.show();
在調(diào)用 VideoView.start()前加以下兩行代碼避免黑屏
mVideoView.setZOrderOnTop(true);
mVideoView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
其他代碼略
注:以上是針對(duì)公司項(xiàng)目有限的條件下的測(cè)試結(jié)果级乍,并不保證其他項(xiàng)目也一樣(代碼調(diào)用位置和使用方法不同舌劳,可能效果不一樣),只是提供一些方案和想法玫荣。
另外甚淡,未嘗試的方法:
1.只用一個(gè) VideoView,切換 ViewPager 只是變化圓點(diǎn)和 VideoView 的 url捅厂,避免切換 VideoView 的黑屏贯卦;參照 仿蝦米音樂引導(dǎo)頁面 - Kevin Blog CSDN.NET
2.使用視頻縮略圖解決視頻黑屏,參照 Android之ViewPager+VideoView引導(dǎo)界面 - 博客頻道 - CSDN.NET
獲取視頻縮略圖
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(this, mUri);
mImageView.setImageBitmap(mmr.getFrameAtTime());
添加ViewPager 滑動(dòng)監(jiān)聽
ViewPager.addOnPageChangeListener,
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
在這個(gè)函數(shù)里處理縮略圖的顯示
public void onPageScrollStateChanged(int state) state==0 時(shí)視圖準(zhǔn)備好了
在這個(gè)函數(shù)里處理縮略圖的消失
—EOF—