源码地址:https://github.com/youth5201314/banner
1.整体分析
一个banner轮播图分:轮播和指示器
轮播部分:用的是ViewPager,
参数分析:默认背景图,轮播延迟时间,是否自动播放,是否可滑动,滑动时间,当前item,上个postion。
private int bannerBackgroundImage;
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 currentItem;
private int lastPosition = 1;
//当前ViewPager
private BannerViewPager viewPager;
//ViewPager 子view集合
private List<View> imageViews;
指示器:用的是一个LinerLayout,通过addView来增加指示器的子view部分。
参数分析:指示器高度,指示器宽度,指示器选中样式,未选中样式。
private int mIndicatorWidth;
private int mIndicatorHeight;
private int mIndicatorSelectedResId = R.drawable.gray_radius;
private int mIndicatorUnselectedResId = R.drawable.white_radius;
//指示器 子view集合
private List<ImageView> indicatorImages;
2.构造方法:
public class Banner extends FrameLayout
该banner控件继承自FrameLayout。、
public Banner(Context context) {
this(context, null);
}
public Banner(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Banner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = 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();
handleTypedArray(context, attrs);
View view = LayoutInflater.from(context).inflate(mLayoutResId, this, true);
bannerDefaultImage = (ImageView) view.findViewById(R.id.bannerDefaultImage);
viewPager = (BannerViewPager) view.findViewById(R.id.bannerViewPager);
titleView = (LinearLayout) view.findViewById(R.id.titleView);
indicator = (LinearLayout) view.findViewById(R.id.circleIndicator);
indicatorInside = (LinearLayout) view.findViewById(R.id.indicatorInside);
bannerTitle = (TextView) view.findViewById(R.id.bannerTitle);
numIndicator = (TextView) view.findViewById(R.id.numIndicator);
numIndicatorInside = (TextView) view.findViewById(R.id.numIndicatorInside);
bannerDefaultImage.setImageResource(bannerBackgroundImage);
initViewPagerScroll();
}
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.gray_radius);
mIndicatorUnselectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_unselected, R.drawable.white_radius);
scaleType = typedArray.getInt(R.styleable.Banner_image_scale_type, scaleType);
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);
mLayoutResId = typedArray.getResourceId(R.styleable.Banner_banner_layout, mLayoutResId);
bannerBackgroundImage = typedArray.getResourceId(R.styleable.Banner_banner_default_image, R.drawable.no_banner);
typedArray.recycle();
}
构造方法里面对控件进行声明,并通过View view = LayoutInflater.from(context).inflate(mLayoutResId, this, true);方式获取到该View的布局。
3.具体实现
1)指示器创建
private void createIndicator() {
indicatorImages.clear();
indicator.removeAllViews();
indicatorInside.removeAllViews();
for (int i = 0; i < count; i++) {
ImageView imageView = new ImageView(context);
imageView.setScaleType(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);
if (bannerStyle == BannerConfig.CIRCLE_INDICATOR ||
bannerStyle == BannerConfig.CIRCLE_INDICATOR_TITLE)
indicator.addView(imageView, params);
else if (bannerStyle == BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE)
indicatorInside.addView(imageView, params);
}
}
通过addView的方式将各个子ImageView添加到指示器LinerLayout中。默认进入第一个为选中状态。
2)VIewPager实现
首先实现ViewPager的PagerAdapter部分:
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);
}
}
之后:
for (int i = 0; i <= count + 1; i++) {
View imageView = null;
if (imageLoader != null) {
imageView = imageLoader.createImageView(context);
}
if (imageView == null) {
imageView = new ImageView(context);
}
setScaleType(imageView);
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);
}
imageViews.add(imageView);
if (imageLoader != null)
imageLoader.displayImage(context, url, imageView);
else
Log.e(tag, "Please set images loader.");
}
currentItem = 1;
if (adapter == null) {
adapter = new BannerPagerAdapter();
viewPager.addOnPageChangeListener(this);
}
viewPager.setAdapter(adapter);
viewPager.setFocusable(true);
viewPager.setCurrentItem(1);
if (gravity != -1)
indicator.setGravity(gravity);
if (isScroll && count > 1) {
viewPager.setScrollable(true);
} else {
viewPager.setScrollable(false);
}
4.ViewPager轮播
播放方式分自动播放和滑动播放:
1)自动播放方式:
handler.postDelayed(task, delayTime);
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);
}
}
}
};
2)滑动播放因VIewPager自带了滑动播放功能,所以这里不需要实现。
当然有播放肯定也需要释放:handler.removeCallbacks(task);
可以在
MotionEvent.ACTION_DOWN的时候调用 stopAutoPlay();
在MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL|| action == MotionEvent.ACTION_OUTSIDE的时候调用StartAutoPlay();
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Log.i(tag, ev.getAction() + "--" + isAutoPlay);
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);
}
5.指示器轮播
@Override
public void onPageSelected(int position) {
currentItem=position;
if (mOnPageChangeListener != null) {
mOnPageChangeListener.onPageSelected(toRealPosition(position));
}
if (bannerStyle == BannerConfig.CIRCLE_INDICATOR ||
bannerStyle == BannerConfig.CIRCLE_INDICATOR_TITLE ||
bannerStyle == BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE) {
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;
switch (bannerStyle) {
case BannerConfig.CIRCLE_INDICATOR:
break;
case BannerConfig.NUM_INDICATOR:
numIndicator.setText(position + "/" + count);
break;
case BannerConfig.NUM_INDICATOR_TITLE:
numIndicatorInside.setText(position + "/" + count);
bannerTitle.setText(titles.get(position - 1));
break;
case BannerConfig.CIRCLE_INDICATOR_TITLE:
bannerTitle.setText(titles.get(position - 1));
break;
case BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE:
bannerTitle.setText(titles.get(position - 1));
break;
}
}
5.外部调用接口
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 setIndicatorGravity(int type) {
switch (type) {
case BannerConfig.LEFT:
this.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
break;
case BannerConfig.CENTER:
this.gravity = Gravity.CENTER;
break;
case BannerConfig.RIGHT:
this.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
break;
}
return this;
}
public Banner setBannerAnimation(Class<? extends PageTransformer> transformer) {
try {
setPageTransformer(true, transformer.newInstance());
} catch (Exception e) {
Log.e(tag, "Please set the PageTransformer class");
}
return this;
}
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* @param limit How many pages will be kept offscreen in an idle state.
* @return Banner
*/
public Banner setOffscreenPageLimit(int limit) {
if (viewPager != null) {
viewPager.setOffscreenPageLimit(limit);
}
return this;
}
/**
* Set a {@link PageTransformer} that will be called for each attached page whenever
* the scroll position is changed. This allows the application to apply custom property
* transformations to each page, overriding the default sliding look and feel.
*
* @param reverseDrawingOrder true if the supplied PageTransformer requires page views
* to be drawn from last to first instead of first to last.
* @param transformer PageTransformer that will modify each page's animation properties
* @return Banner
*/
public Banner setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
viewPager.setPageTransformer(reverseDrawingOrder, transformer);
return this;
}
public Banner setBannerTitles(List<String> titles) {
this.titles = titles;
return this;
}
public Banner setBannerStyle(int bannerStyle) {
this.bannerStyle = bannerStyle;
return this;
}
public Banner setViewPagerIsScroll(boolean isScroll) {
this.isScroll = isScroll;
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.titles.clear();
this.titles.addAll(titles);
update(imageUrls);
}
public void update(List<?> imageUrls) {
this.imageUrls.clear();
this.imageViews.clear();
this.indicatorImages.clear();
this.imageUrls.addAll(imageUrls);
this.count = this.imageUrls.size();
start();
}
public void updateBannerStyle(int bannerStyle) {
indicator.setVisibility(GONE);
numIndicator.setVisibility(GONE);
numIndicatorInside.setVisibility(GONE);
indicatorInside.setVisibility(GONE);
bannerTitle.setVisibility(View.GONE);
titleView.setVisibility(View.GONE);
this.bannerStyle = bannerStyle;
start();
}
public Banner start() {
setBannerStyleUI();
setImageList(imageUrls);
setData();
return this;
}
最后着重讲下里面一个反射的使用,非常经典:
private void initViewPagerScroll() {
try {
Field mField = ViewPager.class.getDeclaredField("mScroller");
mField.setAccessible(true);
mScroller = new BannerScroller(viewPager.getContext());
mScroller.setDuration(scrollTime);
mField.set(viewPager, mScroller);
} catch (Exception e) {
Log.e(tag, e.getMessage());
}
}
通过反射获取到ViewPager里面的mScroller,使用新的BannerScroller替换,实现偷天换日,可以自动设置和ViewPager滑动相关的参数。屡试不爽哟。