前一段時間,公司項目需要做一個視頻引導的功能,剛開始以為用個 ViewPager+Fragment+VideoView 不就實現了嗎,很快就弄好了。不過后來測試發現在滑動切換頁面時會出現黑屏,比較影響用戶體驗,然后在網上找了各種“可行”的方案,都未能完全解決,最后嘗試了一種巧妙的方法才解決這個問題。
首先說明下,這里視頻引導用到的技術點是 ViewPager+Fragment+VideoView(當然也使用過 SurfaceView 來實現,不過原理基本一致),產品提供四個單獨的視頻(不是一個視頻)+ 引導的圓點和進入主頁的按鈕(不是直接添加在視頻上的)。另外限制條件是,產品未提供每個視頻的第一幀的圖片。
解決滑動切換頁面黑屏的問題
出現黑屏的解釋:videoview加載資源需要一定的耗時,無內容時會繪制黑色背景。
1.用遮罩方式掩蓋黑屏
用第一幀的圖片作為 videoview 的遮罩,當視頻加載好,再隱藏掉這個遮罩。以下例子并不能完全解決黑屏:
- a. Android VideoView black screen - Stack Overflow
可以看到評論,滑動還是看會有閃爍,而且視頻第一幀不一定會在 onPrepared() 被調用后出現,此方案并不可行,但網上最多的就是這種方案 - b. 手把手教你炫酷慕課網視頻啟動導航的完美實現 - Losileeya- CSDN.NET
這是一種折中的遮罩方法,就是遮罩的圖片是帶有文字的透明背景圖片,切換效果還是有輕微的黑屏,不過不是特別明顯,尚可以接受;但由于視頻未能提供這樣的素材,無法實現;網上也不少解決方案是采取這種方法。
2.用PageTransformer設置滑動時切換的動畫
當頁面比較多時,快速滑動切換,ViewPager 會閃一下,可以添加切換動畫作為緩沖。
了解自定義 PageTransformer 動畫可以看下這個庫: GitHub - ToxicBakery/ViewPagerTransforms: Library containing common animations needed for transforming ViewPager scrolling for Android v13+.
3.在每個 page 頁增加一個寬高都為0的 SurfaceView
無效
4.入坑:使用videoView.setZOrderOnTop(true)避免黑屏
在視頻加載前設置一張圖片作為過渡圖片,之后調用videoView.setZOrderOnTop(true),確實可以解決滑動黑屏問題,不過調用了該方法,會使其他控件被 VideoView 覆蓋。前面的幾種方案由于條件限制效果都不是很好,這種方法基本看不到黑屏,但卻出現了另一個問題:如何將圓點和按鈕置于 VideoView 上面?
解決調用videoView.setZOrderOnTop(true),其他控件被覆蓋的問題
由于 VideoView 是繼承 SurfaceView 的,也查了相關解決方案,遇到不少坑
坑1:
解決SurfaceView調用setZOrderOnTop(true)遮擋其他控件的問題
調用setZOrderOnTop(true)之后調用了setZOrderMediaOverlay(true)再設置控件顯示,解決遮擋問題,但是又出現了黑屏問題,也就是說調用setZOrderMediaOverlay(true)會使前面設置的setZOrderOnTop(true)失效
坑2:
解決SurfaceView設置透明造成覆蓋其他組件的替代方案 - jwzhangjie的專欄CSDN.NET
里面提到的兩種在 SurfaceView設置了setZOrderOnTop(true)后,添加其他組件的方法:使用 PopupWindow 作為容器承載其他控件,考慮到setZOrderOnTop(true)能覆蓋其他控件,所以也嘗試了用SurfaceView 繪制圓點和按鈕(在 videoview調用setZOrderOnTop(true) 后調用自身的setZOrderOnTop(true)覆蓋在上面)。在我的實踐中,
a.用 PopupWindow 作為容器,大部分手機可以使圓點和按鈕置于上面,但小米手機第一屏不行,home 鍵后也會圓點也會被覆蓋掉;
b.用 SurfaceView 作為容器小米手機正常了,其他手機異常,圓點和按鈕不能顯示在 VideoView 上面。。。
以上兩種方法部分手機異常都找不到具體原因。
解決方案
最終使用了Dialog 作為圓點和按鈕的容器才解決控件被覆蓋的問題。不過 Dialog 會使 ViewPager 的滑動失效,需要重寫 Dialog 的 onTouch 事件,將 TouchEvent 傳遞給 ViewPager 處理,同時要設置Dialog.setCancelable(false); 避免按返回鍵,對話框消失掉。
不完整代碼如下:
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);
}
}
對話框主題
<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();
在調用 VideoView.start()前加以下兩行代碼避免黑屏
mVideoView.setZOrderOnTop(true);
mVideoView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
其他代碼略
注:以上是針對公司項目有限的條件下的測試結果,并不保證其他項目也一樣(代碼調用位置和使用方法不同,可能效果不一樣),只是提供一些方案和想法。
另外,未嘗試的方法:
1.只用一個 VideoView,切換 ViewPager 只是變化圓點和 VideoView 的 url,避免切換 VideoView 的黑屏;參照 仿蝦米音樂引導頁面 - Kevin Blog CSDN.NET
2.使用視頻縮略圖解決視頻黑屏,參照 Android之ViewPager+VideoView引導界面 - 博客頻道 - CSDN.NET
獲取視頻縮略圖
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(this, mUri);
mImageView.setImageBitmap(mmr.getFrameAtTime());
添加ViewPager 滑動監聽
ViewPager.addOnPageChangeListener,
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
在這個函數里處理縮略圖的顯示
public void onPageScrollStateChanged(int state) state==0 時視圖準備好了
在這個函數里處理縮略圖的消失
—EOF—