項目開發(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)致視頻未顯示就開始播放了