XBanner作为经典的轮播器,相比大多数人都用过,它即可以作为图片广告轮播,又可以一页展示多图,还可以自定义子View,可谓是用法多多。
如果想自己写一个轮播图只需实现三步就可以:1、第一步既然可以轮播那么现成的左右滑动就选
ViewPager或ViewPager2 2、轮播的点或者数字其他样式怎么放,这个就需要动态的添加控件,形状完全交给用的人来控制,这只需要声明一个横向线性布局动态添加ImageView即可。3、就是实现自动切换了,搞一个Handler每隔几秒切换ViewPager的位置即可,至于几秒全部交给用户来配置,用自定义属性就行。
XBanner的源码地址XBanner,感兴趣的小伙伴可以自行查阅源码,好了现在进入正题,在设置完属性之后,就是给XBanner设置值,将值转化为控件肯定会存在适配器模式,设置值的方法如下:
public void setData(@LayoutRes int layoutResId, @NonNull List> models, List tips) {
if (models == null) {
models = new ArrayList<>();
}
if (models.isEmpty()) {
mIsAutoPlay = false;
mIsClipChildrenMode = false;
}
if (!mIsClipChildrenModeLessThree && models.size() < 3) {
mIsClipChildrenMode = false;
}
this.layoutResId = layoutResId;
this.mDatas = models;
this.mTipData = tips;
mIsOneImg = models.size() == 1;
initPoints();
initViewPager();
removeBannerPlaceHolderDrawable();
if (!models.isEmpty()) {
removeBannerPlaceHolderDrawable();
} else {
setBannerPlaceholderDrawable();
}
}
其中 initPoints()和 initViewPager()方法就是分别设置导航的点和内容页的数据的方法, initPoints实现如下:
private void initPoints() {
if (mPointRealContainerLl != null) {
mPointRealContainerLl.removeAllViews();
//当图片多于1张时添加指示点
if (getRealCount() > 0 && (mIsShowIndicatorOnlyOne || !mIsOneImg)) {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LWC, LWC);
lp.gravity = Gravity.CENTER_VERTICAL;
lp.setMargins(mPointLeftRightPading, mPointTopBottomPading, mPointLeftRightPading, mPointTopBottomPading);
ImageView imageView;
for (int i = 0; i < getRealCount(); i++) {
imageView = new ImageView(getContext());
imageView.setLayoutParams(lp);
if (mPointNoraml != 0 && mPointSelected != 0) {
imageView.setImageResource(mPointNoraml);
}
mPointRealContainerLl.addView(imageView);
}
}
}
if (mNumberIndicatorTv != null) {
if (getRealCount() > 0 && (mIsShowIndicatorOnlyOne || !mIsOneImg)) {
mNumberIndicatorTv.setVisibility(View.VISIBLE);
} else {
mNumberIndicatorTv.setVisibility(View.GONE);
}
}
}
initViewPager实现如下:
private void initViewPager() {
Log.d("zhc", "initViewPager");
if (mViewPager != null && this.equals(mViewPager.getParent())) {
this.removeView(mViewPager);
mViewPager = null;
}
currentPos = 0;
mViewPager = new XBannerViewPager(getContext());
mViewPager.setAdapter(new XBannerPageAdapter());
mViewPager.clearOnPageChangeListeners();
mViewPager.addOnPageChangeListener(this);
mViewPager.setOverScrollMode(mSlideScrollMode);
mViewPager.setIsAllowUserScroll(mIsAllowUserScroll);
mViewPager.setPageTransformer(true, BasePageTransformer.getPageTransformer(mTransformer));
setPageChangeDuration(mPageChangeDuration);
LayoutParams layoutParams = new LayoutParams(RMP, RMP);
layoutParams.setMargins(0, 0, 0, mBannerBottomMargin);
if (mIsClipChildrenMode) {
setClipChildren(false);
mViewPager.setClipToPadding(false);
mViewPager.setClipChildren(false);
mViewPager.setPadding(mClipChildrenLeftMargin, mClipChildrenTopBottomMargin, mClipChildrenRightMargin, mBannerBottomMargin);
mViewPager.setPageMargin(mViewPagerMargin);
}
addView(mViewPager, 0, layoutParams);
/*当图片多于1张时开始轮播*/
if (!mIsOneImg && mIsAutoPlay && getRealCount() != 0) {
currentPos = (MAX_VALUE / 2 - (MAX_VALUE / 2) % getRealCount()) + 1;
mViewPager.setCurrentItem(currentPos);
mViewPager.setAutoPlayDelegate(this);
startAutoPlay();
} else {
if (mIsHandLoop && getRealCount() != 0) {
currentPos = (MAX_VALUE / 2 - (MAX_VALUE / 2) % getRealCount()) + 1;
mViewPager.setCurrentItem(currentPos);
}
switchToPoint(0);
}
}
很明显,这个库也是用的传统方法,XBannerViewPager 继承自ViewPager,接下来看一下它是怎么把数据适配给用户来做的,它内部设置了mViewPager.setAdapter(new XBannerPageAdapter()),其中XBannerPageAdapter继承自PagerAdapter,它在instantiateItem方法中调用了自己自定义的adapter成功将adpter交给用户来管理
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (getRealCount() == 0) {
return null;
}
final int realPosition = getRealPosition(position);
final View view = LayoutInflater.from(getContext()).inflate(layoutResId, container, false);
if (mOnItemClickListener != null && !mDatas.isEmpty()) {
view.setOnClickListener(new OnDoubleClickListener() {
@Override
public void onNoDoubleClick(View v) {
if (isCanClickSide) {
setBannerCurrentItem(realPosition, true);
}
mOnItemClickListener.onItemClick(XBanner.this, mDatas.get(realPosition), v, realPosition);
}
});
}
if (null != mAdapter && !mDatas.isEmpty()) {
mAdapter.loadBanner(XBanner.this, mDatas.get(realPosition), view, realPosition);
}
container.addView(view);
return view;
}
而getCount方法是用来设置是否无线轮播,自己写的话设置足够大的值自己取余就行了,代码中做法差不多,如下所示:
public int getCount() {
/*当只有一张图片时返回1*/
if (mIsOneImg) {
return 1;
}
return mIsAutoPlay ? MAX_VALUE : (mIsHandLoop ? MAX_VALUE : getRealCount());
}
private int getRealPosition(int position) {
if (getRealCount() == 0) {
return 0;
}
if (mIsAutoPlay || mIsHandLoop) {
return (position - 1 + getRealCount()) % getRealCount();
} else {
return (position + getRealCount()) % getRealCount();
}
}
而多页显示的方式就是下面的代码:让子View可以超过父view的位置显示
if (mIsClipChildrenMode) {
setClipChildren(false);
mViewPager.setClipToPadding(false);
mViewPager.setClipChildren(false);
mViewPager.setPadding(mClipChildrenLeftMargin, mClipChildrenTopBottomMargin, mClipChildrenRightMargin, mBannerBottomMargin);
mViewPager.setPageMargin(mViewPagerMargin);
}
也是通用的写法,最后再来看一下界面切换的动画是怎么搞的,代码如下所示:
public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) {
/**
继承ViewPager,重写setPageTransformer方法,移除版本限制,通过反射设置参数和方法
getDeclaredMethod*()获取的是【类自身】声明的所有方法,包含public、protected和private方法。
getMethod*()获取的是类的所有共有方法,这就包括自身的所有【public方法】,和从基类继承的、从接口实现的所有【public方法】。
getDeclaredField获取的是【类自身】声明的所有字段,包含public、protected和private字段。
getField获取的是类的所有共有字段,这就包括自身的所有【public字段】,和从基类继承的、从接口实现的所有【public字段】。
*/
Class viewpagerClass = ViewPager.class;
try {
boolean hasTransformer = transformer != null;
Field pageTransformerField = viewpagerClass.getDeclaredField("mPageTransformer");
pageTransformerField.setAccessible(true);
PageTransformer mPageTransformer = (PageTransformer) pageTransformerField.get(this);
boolean needsPopulate = hasTransformer != (mPageTransformer != null);
pageTransformerField.set(this, transformer);
Method setChildrenDrawingOrderEnabledCompatMethod = viewpagerClass.getDeclaredMethod("setChildrenDrawingOrderEnabledCompat", boolean.class);
setChildrenDrawingOrderEnabledCompatMethod.setAccessible(true);
setChildrenDrawingOrderEnabledCompatMethod.invoke(this, hasTransformer);
Field drawingOrderField = viewpagerClass.getDeclaredField("mDrawingOrder");
drawingOrderField.setAccessible(true);
if (hasTransformer) {
drawingOrderField.setInt(this, reverseDrawingOrder ? 2 : 1);
} else {
drawingOrderField.setInt(this, 0);
}
if (needsPopulate) {
Method populateMethod = viewpagerClass.getDeclaredMethod("populate");
populateMethod.setAccessible(true);
populateMethod.invoke(this);
}
} catch (Exception e) {
}
}
就是复写了ViewPager.PageTransformer实现的,好了这就是XBanner的实现原理了。