自己动手搭建Banner轮播器

来源:互联网 发布:西门子1200plc编程 编辑:程序博客网 时间:2024/06/05 15:53

源代码引用地址:https://github.com/yiyibb/Zhihu

首先来看轮播效果图

Paste_Image.png
Paste_Image.png

整体是个recyclerview,头部布局为banner轮播图,此处的banner是继承Framelayout实现的。代码后面会具体说明,这里我们先看轮播图的xml文件的结构,总共三部分首先是背景图片,可实现自动滑动,另外是标题,还有最下面的 圆点,都会随着图片的移动而移动。
布局文件.xml

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"             xmlns:tools="http://schemas.android.com/tools"             android:id="@+id/bannerLayout"             android:layout_width="match_parent"             android:layout_height="@dimen/space_200">        <com.yiyi.zhihu.ui.widget.Banner.BannerViewPaper        android:id="@+id/viewPager"        android:layout_width="match_parent"        android:layout_height="wrap_content" />        <TextView        android:id="@+id/banner_title"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="bottom"        android:gravity="left"        android:maxLines="3"        android:padding="@dimen/space_16"        android:textColor="@color/colorWhite"        android:textSize="@dimen/textSize_20"        android:textStyle="bold"        tools:text="图片轮播器"        tools:textColor="@color/colorLightBlue"/>        <LinearLayout        android:id="@+id/indicator"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="bottom|center_horizontal"        android:layout_marginBottom="@dimen/space_10"        android:layout_marginTop="@dimen/space_10"        android:gravity="center"        android:orientation="horizontal" >    </LinearLayout>    </FrameLayout>

看重点主要是BannerViewPaper,这个类仅仅继承了ViewPager,代码如下:

public class BannerViewPaper extends ViewPager {    private boolean scrollable = true;    public BannerViewPaper(Context context) {        super(context);    }    public BannerViewPaper(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        return this.scrollable && super.onTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return this.scrollable && super.onInterceptTouchEvent(ev);    }    public void setScrollable(boolean scrollable) {        this.scrollable = scrollable;    }}

滑动事件以及点击事件设定了scrollable。
接下来看重点banner,其实这也算是自定义viewgroup了,继承了framelayout,实现了滑动以及自动播放功能。看一下代码吧,emm,代码一如既往地多,但是我会一点点来分析,踏踏实实,耐耐心心,做技术本该如此,不是吗?

public class Banner extends FrameLayout implements ViewPager.OnPageChangeListener {    private static final String TAG = "Banner";    private int indicatorSize;    private int mIndicatorWidth;    private int mIndicatorHeight;    private int mIndicatorMargin;    private int bannerStyle = BannerConfig.CIRCLE_INDICATOR;    private int delayTime = BannerConfig.TIME;    private int scrollTime = BannerConfig.DURATION;    private boolean isAutoPlay = BannerConfig.IS_AUTO_PLAY;    private boolean isScroll = BannerConfig.IS_SCROLL;    private int mIndicatorSelectedResId = R.drawable.selected_radius;    private int mIndicatorUnselectedResId = R.drawable.unselected_radius;    private int titleHeight;    private int titleBackground;    private int titleTextColor;    private int titleTextSize;    private int count = 0;    private int currentItem;    private int lastPosition = 1;    private Context mContext;    private BannerPagerAdapter mAdapter;    private List<View> imageViews;    private List<ImageView> indicatorImages;    private List imageUrls;    private List<String> titles;    private BannerViewPaper mViewPaper;    private TextView bannerTitle;    private LinearLayout indicator;    private BannerScroller mScroller;    private DisplayMetrics dm;    private ImageLoaderInterface imageLoader;    private OnBannerClickListener mOnBannerClickListener;    private ViewPager.OnPageChangeListener mOnPageChangeListener;    private WeakHandler handler = new WeakHandler();    public Banner(Context context) {        this(context, null);    }    public Banner(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public Banner(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.mContext = context;        titles = new ArrayList<>();        imageUrls = new ArrayList();        imageViews = new ArrayList<>();        indicatorImages = new ArrayList<>();        dm = context.getResources().getDisplayMetrics();        indicatorSize = dm.widthPixels / 80;        initView(context, attrs);    }    private void initView(Context context, AttributeSet attrs) {        imageViews.clear();        View view = LayoutInflater.from(context).inflate(R.layout.banner, this, true);        mViewPaper = (BannerViewPaper) view.findViewById(R.id.viewPager);        bannerTitle = (TextView) view.findViewById(R.id.banner_title);        indicator = (LinearLayout) view.findViewById(R.id.indicator);        handleTypedArray(context, attrs);        initViewPaperScroll();    }    private void handleTypedArray(Context context, AttributeSet attrs) {        if (attrs == null) {            return;        }        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Banner);        mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_width, indicatorSize);        mIndicatorHeight = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_height, indicatorSize);        mIndicatorMargin = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_margin, BannerConfig.PADDING_SIZE);        mIndicatorSelectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_selected, R.drawable.selected_radius);        mIndicatorUnselectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_unselected, R.drawable.unselected_radius);        delayTime = typedArray.getInt(R.styleable.Banner_delay_time, BannerConfig.TIME);        scrollTime = typedArray.getInt(R.styleable.Banner_scroll_time, BannerConfig.DURATION);        isAutoPlay = typedArray.getBoolean(R.styleable.Banner_is_auto_play, BannerConfig.IS_AUTO_PLAY);        titleBackground = typedArray.getColor(R.styleable.Banner_title_background, BannerConfig.TITLE_BACKGROUND);        titleHeight = typedArray.getDimensionPixelSize(R.styleable.Banner_title_height, BannerConfig.TITLE_HEIGHT);        titleTextColor = typedArray.getColor(R.styleable.Banner_title_textcolor, BannerConfig.TITLE_TEXT_COLOR);        titleTextSize = typedArray.getDimensionPixelSize(R.styleable.Banner_title_textsize, BannerConfig.TITLE_TEXT_SIZE);        typedArray.recycle();    }    private void initViewPaperScroll() {        try {            Field mField = ViewPager.class.getDeclaredField("mScroller");            mField.setAccessible(true);            mScroller = new BannerScroller(mViewPaper.getContext());            mScroller.setDuration(scrollTime);            mField.set(mViewPaper, mScroller);        } catch (Exception e) {            Log.e(TAG, e.getMessage());        }    }    public Banner isAutoPlay(boolean isAutoPlay) {        this.isAutoPlay = isAutoPlay;        return this;    }    public Banner setImageLoader(ImageLoaderInterface imageLoader) {        this.imageLoader = imageLoader;        return this;    }    public Banner setDelayTime(int delayTime) {        this.delayTime = delayTime;        return this;    }    public Banner setBannerTitles(List<String> titles) {        this.titles = titles;        bannerTitle.setText(titles.get(0));        return this;    }    public Banner setImages(List<?> imageUrls) {        this.imageUrls = imageUrls;        this.count = imageUrls.size();        return this;    }    public void update(List<?> imageUrls, List<String> titles) {        this.imageUrls.clear();        this.titles.clear();        this.imageUrls.addAll(imageUrls);        this.titles.addAll(titles);        this.count = this.imageUrls.size();        start();    }    public void update(List<?> imageUrls) {        this.imageUrls.clear();        this.imageUrls.addAll(imageUrls);        this.count = this.imageUrls.size();        start();    }    public Banner start() {        setImagesList(imageUrls);        if (isAutoPlay) {            startAutoPlay();        }        return this;    }    private void createIndicator() {//滚动的圆点        indicatorImages.clear();        indicator.removeAllViews();        for (int i = 0; i < count; ++i) {            ImageView imageView = new ImageView(mContext);            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight);            params.leftMargin = mIndicatorMargin;            params.rightMargin = mIndicatorMargin;            if (i == 0) {                imageView.setImageResource(mIndicatorSelectedResId);            } else {                imageView.setImageResource(mIndicatorUnselectedResId);            }            indicatorImages.add(imageView);            indicator.addView(imageView, params);        }    }    private void setData() {        currentItem = 1;        if (mAdapter == null) {            mAdapter = new BannerPagerAdapter();        }        mViewPaper.setAdapter(mAdapter);        mViewPaper.setFocusable(true);        mViewPaper.setCurrentItem(currentItem);        mViewPaper.addOnPageChangeListener(this);        if (isScroll && count > 1) {            mViewPaper.setScrollable(true);        } else {            mViewPaper.setScrollable(false);        }    }    private void setImagesList(List<?> imageUrls) {        if (imageUrls == null || imageUrls.size() <= 0) {            Log.e(TAG, "please set the images data");            return;        }        imageViews.clear();        createIndicator();//滚动小圆球        for (int i = 0; i <= count + 1; ++i) {            View imageView = null;            if (imageLoader != null) {                imageView = imageLoader.creteImageView(mContext);            }            if (imageView == null) {                imageView = new ImageView(mContext);            }            ((ImageView)imageView).setScaleType(ImageView.ScaleType.CENTER_CROP);            Object url = null;            // 无限轮播实现原理            if (i == 0) {                url = imageUrls.get(count - 1);            } else if (i == count + 1) {                url = imageUrls.get(0);            } else {                url = imageUrls.get(i - 1);            }            imageViews.add(imageView);            if (imageLoader != null) {                imageLoader.displayImage(mContext, url, imageView);            } else {                Log.e(TAG, "please set image loader");            }        }        setData();    }    public void startAutoPlay() {        handler.removeCallbacks(task);        handler.postDelayed(task, delayTime);    }    public void stopAutoPlay() {        handler.removeCallbacks(task);    }    private final Runnable task = new Runnable() {        @Override        public void run() {            if (count > 1 && isAutoPlay) {                currentItem = currentItem % (count + 1) + 1;                if (currentItem == 1) {                    mViewPaper.setCurrentItem(currentItem);                    handler.postDelayed(task, delayTime);                    Log.i("困了1",mViewPaper.getCurrentItem()+"");                } else if (currentItem == count+1) {                    //mViewPaper.setCurrentItem(currentItem,false);                    handler.post(task);                    Log.i("困了1",mViewPaper.getCurrentItem()+"");                }  else {                    mViewPaper.setCurrentItem(currentItem);                    handler.postDelayed(task, delayTime);                    Log.i("困了2",mViewPaper.getCurrentItem()+"");                }            }        }    };    /**     * 返回真实的位置     *     * @param position     * @return 下标从0开始     */    public int toRealPosition(int position) {        int realPosition = (position - 1) % count;        if (realPosition < 0) {            realPosition += count;        }        return realPosition;    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if  (isAutoPlay) {            int action = ev.getAction();            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL                    || action == MotionEvent.ACTION_OUTSIDE) {                startAutoPlay();            } else if (action == MotionEvent.ACTION_DOWN) {                stopAutoPlay();            }        }        return super.dispatchTouchEvent(ev);    }    class BannerPagerAdapter extends PagerAdapter {        @Override        public int getCount() {            return imageViews.size();        }        @Override        public boolean isViewFromObject(View view, Object object) {            return view == object;        }        @Override        public Object instantiateItem(ViewGroup container, final int position) {            container.addView(imageViews.get(position));            View view = imageViews.get(position);            if (mOnBannerClickListener != null) {                view.setOnClickListener(new OnClickListener() {                    @Override                    public void onClick(View view) {                        mOnBannerClickListener.OnBannerClick(toRealPosition(position));                    }                });            }            return view;        }        @Override        public void destroyItem(ViewGroup container, int position, Object object) {            container.removeView((View)object);        }    }    @Override    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {        if (mOnPageChangeListener != null) {//不执行            mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);            Log.i("梦醒","对象为空1");        }    }    @Override    public void onPageSelected(int position) {        if (mOnPageChangeListener != null) {//不执行            mOnPageChangeListener.onPageSelected(position);            Log.i("梦醒","对象为空2");        }        indicatorImages.get((lastPosition - 1 + count) % count).setImageResource(mIndicatorUnselectedResId);        indicatorImages.get((position - 1 + count) % count).setImageResource(mIndicatorSelectedResId);        lastPosition = position;        if (position == 0) position = count;        if (position > count) position = 1;        bannerTitle.setText(titles.get(position - 1));    }    @Override    public void onPageScrollStateChanged(int state) {        if (mOnPageChangeListener != null) {            mOnPageChangeListener.onPageScrollStateChanged(state);        }        currentItem = mViewPaper.getCurrentItem();        switch (state) {            /* SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态(手指按下但是还没有拖动的时候还不是这个状态,                                           只有按下并且手指开始拖动后log才打出。)            SCROLL_STATE_IDLE(0)滑动动画做完的状态。            SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。*/            case 0://No operation               /* if (currentItem == 0) {                    mViewPaper.setCurrentItem(count,false);                } else if (currentItem == count + 1) {                    mViewPaper.setCurrentItem(1, false);                }                Log.i("梦醒0",mViewPaper.getCurrentItem()+"");*/                break;            case 1://start Sliding正在滑动                /*if (currentItem == count + 1) {                    mViewPaper.setCurrentItem(1, false);                } else if (currentItem == 0) {                    mViewPaper.setCurrentItem(count, false);                }                Log.i("梦醒1",mViewPaper.getCurrentItem()+"");*/                break;            case 2://end Sliding                /*if (currentItem == 0) {                    mViewPaper.setCurrentItem(count,false);                } else if (currentItem == count + 1) {                    mViewPaper.setCurrentItem(1, false);                }                Log.i("梦醒2",mViewPaper.getCurrentItem()+"");*/                break;        }    }    public void setOnPageChangeListener(ViewPager.OnPageChangeListener onPageChangeListener) {        mOnPageChangeListener = onPageChangeListener;    }    public Banner setOnBannerClickListener(OnBannerClickListener listener) {        this.mOnBannerClickListener = listener;        return this;    }    public interface OnBannerClickListener {        public void OnBannerClick(int position);    }}

找关键,挑重点,重要的几个方法就这么多
initView();
startAutoPlay();
task;
介绍一下整体流程,说到底banner的实现使用就是viewpager,利用定时让viewpager自动播放,难点是无限轮播,这里我也只能算是看懂了代码,,但是不能理会他的精髓,直接让我写,我还是写不出来,这就是大神的能力吧。
首先initview初始化布局文件,这里面有两个方法一个是 handleTypedArray用于配置布局控件中的风格,高度长度,背景等等。另一个方法是initViewPaperScroll()这个方法是利用Java反射机制控制viewpager的滑动时间。再往后边看就是一些设定参数的方法,没啥可讲的。
后面重点是start方法,这个方法里面先是初始化了数据。关键代码如下:

for (int i = 0; i <= count + 1; ++i) {            View imageView = null;            if (imageLoader != null) {                imageView = imageLoader.creteImageView(mContext);            }            if (imageView == null) {                imageView = new ImageView(mContext);            }            ((ImageView)imageView).setScaleType(ImageView.ScaleType.CENTER_CROP);            Object url = null;            // 无限轮播实现原理            if (i == 0) {                url = imageUrls.get(count - 1);            } else if (i == count + 1) {                url = imageUrls.get(0);            } else {                url = imageUrls.get(i - 1);            }            imageViews.add(imageView);            if (imageLoader != null) {                imageLoader.displayImage(mContext, url, imageView);            } else {                Log.e(TAG, "please set image loader");            }        }

这个轮播图片总数为5,也就是count为5。画了一张草图

Paste_Image.png
Paste_Image.png

图上面是从网络上获取到的url集合,图下面是viewpager对应的imageview的六张图片。下面来看runable延时任务,每隔一段时间就会执行一次,但是由于这里是无限轮播,所以会有一些特殊处理

private final Runnable task = new Runnable() {        @Override        public void run() {            if (count > 1 && isAutoPlay) {                currentItem = currentItem % (count + 1) + 1;                if (currentItem == 1) {                    mViewPaper.setCurrentItem(currentItem);                    handler.postDelayed(task, delayTime);                } else if (currentItem == count+1) {                    handler.post(task);                }  else {                    mViewPaper.setCurrentItem(currentItem);                    handler.postDelayed(task, delayTime);                }            }        }    };

这里其实是一个循环,因为 currentItem每次都会除以6取余加1,初始化currentItem为1,需要注意的是 currentItem是从imageviews中取的值。 currentItem会按照1—>2—>3—>4—>5—>6—>1这样循环下去。
当照片为imageviews中的最后一张时,task任务会直接执行下一次任务不做延时,也就是直接蹦到imageviews的第一张。这时候您如果仔细看的话会发现imageviews的第0张图片并没有什么卵用啊,其实这个问题是隐藏的,刚开始我也没注意到。当currentItem为1时,也就是imageviews中的第一张照片,这时候用户要是向左滑动,就会蹦到imageviews的第0张图片。这时候再来看,按照正常逻辑走 currentItem下一个又会变成1,不得不佩服作者的逻辑,这种可以被算上算法了吧,左右的操作都在这个循环里。

Tip:我对作者的源代码稍稍改变了一下,因为他这里有点小小的问题,viewpager在自动滑动的情况下,当用户向左滑动viewpager一直到倒数第一张图片,这时候用户松手,轮播会直接跳转到第二张图片然后继续滑动。我修改了onPageScrollStateChanged和task两个方法,您可以仔细研究一下作者的源码。https://github.com/yiyibb/Zhihu

还有一些次要的方法:
createIndicator:用于底部圆点的滚动,这个圆点滚动效果其实是在onPageSelected时候开始变化的,道理比较简单,也是跟上面一样像一个循环。
onPageScrollStateChanged:这个方法原作者是写了一些代码的,针对的也是用户滑动事件,简单说说这个方法对应着三种状态
SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态(手指按下但是还没有拖动的时候还不是这个状态,只有按下并且手指开始拖动后log才打出。)
SCROLL_STATE_IDLE(0)滑动动画做完的状态。
SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。
源代码作者写的代码我感觉是非必要的,已经注释掉了,也可能是自己没能领会到吧。
dispatchTouchEvent:这个是触摸事件,用于拦截用户的滑动事件,没啥可讲的
OnBannerClickListener:监听回调接口
感兴趣的可以关注我最新开的公众号,重在分享!!微信搜索 开发Android的小学生

qrcode_for_gh_c686d73be7e1_430.jpg
qrcode_for_gh_c686d73be7e1_430.jpg
原创粉丝点击