banner实现方式分析

  作为一名android开发小白,最近要用到banner轮播图,于是便研究了一下banner的实现方式,在此记录一下所用到的知识点。

  我是在github上看到的这个项目,地址:banner轮播图 将banner引入到项目中的方式上述链接已有描述,使用起来还是比较方便的。使用的方法如下:

banner在线生成器 java bannercode_java


接下来会对每一行代码进行分析。

1、banner.setImageLoader(new GlideImageLoader())

  这里我们需要实现一个图片加载器,作用:banner的图片来源可以有很多种,可以是从本地加载,可以从网络上下载,可以是一个url地址,图片加载器的作用就是将各种来源的图片转换为安卓的ImageView,下面是我实现的一个简单加载器:

public class GlideImageLoader extends ImageLoader {

    @Override
    public void displayImage(Context context, Object path, ImageView imageView) {
        //Glide 加载图片简单用法
        Glide.with(context).load(path).into(imageView);
    }
}

这篇文章对Glide框架有一个深入的分析Glide图片加载框架,作者为大名鼎鼎的郭霖。简单概况来说,Glide框架的作用如下:
    Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等
  Glide.with(context).load(path).into(imageView)这一行代码能实现很多功能,可以将path中的图片资源加载到我们想要显示的imageView中。

2、banner.setImages(images)

setImages的实现方式为:

public Banner setImages(List<?> imageUrls) {
        this.imageUrls.addAll(imageUrls);
        this.count = imageUrls.size();
        return this;
    }

banner中共定义了四个List,如下:

private List<String> titles;
    private List imageUrls;
    private List<View> imageViews;
    private List<ImageView> indicatorImages;

titles是用来存放轮播图的标题,imageUrls用来用来存放图片的来源,imageViews就是要轮播的图片view, indicatorImages用来存放指示器的view。

3、banner.start()

public Banner start() {
        setBannerStyleUI();
        setImageList(imageUrls);
        setData();
        return this;
    }

先看setBannerStyleUI():

private void setBannerStyleUI() {
        int visibility = count > 1 ? View.VISIBLE : View.GONE;
        switch (bannerStyle) {
            case BannerConfig.CIRCLE_INDICATOR:
                indicator.setVisibility(visibility);
                break;
            case BannerConfig.NUM_INDICATOR:
                numIndicator.setVisibility(visibility);
                break;
            case BannerConfig.NUM_INDICATOR_TITLE:
                numIndicatorInside.setVisibility(visibility);
                setTitleStyleUI();
                break;
            case BannerConfig.CIRCLE_INDICATOR_TITLE:
                indicator.setVisibility(visibility);
                setTitleStyleUI();
                break;
            case BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE:
                indicatorInside.setVisibility(visibility);
                setTitleStyleUI();
                break;
        }
    }

此方法主要设置轮播图指示器样式,如果轮播图有标题的话还会调用setTitleStyleUI()方法设置标题。
再来看一下setImageList(List<?> imagesUrl)这个方法,

private void setImageList(List<?> imagesUrl) {
        if (imagesUrl == null || imagesUrl.size() <= 0) {
            bannerDefaultImage.setVisibility(VISIBLE);
            Log.e(tag, "The image data set is empty.");
            return;
        }
        bannerDefaultImage.setVisibility(GONE);
        initImages();
        for (int i = 0; i <= count + 1; i++) {
            Object url = null;
            if (i == 0) {
                url = imagesUrl.get(count - 1);
            } else if (i == count + 1) {
                url = imagesUrl.get(0);
            } else {
                url = imagesUrl.get(i - 1);
            }
            View imageView = null;
            if (imageLoader != null) {
                imageView = imageLoader.createImageView(context, url);
            }
            if (imageView == null) {
                imageView = new ImageView(context);
            }
            setScaleType(imageView);

            imageViews.add(imageView);
            if (imageLoader != null)
                imageLoader.displayImage(context, url, imageView);
            else
                Log.e(tag, "Please set images loader.");
        }
    }

这个方法的主要作用是将imagesUrl和imagesView进行一下转换,用到了标题1讲的imageLoader。
最后调用的方法是setData()方法,

private void setData() {
        if (startIndex != 0) {
            currentItem = startIndex;
        } else {
            currentItem = 1;
        }
        if (adapter == null) {
            adapter = new BannerPagerAdapter();
            viewPager.addOnPageChangeListener(this);
            viewPager.setAdapter(adapter);
        }else {
            adapter.notifyDataSetChanged();
        }
        viewPager.setFocusable(true);
        viewPager.setCurrentItem(currentItem);
        if (gravity != -1)
            indicator.setGravity(gravity);
        if (isScroll && count > 1) {
            viewPager.setScrollable(true);
        } else {
            viewPager.setScrollable(false);
        }
        if (isAutoPlay)
            startAutoPlay();
    }

这个方法要重点分析一下,涉及到的内容还是很多的。
首先用到了安卓系统给我们提供的ViewPager这个控件,关于这个控件的使用,这篇博客有详细的介绍:ViewPager详解,这里简单介绍一下ViewPager:

ViewPager

  ViewPager可以实现页面的滑动切换,要使用必须实现一个适配器:PageAdapter,该适配器必须重写四个函数:
  boolean isViewFromObject(View arg0, Object arg1)
  int getCount()
  void destroyItem(ViewGroup container, int position,Object object)
  Object instantiateItem(ViewGroup container, int position)
博客中有详细解读,这里不再赘述。
banner中自己实现的适配器如下:

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 (bannerListener != null) {
                view.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Log.e(tag, "你正在使用旧版点击事件接口,下标是从1开始," +
                                "为了体验请更换为setOnBannerListener,下标从0开始计算");
                        bannerListener.OnBannerClick(position);
                    }
                });
            }
            if (listener != null) {
                view.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        listener.OnBannerClick(toRealPosition(position));
                    }
                });
            }
            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

    }

ExecHandler

在setData()方法前面主要对ViewPager进行了设置,最后一句:

if (isAutoPlay)
    startAutoPlay();

startAutoPlay的实现方式为:

public void startAutoPlay() {
        handler.removeCallbacks(task);
        handler.postDelayed(task, delayTime);
    }

task的实现方式为:

private final Runnable task = new Runnable() {
        @Override
        public void run() {
            if (count > 1 && isAutoPlay) {
                currentItem = currentItem % (count + 1) + 1;
//                Log.i(tag, "curr:" + currentItem + " count:" + count);
                if (currentItem == 1) {
                    viewPager.setCurrentItem(currentItem, false);
                    handler.post(task);
                } else {
                    viewPager.setCurrentItem(currentItem);
                    handler.postDelayed(task, delayTime);
                }
            }
        }
    };

其中handler在类中进行了初始化:

private WeakHandler handler = new WeakHandler();

所调用方法postDelayed(),实现方式为:

/**
     * Causes the Runnable r to be added to the message queue, to be run
     * after the specified amount of time elapses.
     * The runnable will be run on the thread to which this handler
     * is attached.
     *
     * @param r The Runnable that will be executed.
     * @param delayMillis The delay (in milliseconds) until the Runnable
     *        will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the Runnable will be processed --
     *         if the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean postDelayed(Runnable r, long delayMillis) {
        return mExec.postDelayed(wrapRunnable(r), delayMillis);
    }

注释对应的翻译如下:
  该方法会将Runnable r 添加到消息队列中,并在指定的时间之后运行。
  Runnable r 子线程会在handler所在的进程下创建。
  如果 Runnable 已成功放入消息队列,则返回 true。 如果返回false,通常是因为处理消息队列的循环程序正在退出。 请注意,结果为 true 并不一定意味着Runnable r 会运行。
  如果在传递message的时候循环队列退出了,那么该message将会被丢弃。

mExec的初始化方式为:

public WeakHandler() {
        mCallback = null;
        mExec = new ExecHandler();
    }

ExexHandler()的定义为:

private static class ExecHandler extends Handler {
        private final WeakReference<Handler.Callback> mCallback;

        ExecHandler() {
            mCallback = null;
        }

        ExecHandler(WeakReference<Handler.Callback> callback) {
            mCallback = callback;
        }

        ExecHandler(Looper looper) {
            super(looper);
            mCallback = null;
        }

        ExecHandler(Looper looper, WeakReference<Handler.Callback> callback) {
            super(looper);
            mCallback = callback;
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            if (mCallback == null) {
                return;
            }
            final Handler.Callback callback = mCallback.get();
            if (callback == null) { // Already disposed
                return;
            }
            callback.handleMessage(msg);
        }
    }

handler是Android开发中很重要的一个消息传递机制,Handler 用法及解析这篇博客进行了详细地分析。使用WeakReference主要是为了避免内存泄漏。
在task中对ViewPager的currentItem进行了更新,这样就能实现自动轮播的效果。
  这样banner的主要方法都已经分析完成,总的来说banner使用起来还是很方便的。