Android ViewPager实现轻量级Banner

来源:互联网 发布:淘宝直通车效果怎么样 编辑:程序博客网 时间:2024/05/29 18:24

效果图
发现好多人提到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崩溃。
代码传送门http://download.csdn.net/download/qq_31390699/9944176