系统级应用开发实现视频图片轮播

项目开发中遇到要在广告屏上显示广告视频切换的功能,由于是系统应用开发这和常规App开发不一样,涉及到焦点抢占,需要自定义轮播和视频播放器。因此需要自己自定义的逻辑,为了方便使用我们先自定义一个view.

public class Banner extends RelativeLayout
{
    private ViewPager viewPager;
    private final int UPTATE_VIEWPAGER = 100;
    //图片默认时间间隔
    private int imgDelyed = 2000;
    //每个位置默认时间间隔,因为有视频的原因
    private int delyedTime = 2000;
    //默认显示位置,为实现无限轮播
    private int autoCurrIndex = 1;
    //是否自动播放
    private boolean isAutoPlay = false;

    public Banner(Context context)
    {
        super(context);
        init();
    }

    public Banner(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init();
    }

    public Banner(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public Banner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init()
    {
        viewPager = new ViewPager(getContext());
        LinearLayout.LayoutParams vp_param = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        viewPager.setLayoutParams(vp_param);
        this.addView(viewPager);
    }
}

很简单,就实现了一些初始化,然后在布局里是这样的




    


引用我们自定义的view,viewpage有了,我们可以准备PagerAdapter

public class BannerViewAdapter extends PagerAdapter
{
    private List listBean;

    public BannerViewAdapter(List list){
        if (list == null){
            list = new ArrayList<>();
        }
        this.listBean = list;
    }

    public void setDataList(List list){
        if (list != null && list.size() > 0){
            this.listBean = list;
        }
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position)
    {
        View view = listBean.get(position);
        container.addView(view);
        return view;
    }

    @Override
    public int getItemPosition(Object object)
    {
        return POSITION_NONE;
    }

    @Override
    public int getCount()
    {
        return listBean.size();
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object)
    {
        container.removeView((View) object);
    }

    @Override
    public boolean isViewFromObject(View view, Object object)
    {
        return view == object;
    }

}

没什么特别的,getItemPosition()是切换数据用的,后面有提到,好了我们来看看Activity

public class BannerActivity extends AppCompatActivity
{

    private Banner banner;
    private List list;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_banner);
        banner = (Banner) findViewById(R.id.banner);
        initData();
        initView();
    }

    private void initData(){
        HttpProxyCacheServer proxy = MApplication.getProxy(getApplicationContext());
        String proxyUrl = proxy.getProxyUrl("http://fs.mv.web.kugou.com/201805151034/301b4249052e4f77917f02c1903e3370/G131/M06/0D/00/ww0DAFr5qtqACRUoAh-sVLABkV8377.mp4");
        String proxyUrl2 = proxy.getProxyUrl("http://fs.mv.web.kugou.com/201805151530/498716f6f332829687bbf077e252a083/G133/M05/1F/19/xQ0DAFrwJs6AHgs6AbSCfVQb4IQ631.mp4");

        list = new ArrayList<>();
        list.add(proxyUrl);
        list.add("http://img2.imgtn.bdimg.com/it/u=3817131034,1038857558&fm=27&gp=0.jpg");
        list.add("http://img1.imgtn.bdimg.com/it/u=4194723123,4160931506&fm=200&gp=0.jpg");
        list.add(proxyUrl2);
        list.add("http://img5.imgtn.bdimg.com/it/u=1812408136,1922560783&fm=27&gp=0.jpg");
    }

    private void initView(){
        banner.setDataList(list);
        banner.setImgDelyed(5000);
        banner.autoBanner();
        banner.startAutoPlay();
    }
}

initData,初始化数据,HttpProxyCacheServer是用的一个视频缓存库[AndroidVideoCache],initView我们主要完成的东西,先看看setDataList()

public void setDataList(List dataList){
        if (dataList == null){
            dataList = new ArrayList<>();
        }
        //用于显示的数组
        if (views == null)
        {
            views = new ArrayList<>();
        }else {
            views.clear();
        }

        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        RequestOptions options = new RequestOptions();
        options.centerCrop();
        //数据大于一条,才可以循环
        if (dataList.size() > 1)
        {
            autoCurrIndex = 1;
            //循环数组,将首位各加一条数据
            for (int i = 0; i < dataList.size() + 2; i++)
            {
                String url;
                if (i == 0)
                {
                    url = dataList.get(dataList.size() - 1);
                } else if (i == dataList.size() + 1)
                {
                    url = dataList.get(0);
                } else
                {
                    url = dataList.get(i - 1);
                }

                if (MimeTypeMap.getFileExtensionFromUrl(url).equals("mp4"))
                {
                    MVideoView videoView = new MVideoView(getContext());
                    videoView.setLayoutParams(lp);
                    videoView.setVideoURI(Uri.parse(url));
                    videoView.start();
                    views.add(videoView);
                } else
                {
                    ImageView imageView = new ImageView(getContext());
                    imageView.setLayoutParams(lp);
                    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                    Glide.with(getContext()).load(url).apply(options).into(imageView);
                    views.add(imageView);
                }
            }
        }else if(dataList.size() == 1){
            autoCurrIndex = 0;
            String url = dataList.get(0);
            if (MimeTypeMap.getFileExtensionFromUrl(url).equals("mp4"))
            {
                MVideoView videoView = new MVideoView(getContext());
                videoView.setLayoutParams(lp);
                videoView.setVideoURI(Uri.parse(url));
                videoView.start();
                //监听视频播放完的代码
                videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

                    @Override
                    public void onCompletion(MediaPlayer mPlayer) {
                        mPlayer.start();
                        mPlayer.setLooping(true);
                    }
                });
                views.add(videoView);
            } else
            {
                ImageView imageView = new ImageView(getContext());
                imageView.setLayoutParams(lp);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                Glide.with(getContext()).load(url).apply(options).into(imageView);
                views.add(imageView);
            }
        }
    }

解释一下,先集合初始化,然后判断集合长度,分大于1,和小于1
一、大于1,可以无限循环
1、采用集合首尾各加一条数据来进行循环,具体原因略
2、根据后缀来判断是视频还是图片,目前视频我只判断MP4的,MimeTypeMap.getFileExtensionFromUrl()是Android自带的
3、根据不同的类型创建不同的View,加入集合,MVideoView 是为了处理视频不全屏做的处理,图片用的Glide
二、等于1,不能无限循环
如果是视频,那么这个视频要可以循环播放,所以加入播放完成监听,图片不变

public void setImgDelyed(int imgDelyed){
        this.imgDelyed = imgDelyed;
}

设置图片播放间隔

public void startBanner()
    {
        mAdapter = new BannerViewAdapter(views);
        viewPager.setAdapter(mAdapter);
        viewPager.setOffscreenPageLimit(1);
        viewPager.setCurrentItem(autoCurrIndex);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener()
        {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
            {

            }

            @Override
            public void onPageSelected(int position)
            {
                Log.d("TAG","position:"+position);
                //当前位置
                autoCurrIndex = position;
                getDelayedTime(position);
            }

            @Override
            public void onPageScrollStateChanged(int state)
            {
                Log.d("TAG",""+state);

                //移除自动计时
                mHandler.removeCallbacks(runnable);

                //ViewPager跳转
                int pageIndex = autoCurrIndex;
                if(autoCurrIndex == 0){
                    pageIndex = views.size()-2;
                }else if(autoCurrIndex == views.size() - 1){
                    pageIndex = 1;
                }
                if (pageIndex != autoCurrIndex) {
                    //无滑动动画,直接跳转
                    viewPager.setCurrentItem(pageIndex, false);
                }

                //停止滑动时,重新自动倒计时
                if (state == 0 && isAutoPlay && views.size() > 1){
                    View view1 = views.get(pageIndex);
                    if (view1 instanceof VideoView){
                        final VideoView videoView = (VideoView) view1;
                        int current = videoView.getCurrentPosition();
                        int duration = videoView.getDuration();
                        delyedTime = duration - current;
                        //某些时候,某些视频,获取的时间无效,就延时10秒,重新获取
                        if (delyedTime <= 0){
                            time.getDelyedTime(videoView,runnable);
                            mHandler.postDelayed(time,imgDelyed);
                        }else {
                            mHandler.postDelayed(runnable,delyedTime);
                        }
                    }else {
                        delyedTime = imgDelyed;
                        mHandler.postDelayed(runnable,delyedTime);
                    }
                    Log.d("TAG",""+pageIndex+"--"+autoCurrIndex);
                }
            }
        });
    }

重中之重,我们一点点来说,各种初始化我就不解释了,viewPager.setCurrentItem(autoCurrIndex);,初始化显示的位置,数据大于1时,初始化显示1的位置,因为0的位置是假数据,数据等于1的时候,初始化显示0的位置,然后添加页面切换监听。
onPageSelected:记录当前位置,而且如果是视频,就进行播放,具体代码后面提到
onPageScrollStateChanged:移除自动轮播,且判断当前页面是否是临界,并进行跳转,然后根据条件判断是否进行自动轮播,这地方有一点就是在获取视频的总长和已播放长度时,有时会失败,可能是因为为视频还未真正加载,所以我做了一个判断,当小于0的时候,用handler来做一个延时任务,延时时间是图片轮播时间,延时结束后再次获取视频的总长和已播放长度,唯一问题就是视频可能很短,暂时没想到其他解决方法。那么time,和runnable是这样的

/**
     * 发消息,进行循环
     */
    private Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {
            mHandler.sendEmptyMessage(UPTATE_VIEWPAGER);
        }
    };

    /**
     * 这个类,恩,获取视频长度,以及已经播放的时间
     */
    private class Time implements Runnable{

        private VideoView videoView;
        private Runnable runnable;

        public void getDelyedTime(VideoView videoView,Runnable runnable){
            this.videoView = videoView;
            this.runnable = runnable;
        }
        @Override
        public void run()
        {
            int current = videoView.getCurrentPosition();
            int duration = videoView.getDuration();
            int delyedTime = duration - current;
            mHandler.postDelayed(runnable,delyedTime);
        }
    }

Time本不想这样写,需要初始化对象,而且得赋值,但是在几个地方都有用到,初始化没写,相信都会,如果你不想这样写,可以这样

mHandler.postDelayed(new Runnable() {
                @Override
                public void run()
                {
                    int current = videoView.getCurrentPosition();
                    int duration = videoView.getDuration();
                    int delyedTime = duration - current;
                    mHandler.postDelayed(runnable,delyedTime);
                }
            },imgDelyed);

用new Runnable替换time就好了,具体怎么弄,看喜好

 /**
     * 获取delyedTime
     * @param position 当前位置
     */
    private void getDelayedTime(int position){
        View view1 = views.get(position);
        if (view1 instanceof VideoView){
            VideoView videoView = (VideoView) view1;
            videoView.start();
            videoView.seekTo(0);
            delyedTime = videoView.getDuration();
            time.getDelyedTime(videoView,runnable);
        }else {
            delyedTime = imgDelyed;
        }
    }

获取某个位置的轮播时间,又出现了time的身影,本不应该出现的,这个方法是为其他地方服务的

//开启自动循环
    public void startAutoPlay(){
        isAutoPlay = true;
        if (views.size() > 1){
            getDelayedTime(autoCurrIndex);
            if (delyedTime <= 0){
                mHandler.postDelayed(time,imgDelyed);
            }else {
                mHandler.postDelayed(runnable,delyedTime);
            }
        }
    }

理一下逻辑,getDelayedTime()获取delyedTime ,小于0肯定是视频,而且还没获取到,那么延时,其他就是要么图片,要么视频获取到时间了,那么再加上一个handler就可以运行了

//接受消息实现轮播
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPTATE_VIEWPAGER:
                    viewPager.setCurrentItem(autoCurrIndex+1);
                    break;
            }
        }
    };

关于这个autoCurrIndex为什么没有越界处理,而不担心越界,这个主要在onPageScrollStateChanged里面
1、假如数组长度为3,经过处理会变成5,记住0和4,是伪数据,为了过度动画的
2、当autoCurrIndex = position = 0的时候 会直接用viewPager.setCurrentItem(pageIndex, false);跳到3的位置,此时autoCurrIndex = position = 3
3、当autoCurrIndex = position = 4的时候 会直接用viewPager.setCurrentItem(pageIndex, false);跳到1的位置,此时autoCurrIndex = position = 1
具体解释一下流程就是:当页面开始动的时候onPageScrollStateChanged –>state = 1,然后onPageScrollStateChanged –>state = 2,然后执行onPageSelected(),改变autoCurrIndex ,然后onPageScrollStateChanged –>state = 0。也就是说autoCurrIndex 在变成0或4的时候会立刻执行onPageScrollStateChanged –>state = 0,判断后执行viewPager.setCurrentItem(pageIndex, false);跳到其他位置,以上流程又重新走一遍,但autoCurrIndex 已经不为0或4了。所以0或4的存在非常短,也就不会产生影响。

public void dataChange(List list){
        if (list != null && list.size()>0)
        {
            //改变资源时要重新开启循环,否则会把视频的时长赋给图片,或者相反
            //因为delyedTime也要改变,所以要重新获取delyedTime
            mHandler.removeCallbacks(runnable);
            setDataList(list);
            mAdapter.setDataList(views);
            mAdapter.notifyDataSetChanged();
            viewPager.setCurrentItem(autoCurrIndex,false);
            //开启循环
            if (isAutoPlay && views.size() > 1){
                getDelayedTime(autoCurrIndex);
                if (delyedTime <= 0){
                    mHandler.postDelayed(time,imgDelyed);
                }else {
                    mHandler.postDelayed(runnable,delyedTime);
                }
            }
        }
    }

切换数据,没什么好说的,关于viewpage,数据切换不立刻刷新,我就不说了,反正我是重写了public int getItemPosition(Object object),最后资源释放,反正就是能放什么就放什么就对了.总的来说,有两点,1是获取视频时长的问题,2是viewpage的缓存会导致视频未显示就开始播放了

你可能感兴趣的:(系统级应用开发实现视频图片轮播)