系統級應用開發實現視頻圖片輪播

項目開發中遇到要在廣告屏上顯示廣告視頻切換的功能,由于是系統應用開發這和常規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);
    }
}

很簡單,就實現了一些初始化,然后在布局里是這樣的

<?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有了,我們可以準備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()是切換數據用的,后面有提到,好了我們來看看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,初始化數據,HttpProxyCacheServer是用的一個視頻緩存庫[AndroidVideoCache],initView我們主要完成的東西,先看看setDataList()

public void setDataList(List<String> 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<String> 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的緩存會導致視頻未顯示就開始播放了

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,613評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 1. 《歡樂頌》熱,雖已退潮,但劇里的五美卻深入人心,最讓人心疼的莫過于樊勝美,最讓...
    隱塵非閱讀 848評論 4 1
  • 每天都在買買買! 網上買,逛街買,旅游買,下鄉淘……我永遠都在無休止的買東西,唉! 攝影的坑到底有多大,真的是跌進...
    牧田麻麻閱讀 143評論 0 0