Android 轮播图(Viewpager+Handler定时器)

释放双眼,带上耳机,听听看~!

 

效果图
发现好多人提到banner,第一个想法就是撸个第三方依赖。然后出bug了,打开三方代码,一堆文件无从下手,改了又担心出现新bug,然后又替换了第二个三方…

一个ViewPager能实现的功能,何必求助第三方。
Banner的实现技术点主要在于
1 无限循环,当banner滑到最后一张后继续滑动,要滑回第一张
2 自动轮播
Adapter代码

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import java.util.List;
/**
* Created by KID on 2017/8/11 0011.
*/
public class PagerBannerAdapter extends PagerAdapter {
private Context context;
private List<String> imgUrls;
private ViewPager mViewPager;
private boolean isPlay=false;
public PagerBannerAdapter(Context context, List<String> imgUrls,ViewPager mViewPager) {
this.context = context;
this.imgUrls = imgUrls;
this.mViewPager=mViewPager;
}
//是否自动播放第一张图片到第二张
public void setPlayingFirstItem(boolean isPlay){
this.isPlay=isPlay;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
final int pos = position%imgUrls.size();
View view = LayoutInflater.from(context).inflate(R.layout.item_vp, container, false);
ImageView imageView = (ImageView) view.findViewById(R.id.img_item);
Glide.with(context).load(imgUrls.get(pos)).into(imageView);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"当前看到的要做点击事件的position="+pos+"-----实际position="+position,Toast.LENGTH_SHORT).show();
}
});
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
Log.d("BannerView","destroyItem position======"+position);
}
@Override
public int getCount() {
//理论上当图片不为1张时,getCount可以设置无穷大,这里设置成图片4倍只是想看会不会滑到头,实际上3倍就够了,设2倍的话,当图片只有2张时可能会碰到头
return imgUrls.size()==1?imgUrls.size():imgUrls.size()*4;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
//当显示界面加载完时调用该方法
@Override
public void finishUpdate(ViewGroup container) {
Log.d("BannerView","finishUpdate position======"+mViewPager.getCurrentItem());
int position = mViewPager.getCurrentItem();
if(imgUrls.size()==1){
//TODO 但轮播图片只有一张的时候,什么都不做,或者隐藏小圆点,提示文本之类。当然,如果你只有一张图片,你还想重复滑出这张图片的话,在这里开始你的骚操作
} else {
//TODO 但轮播图片超过3张时,每当图片的position超过图片数量时,切换viewpager当前选择item(去除切换动画效果)
if (position == 0){
position = imgUrls.size();
//TODO 自动轮播的时候,需要这部判断来让第一张和第二张平滑过渡
if(!isPlay){
mViewPager.setCurrentItem(position,false);
}
} else if(position>imgUrls.size()+1){
mViewPager.setCurrentItem(position%imgUrls.size(),false);
}
}
}
}

Activity代码

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
/**
* Created by KID on 2017/8/16 0016.
* Handler实现图片自动轮播
*/
public class PlayBannerActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {
@Bind(R.id.view_pager)
ViewPager viewPager;
@Bind(R.id.tv_page)
TextView pageTv;
List<String> imgUrls=new ArrayList<>();
PagerBannerAdapter adapter;
private boolean isStill;//是否静止
int pos ;//看到的position
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_banner);
ButterKnife.bind(this);
initData();
}
private void initData() {
imgUrls.add("http://img2.91.com/uploads/allimg/140417/59-14041GQ2040-L.jpg");
imgUrls.add("http://img.pconline.com.cn/images/upload/upc/tx/wallpaper/1205/25/c2/11755122_1337938898578_800x600.jpg");
imgUrls.add("http://img04.tooopen.com/images/20130114/tooopen_22372502.jpg");
imgUrls.add("http://img3.iqilu.com/data/attachment/forum/201308/21/100932s9pwjxmm4h8jy704.jpg");
imgUrls.add("http://images.ali213.net/picfile/pic/2013/02/25/927_48.jpg");
adapter=new PagerBannerAdapter(PlayBannerActivity.this,imgUrls,viewPager);
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(this);
//第一次进入页面开始轮播图的间隔要久一点,要等图片,还有你其他的页面数据加载出来以后才轮播,提高用户体验
mHandler.sendEmptyMessageDelayed(MESSAGE_PLAY_IMAGE,4000);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(positionOffset==0&&positionOffsetPixels==0){
isStill=true;
}else {
isStill=false;
mHandler.removeMessages(MESSAGE_PLAY_IMAGE);
}
}
@Override
public void onPageSelected(int position) {
//打印position为真实的position
pos=position%imgUrls.size();
pageTv.setText(pos+1+"/"+imgUrls.size());
}
@Override
public void onPageScrollStateChanged(int state) {
if(state==0){//静止
isStill=true;
mHandler.removeMessages(MESSAGE_PLAY_IMAGE);
mHandler.sendEmptyMessageDelayed(MESSAGE_PLAY_IMAGE,PLAY_DELAY);
}
}
/**
* 消息处理
*/
//轮播下一张图片
private static final int MESSAGE_PLAY_IMAGE=1001;
//轮播间隔时间
private static final long PLAY_DELAY=2000;
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
/**滑动中,同步播放进度*/
case MESSAGE_PLAY_IMAGE:
if(isStill){
if(pos==0){
adapter.setPlayingFirstItem(true);
viewPager.setCurrentItem(0,false);
viewPager.setCurrentItem(pos+1);
adapter.setPlayingFirstItem(false);
}else {
viewPager.setCurrentItem(pos+1);
adapter.setPlayingFirstItem(false);
}
}
break;
}
}
};
}

核心代码

    //当显示界面加载完时调用该方法
@Override
public void finishUpdate(ViewGroup container) {
Log.d("BannerView","finishUpdate position======"+mViewPager.getCurrentItem());
int position = mViewPager.getCurrentItem();
if(imgUrls.size()==1){
//TODO 但轮播图片只有一张的时候,什么都不做,或者隐藏小圆点,提示文本之类。当然,如果你只有一张图片,你还想重复滑出这张图片的话,在这里开始你的骚操作
} else {
//TODO 但轮播图片超过3张时,每当图片的position超过图片数量时,切换viewpager当前选择item(去除切换动画效果)
if (position == 0){
position = imgUrls.size();
//TODO 自动轮播的时候,需要这部判断来让第一张和第二张平滑过渡
if(!isPlay){
mViewPager.setCurrentItem(position,false);
}
} else if(position>imgUrls.size()+1){
mViewPager.setCurrentItem(position%imgUrls.size(),false);
}
}
}

将getCount return 无穷大,当图片显示在第一张位置(position=0)的时候,我们要让viewpager左边也有图片,所以将position的索引加上图片数量。其实到这里,我们就已经能实现图片无限轮播了,再加上hanlder延迟执行,自动轮播效果就出来了。但position的数量无限增加,item的数量也会一直增加,哪怕viewpager默认只会保存当前和相邻两页,那些被划过的pager占用的内存也不会马上释放掉。所以我们不能让position,也就是item无限增加。 ViewPager的切换中,可以设置去除切换效果setCurrentItem(position,false);
而我们需要找准一个时机,让viewpager切回position最小时的状态 mViewPager.setCurrentItem(position%imgUrls.size(),false);
handler自动轮播的代码就比较简单了,通过 mHandler.sendEmptyMessageDelayed(MESSAGE_PLAY_IMAGE,PLAY_DELAY);
和mHandler.removeMessages(MESSAGE_PLAY_IMAGE)来实现。当viewpager处于静止状态时,抛出一个延迟 n秒执行的消息去让viewpager切换到下一页,viewpager切换到下一页后,又会处于静止状态,再次抛出n秒执行的消息。当延迟消息执行前,viewpager状态不是静止了,就把延迟消息取消掉。
至此,一个自动轮播的banner就出来了。
可能你看着,感觉代码一堆,没第三方写起来简洁- -!
ok,让我们稍微封装一下

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import java.util.List;
/**
* Created by KID on 2017/8/17 0017.
*/
public class BannerView extends FrameLayout implements ViewPager.OnPageChangeListener {
//轮播下一张图片
private static final int MESSAGE_PLAY_IMAGE=1001;
//轮播间隔时间
private static final long PLAY_DELAY=2000L;
//第一张图片间隔多久开始轮播
private static final long FIRST_DELAY=4000L;
private ViewPager viewPager;
private Context context;
private List<String> imgUrls;
private BannerPageAdapter bannerPageAdapter;
private int cusPosition;//当前看到的选中位置
private boolean isAutoPlay=false;
private BannerListener bannerListener;
public interface BannerListener{
void OnBannerSelect(int position);
void OnBannerClick(int position);
}
public void setBannerListener(BannerListener bannerListener){
this.bannerListener=bannerListener;
}
private boolean isStill;//是否静止
int pos ;//看到的position
public BannerView(Context context) {
super(context);
this.context=context;
}
public BannerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context=context;
init(context);
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context=context;
}
/**
* @param isAutoPlay 是否自动播放 ,实现比较暴力,直接不让handler消息执行
*/
public void setAutoPlay(boolean isAutoPlay){
this.isAutoPlay=isAutoPlay;
}
/**
* 设置图片
* @param imageList
*/
public void setImageList(List<String>imageList){
imgUrls=imageList;
bannerPageAdapter=new BannerPageAdapter();
viewPager.setAdapter(bannerPageAdapter);
viewPager.addOnPageChangeListener(this);
//第一次进入页面开始轮播图的间隔要久一点,要等图片,还有你其他的页面数据加载出来以后才轮播,提高用户体验
mHandler.sendEmptyMessageDelayed(MESSAGE_PLAY_IMAGE,FIRST_DELAY);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.view_banner, this);
viewPager= (ViewPager) findViewById(R.id.view_pager);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(positionOffset==0&&positionOffsetPixels==0){
isStill=true;
}else {
isStill=false;
mHandler.removeMessages(MESSAGE_PLAY_IMAGE);
}
}
@Override
public void onPageSelected(int position) {
//打印position为真实的position
pos=position%imgUrls.size();
if(cusPosition!=pos){//引入自动播放后,会在某一位置做一个无动画效果的页面切换,加入这步判断才回调防止重复调用
bannerListener.OnBannerSelect(pos);
}
cusPosition=pos;
}
@Override
public void onPageScrollStateChanged(int state) {
if(state==0){//静止
isStill=true;
mHandler.removeMessages(MESSAGE_PLAY_IMAGE);
mHandler.sendEmptyMessageDelayed(MESSAGE_PLAY_IMAGE,PLAY_DELAY);
}
}
class BannerPageAdapter extends PagerAdapter {
private boolean isPlay=false;
public BannerPageAdapter() {
}
//是否自动播放第一张图片到第二张
public void setPlayingFirstItem(boolean isPlay){
this.isPlay=isPlay;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
final int pos = position%imgUrls.size();
View view = LayoutInflater.from(context).inflate(R.layout.item_vp, container, false);
ImageView imageView = (ImageView) view.findViewById(R.id.img_item);
Glide.with(context).load(imgUrls.get(pos)).into(imageView);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bannerListener.OnBannerClick(pos);
}
});
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getCount() {
//          理论上当图片不为1张时,getCount可以设置无穷大,这里设置成图片4倍只是想看会不会滑到头,实际上3倍就够了,设2倍的话,当图片只有2张时可能会碰到头
//          return Integer.MAX_VALUE;
return imgUrls.size()==1?imgUrls.size():imgUrls.size()*4;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
//当显示界面加载完时调用该方法
@Override
public void finishUpdate(ViewGroup container) {
Log.d("BannerView","finishUpdate position======"+viewPager.getCurrentItem());
int position = viewPager.getCurrentItem();
if(imgUrls.size()==1){
//TODO 但轮播图片只有一张的时候,什么都不做,或者隐藏小圆点,提示文本之类。当然,如果你只有一张图片,你还想重复滑出这张图片的话,在这里开始你的骚操作
} else {
//TODO 但轮播图片超过3张时,每当图片的position超过图片数量时,切换viewpager当前选择item(去除切换动画效果)
if (position == 0){
position = imgUrls.size();
//TODO 自动轮播的时候,需要这部判断来让第一张和第二张平滑过渡
if(!isPlay){
viewPager.setCurrentItem(position,false);
}
} else if(position>imgUrls.size()+1){
viewPager.setCurrentItem(position%imgUrls.size(),false);
}
}
}
}
/**
* 消息处理
*/
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
/**滑动中,同步播放进度*/
case MESSAGE_PLAY_IMAGE:
if(isStill&&isAutoPlay){
if(pos==0){
bannerPageAdapter.setPlayingFirstItem(true);
viewPager.setCurrentItem(0,false);
viewPager.setCurrentItem(pos+1);
bannerPageAdapter.setPlayingFirstItem(false);
}else {
viewPager.setCurrentItem(pos+1);
bannerPageAdapter.setPlayingFirstItem(false);
}
}
break;
}
}
};
/**
* 退出页面后防止handler还执行
*/
public void stopPlay(){
if(mHandler!=null)mHandler.removeMessages(MESSAGE_PLAY_IMAGE);
}
}

使用

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
public class CusBannerActivity extends AppCompatActivity {
@Bind(R.id.bannerView)
BannerView bannerView;
@Bind(R.id.tv_page)
TextView pageTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cus_banner);
ButterKnife.bind(this);
final List<String>list=new ArrayList<>();
list.add("http://img2.91.com/uploads/allimg/140417/59-14041GQ2040-L.jpg");
list.add("http://img.pconline.com.cn/images/upload/upc/tx/wallpaper/1205/25/c2/11755122_1337938898578_800x600.jpg");
list.add("http://img04.tooopen.com/images/20130114/tooopen_22372502.jpg");
list.add("http://img3.iqilu.com/data/attachment/forum/201308/21/100932s9pwjxmm4h8jy704.jpg");
list.add("http://images.ali213.net/picfile/pic/2013/02/25/927_48.jpg");
bannerView.setImageList(list);
bannerView.setAutoPlay(true);
bannerView.setBannerListener(new BannerView.BannerListener() {
@Override
public void OnBannerSelect(int position) {
pageTv.setText(position+1+"/"+list.size());
Toast.makeText(CusBannerActivity.this, "选中======="+position, Toast.LENGTH_SHORT).show();
}
@Override
public void OnBannerClick(int position) {
Toast.makeText(CusBannerActivity.this, "点击======="+position, Toast.LENGTH_SHORT).show();
}
});
pageTv.setText(1+"/"+list.size());
}
@Override
protected void onDestroy() {
super.onDestroy();
bannerView.stopPlay();
}
}

在onDestory里,我们需要让hanlder不再执行。PS,有些手机退出界面后,onDestory迟迟不执行,这时候就要结合实际情况,在onPause或者监听返回键,你自己项目的返回按钮执行bannerView.stopPlay()这步操作了。很多三方库封装的banner,定时器没有关闭操作,在页面finish后,延迟执行的操作里,找不到之前被关闭页面的view,导致App崩溃。

人已赞赏
Android文章

Android view的单击,双击,长按事件监听

2020-4-5 8:33:18

Android文章

Android WebView上滑隐藏头部,下滑显示头部

2020-4-5 9:48:05

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索