系統(tǒng)級應(yīng)用開發(fā)實現(xiàn)視頻圖片輪播

項目開發(fā)中遇到要在廣告屏上顯示廣告視頻切換的功能蚓峦,由于是系統(tǒng)應(yīng)用開發(fā)這和常規(guī)App開發(fā)不一樣卵迂,涉及到焦點(diǎn)搶占,需要自定義輪播和視頻播放器请垛。因此需要自己自定義的邏輯催训,為了方便使用我們先自定義一個view.

public class Banner extends RelativeLayout
{
    private ViewPager viewPager;
    private final int UPTATE_VIEWPAGER = 100;
    //圖片默認(rèn)時間間隔
    private int imgDelyed = 2000;
    //每個位置默認(rèn)時間間隔,因為有視頻的原因
    private int delyedTime = 2000;
    //默認(rèn)顯示位置宗收,為實現(xiàn)無限輪播
    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);
    }
}

很簡單漫拭,就實現(xiàn)了一些初始化,然后在布局里是這樣的

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.test.net.Banner
        android:id="@+id/banner"
        android:layout_width="match_parent"
        android:layout_height="200dp"></com.test.net.Banner>

</RelativeLayout>

引用我們自定義的view混稽,viewpage有了采驻,我們可以準(zhǔn)備PagerAdapter

public class BannerViewAdapter extends PagerAdapter
{
    private List<View> listBean;

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

    public void setDataList(List<View> 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()是切換數(shù)據(jù)用的匈勋,后面有提到礼旅,好了我們來看看Activity

public class BannerActivity extends AppCompatActivity
{

    private Banner banner;
    private List<String> 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,初始化數(shù)據(jù)洽洁,HttpProxyCacheServer是用的一個視頻緩存庫[AndroidVideoCache]痘系,initView我們主要完成的東西,先看看setDataList()

public void setDataList(List<String> dataList){
        if (dataList == null){
            dataList = new ArrayList<>();
        }
        //用于顯示的數(shù)組
        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();
        //數(shù)據(jù)大于一條饿自,才可以循環(huán)
        if (dataList.size() > 1)
        {
            autoCurrIndex = 1;
            //循環(huán)數(shù)組汰翠,將首位各加一條數(shù)據(jù)
            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();
                //監(jiān)聽視頻播放完的代碼
                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苟穆,可以無限循環(huán)
1、采用集合首尾各加一條數(shù)據(jù)來進(jìn)行循環(huán),具體原因略
2雳旅、根據(jù)后綴來判斷是視頻還是圖片跟磨,目前視頻我只判斷MP4的,MimeTypeMap.getFileExtensionFromUrl()是Android自帶的
3攒盈、根據(jù)不同的類型創(chuàng)建不同的View抵拘,加入集合,MVideoView 是為了處理視頻不全屏做的處理型豁,圖片用的Glide
二僵蛛、等于1,不能無限循環(huán)
如果是視頻迎变,那么這個視頻要可以循環(huán)播放充尉,所以加入播放完成監(jiān)聽,圖片不變

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

設(shè)置圖片播放間隔

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);
                //當(dāng)前位置
                autoCurrIndex = position;
                getDelayedTime(position);
            }

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

                //移除自動計時
                mHandler.removeCallbacks(runnable);

                //ViewPager跳轉(zhuǎn)
                int pageIndex = autoCurrIndex;
                if(autoCurrIndex == 0){
                    pageIndex = views.size()-2;
                }else if(autoCurrIndex == views.size() - 1){
                    pageIndex = 1;
                }
                if (pageIndex != autoCurrIndex) {
                    //無滑動動畫衣形,直接跳轉(zhuǎn)
                    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);
                }
            }
        });
    }

重中之重句狼,我們一點(diǎn)點(diǎn)來說笋熬,各種初始化我就不解釋了,viewPager.setCurrentItem(autoCurrIndex);鲜锚,初始化顯示的位置突诬,數(shù)據(jù)大于1時,初始化顯示1的位置芜繁,因為0的位置是假數(shù)據(jù)旺隙,數(shù)據(jù)等于1的時候,初始化顯示0的位置骏令,然后添加頁面切換監(jiān)聽蔬捷。
onPageSelected:記錄當(dāng)前位置,而且如果是視頻榔袋,就進(jìn)行播放周拐,具體代碼后面提到
onPageScrollStateChanged:移除自動輪播,且判斷當(dāng)前頁面是否是臨界凰兑,并進(jìn)行跳轉(zhuǎn)妥粟,然后根據(jù)條件判斷是否進(jìn)行自動輪播,這地方有一點(diǎn)就是在獲取視頻的總長和已播放長度時吏够,有時會失敗勾给,可能是因為為視頻還未真正加載滩报,所以我做了一個判斷,當(dāng)小于0的時候播急,用handler來做一個延時任務(wù)脓钾,延時時間是圖片輪播時間,延時結(jié)束后再次獲取視頻的總長和已播放長度桩警,唯一問題就是視頻可能很短可训,暫時沒想到其他解決方法。那么time捶枢,和runnable是這樣的

/**
     * 發(fā)消息握截,進(jìn)行循環(huán)
     */
    private Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {
            mHandler.sendEmptyMessage(UPTATE_VIEWPAGER);
        }
    };

    /**
     * 這個類,恩柱蟀,獲取視頻長度川蒙,以及已經(jīng)播放的時間
     */
    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 當(dāng)前位置
     */
    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;
        }
    }

獲取某個位置的輪播時間伶椿,又出現(xiàn)了time的身影辜伟,本不應(yīng)該出現(xiàn)的,這個方法是為其他地方服務(wù)的

//開啟自動循環(huán)
    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就可以運(yùn)行了

//接受消息實現(xiàn)輪播
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPTATE_VIEWPAGER:
                    viewPager.setCurrentItem(autoCurrIndex+1);
                    break;
            }
        }
    };

關(guān)于這個autoCurrIndex為什么沒有越界處理枚赡,而不擔(dān)心越界,這個主要在onPageScrollStateChanged里面
1谓谦、假如數(shù)組長度為3贫橙,經(jīng)過處理會變成5,記住0和4反粥,是偽數(shù)據(jù)卢肃,為了過度動畫的
2谓松、當(dāng)autoCurrIndex = position = 0的時候 會直接用viewPager.setCurrentItem(pageIndex, false);跳到3的位置,此時autoCurrIndex = position = 3
3践剂、當(dāng)autoCurrIndex = position = 4的時候 會直接用viewPager.setCurrentItem(pageIndex, false);跳到1的位置鬼譬,此時autoCurrIndex = position = 1
具體解釋一下流程就是:當(dāng)頁面開始動的時候onPageScrollStateChanged –>state = 1,然后onPageScrollStateChanged –>state = 2,然后執(zhí)行onPageSelected(),改變autoCurrIndex 逊脯,然后onPageScrollStateChanged –>state = 0优质。也就是說autoCurrIndex 在變成0或4的時候會立刻執(zhí)行onPageScrollStateChanged –>state = 0,判斷后執(zhí)行viewPager.setCurrentItem(pageIndex, false);跳到其他位置军洼,以上流程又重新走一遍巩螃,但autoCurrIndex 已經(jīng)不為0或4了。所以0或4的存在非常短匕争,也就不會產(chǎn)生影響避乏。

public void dataChange(List<String> list){
        if (list != null && list.size()>0)
        {
            //改變資源時要重新開啟循環(huán),否則會把視頻的時長賦給圖片甘桑,或者相反
            //因為delyedTime也要改變拍皮,所以要重新獲取delyedTime
            mHandler.removeCallbacks(runnable);
            setDataList(list);
            mAdapter.setDataList(views);
            mAdapter.notifyDataSetChanged();
            viewPager.setCurrentItem(autoCurrIndex,false);
            //開啟循環(huán)
            if (isAutoPlay && views.size() > 1){
                getDelayedTime(autoCurrIndex);
                if (delyedTime <= 0){
                    mHandler.postDelayed(time,imgDelyed);
                }else {
                    mHandler.postDelayed(runnable,delyedTime);
                }
            }
        }
    }

切換數(shù)據(jù)捧搞,沒什么好說的誉己,關(guān)于viewpage,數(shù)據(jù)切換不立刻刷新妓柜,我就不說了肤寝,反正我是重寫了public int getItemPosition(Object object)慨亲,最后資源釋放体捏,反正就是能放什么就放什么就對了.總的來說腐泻,有兩點(diǎn)纵搁,1是獲取視頻時長的問題窄做,2是viewpage的緩存會導(dǎo)致視頻未顯示就開始播放了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愧驱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子椭盏,更是在濱河造成了極大的恐慌组砚,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庸汗,死亡現(xiàn)場離奇詭異惫确,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚯舱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門改化,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人枉昏,你說我怎么就攤上這事陈肛。” “怎么了兄裂?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵句旱,是天一觀的道長阳藻。 經(jīng)常有香客問我,道長谈撒,這世上最難降的妖魔是什么腥泥? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮啃匿,結(jié)果婚禮上蛔外,老公的妹妹穿的比我還像新娘。我一直安慰自己溯乒,他們只是感情好夹厌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裆悄,像睡著了一般矛纹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上光稼,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天或南,我揣著相機(jī)與錄音,去河邊找鬼钟哥。 笑死迎献,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腻贰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼扒秸,長吁一口氣:“原來是場噩夢啊……” “哼播演!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伴奥,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤写烤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拾徙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洲炊,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年尼啡,在試婚紗的時候發(fā)現(xiàn)自己被綠了暂衡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡崖瞭,死狀恐怖狂巢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情书聚,我是刑警寧澤唧领,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布藻雌,位于F島的核電站,受9級特大地震影響斩个,放射性物質(zhì)發(fā)生泄漏胯杭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一受啥、第九天 我趴在偏房一處隱蔽的房頂上張望歉摧。 院中可真熱鬧,春花似錦腔呜、人聲如沸叁温。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膝但。三九已至,卻和暖如春谤草,著一層夾襖步出監(jiān)牢的瞬間跟束,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工丑孩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冀宴,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓温学,卻偏偏與公主長得像略贮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仗岖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,504評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫逃延、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,022評論 4 62
  • 1. 《歡樂頌》熱,雖已退潮檩电,但劇里的五美卻深入人心拄丰,最讓人心疼的莫過于樊勝美,最讓...
    隱塵非閱讀 764評論 4 1
  • 每天都在買買買俐末! 網(wǎng)上買料按,逛街買,旅游買鹅搪,下鄉(xiāng)淘……我永遠(yuǎn)都在無休止的買東西站绪,唉! 攝影的坑到底有多大丽柿,真的是跌進(jìn)...
    牧田麻麻閱讀 137評論 0 0