banner实现方式分析
作为一名android开发小白,最近要用到banner轮播图,于是便研究了一下banner的实现方式,在此记录一下所用到的知识点。
我是在github上看到的这个项目,地址:banner轮播图 将banner引入到项目中的方式上述链接已有描述,使用起来还是比较方便的。使用的方法如下:
接下来会对每一行代码进行分析。
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使用起来还是很方便的。