ANDROID视频引导滑动黑屏扫雷以及解决方案

首发自: ANDROID视频引导滑动黑屏扫雷以及解决方案

前一段时间,公司项目需要做一个视频引导的功能,刚开始以为用个 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); 避免按返回键,对话框消失掉。
不完整代码如下:

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—

    原文作者:cfanr
    原文地址: https://www.jianshu.com/p/4af68e412bbb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞