前一段时间,公司项目需要做一个视频引导的功能,刚开始以为用个 ViewPager+Fragment+VideoView 不就实现了吗,很快就弄好了。不过后来测试发现在滑动切换页面时会出现黑屏,比较影响用户体验,然后在网上找了各种“可行”的方案,都未能完全解决,最后尝试了一种巧妙的方法才解决这个问题。

首先说明下,这里视频引导用到的技术点是 ViewPager+Fragment+VideoView(当然也使用过 SurfaceView 来实现,不过原理基本一致),产品提供四个单独的视频(不是一个视频)+ 引导的圆点和进入主页的按钮(不是直接添加在视频上的)。另外限制条件是,产品未提供每个视频的第一帧的图片。

解决滑动切换页面黑屏的问题

出现黑屏的解释:videoview加载资源需要一定的耗时,无内容时会绘制黑色背景。

1.用遮罩方式掩盖黑屏

用第一帧的图片作为 videoview 的遮罩,当视频加载好,再隐藏掉这个遮罩。以下例子并不能完全解决黑屏:

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); 避免按返回键,对话框消失掉。
不完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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);
}
}

对话框主题

1
2
3
4
5
6
7
8
<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>

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
//……
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()前加以下两行代码避免黑屏

1
2
mVideoView.setZOrderOnTop(true);
mVideoView.getHolder().setFormat(PixelFormat.TRANSLUCENT);

其他代码略

注:以上是针对公司项目有限的条件下的测试结果,并不保证其他项目也一样(代码调用位置和使用方法不同,可能效果不一样),只是提供一些方案和想法。

另外,未尝试的方法:
1.只用一个 VideoView,切换 ViewPager 只是变化圆点和 VideoView 的 url,避免切换 VideoView 的黑屏;参照 仿虾米音乐引导页面 - Kevin Blog CSDN.NET
2.使用视频缩略图解决视频黑屏,参照 Android之ViewPager+VideoView引导界面 - 博客频道 - CSDN.NET

1
2
3
4
5
6
7
8
9
10
获取视频缩略图
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—