项目开发中遇到要在广告屏上显示广告视频切换的功能,由于是系统应用开发这和常规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的缓存会导致视频未显示就开始播放了