自定义一个轮播图开源库
自定义一个轮播图开源库
一、前言
平时用惯了第三方开源库,也很喜欢AS导入项目的便捷性。但是今天玩点不一样的。自己撸个开源库试试看呢,以后用起来也方便。开工!!!

二、建库
- File——>New——>New Model......

- 选择Android Library ,然后点击Next。

- 设置Library的名称,然后点击Finish。

- 这样一个Library就创建完成了。

三、编辑
Library已经创建完成了,下面我们也该开始编写我们的代码了吧!
这次写的是一个简单的广告栏控件,其主要功能有循环滚动播放图片,地洞滚动以及翻页效果,支持第三方图片库的加载方式,可以设置点击事件等等。
现在广告栏的开源库有很多,但是都不太适用我现在的项目。此控件采用ViewPager+ImageView+LinearLayout的方式来实现的,通过View Pager的setCurrentItem()方法来设置当前的显示页面来实现循环翻页。
3.1、自定义ViewPager
自定义的ViewPager继承自V4包下的ViewPager类,主要设置各种事件以及对事件的操作。

在初始化方法中设置addOnPageChangeListener()监听器,而不是使用已经被废除了的setOnPageChangeListener()。在这个监听器中主要设置Scrolled和Selected的监听,主要是对用户设置了的OnPageChangeListener进行回调。
private void init() {
super.addOnPageChangeListener(onPageChangeListener);
}
//页面改变的监听器
private OnPageChangeListener onPageChangeListener=new OnPageChangeListener() {
private float mPreviousPosition = -1;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int mRealPosition = position;
if (null != mOuterPageChangeListener) {
//如果position不是最后一个直接回调
if (mAdapter.getRealCount() - 1 != mRealPosition) {
mOuterPageChangeListener.onPageScrolled(mRealPosition, positionOffset, positionOffsetPixels);
} else {
if (positionOffset > 0.5f) {
mOuterPageChangeListener.onPageScrolled(0, 0, 0);
} else {
mOuterPageChangeListener.onPageScrolled(mRealPosition,0,0);
}
}
}
}
@Override
public void onPageSelected(int position) {
//转换为实际的position
int mRealPosition = mAdapter.getRealPosition(position);
if (mPreviousPosition != mRealPosition) {
mPreviousPosition = mRealPosition;
//如果设置了PageChangeListener就调用onPageSelected方法
if (null != mOuterPageChangeListener) {
mOuterPageChangeListener.onPageSelected(mRealPosition);
}
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (null != mOuterPageChangeListener) {
mOuterPageChangeListener.onPageScrollStateChanged(state);
}
}
};
重写onInterceptTouchEvent方法和onTouchEvent方法,来实现对用户单击事件的处理。
/**
* 事件拦截
* @param ev
* @return
*/
Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isCanScroll) {
return super.onInterceptTouchEvent(ev);
} else {
return false;
}
}
/**
* 触摸事件
* @param ev
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isCanScroll) {
if (null != mOnItemClickListener) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldX = ev.getX();
break;
case MotionEvent.ACTION_UP:
newX = ev.getX();
if (Math.abs(oldX - newX) < sens) {
mOnItemClickListener.onItemClick(getRealItem());
}
oldX = 0;
newX = 0;
break;
}
}
return super.onTouchEvent(ev);
} else {
return false;
}
}
要让我的自定义View Pager能够完美运行起来,肯定是少不了适配器的,还需要设置适配器,也可以设置一下循环的开关。
/**
* 设置适配器
* @param mAdapter
*/
public void setAdapter(BannerPageAdapter mAdapter,boolean mIsLoop) {
this.mAdapter = mAdapter;
mAdapter.setLoop(mIsLoop);
mAdapter.setViewPager(this);
super.setAdapter(mAdapter);
//设置当前的Item为页面的数量
setCurrentItem(getItem(),false);
}
/**
* 设置是否循环翻页
* @param mLoop
*/
public void setLoop(boolean mLoop) {
isLoop = mLoop;
if (!isLoop) {
setCurrentItem(getRealItem(),false);
}
if (null == mAdapter) {
return;
}
mAdapter.setLoop(isLoop);
mAdapter.notifyDataSetChanged();
}
3.2、自定义ViewPagerAdapter
新建一个BannerPageAdapter,让其继承自PagerAdapter。
public class BannerPageAdapter<T> extends PagerAdapter
通过重写finishUpdate来实现循环翻页,通过setCurrentItem来设置当前的项目实现循环翻页。
/**
* 当所有页面中的更改已完成时调用。
* 此时必须确保所有页面都已实际添加或从容器中删除。
* 根据当前item设置实际的item实现无限循环。
* @param container
*/
@Override
public void finishUpdate(ViewGroup container) {
int position = mViewPager.getCurrentItem();
if (0 == position) {
position = mViewPager.getItem();
} else if (getCount() - 1 == position) {
position = mViewPager.getLastItem();
}
mViewPager.setCurrentItem(position, false);
}
重写instantiateItem,这个方法有点类似于List View中的getView方法,用于创建子Item。
/**
* 创建item
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
int mRealPosition = getRealPosition(position);
View mView = getView(mRealPosition, null, container);
container.addView(mView);
return mView;
}
/**
* 获取item视图
* @param position
* @param mView
* @param mGroup
* @return
*/
public View getView(int position,View mView,ViewGroup mGroup){
BannerHolder mHolder = null;
if (null == mView) {
mHolder = (BannerHolder) mHolderCreator.createHolder();
mView = mHolder.createView(mGroup.getContext());
mView.setTag(R.id.banner_item_tag, mHolder);
} else {
mHolder = (BannerHolder<T>) mView.getTag(R.id.banner_item_tag);
}
if (null != mList && !mList.isEmpty()) {
mHolder.UpdateUI(mGroup.getContext(),position,mList.get(position));
}
return mView;
}
重写getCount来设置翻页的最大数量是多少,我设置的是500。这个数值可以自行设置,你觉得好就好。
//翻页的最大量
private final int MAX_COUNT = 500;
/**
* 设置Count
* @return
*/
@Override
public int getCount() {
return isLoop?getRealCount()* MAX_COUNT :getRealCount() ;
}
/**
* 获取实际的翻页数量
* @return
*/
public int getRealCount(){
return mList == null ? 0 : mList.size();
}
创建两个接口BannerHolder和BannerHolderCreator。BannerHolder用于创建页面和更新页面内容;BannerHolderCreator用于创建BannerHolder。
public interface BannerHolder<T> {
View createView(Context mContext);
void UpdateUI(Context mContext, int position, T data);
}
public interface BannerHolderCreator {
public BannerHolder createHolder();
}
因为要让页面无限循环,所以页面的position并不是我们需要的实际的position,因此自定义的适配器还应该有一个将position转换为当前实际的position的方法,通过这个方法来获取实际的位置。
/**
* 获取实际的position
* @param position
* @return
*/
public int getRealPosition(int position){
int mRealCount = getRealCount();
if (0 == mRealCount) {
return 0;
}
int mRealPosition = position % mRealCount;
return mRealPosition;
}
3.3、自定义Linear Layout
View Pager有了,肯定得有个东西装它啊,不然下面的指示器怎么出来呢?
public class BannerView<T> extends LinearLayout
三个构造函数
public BannerView(Context context) {
super(context);
init(context);
}
public BannerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
肯定还要有能够实现自动滚动的线程。
/**
* 自动滚动线程
* 使用WeakReference防止内存泄漏的问题
*/
static class AdSwitchTask implements Runnable{
private final WeakReference<BannerView> mReference;
AdSwitchTask(BannerView mBannerView) {
this.mReference = new WeakReference<BannerView>(mBannerView);
}
@Override
public void run() {
BannerView mBannerView = mReference.get();
if (null != mBannerView) {
if (null != mBannerView.mViewPager && mBannerView.turning) {
int page = mBannerView.mViewPager.getCurrentItem() + 1;
mBannerView.mViewPager.setCurrentItem(page);
mBannerView.postDelayed(mBannerView.mAdSwitchTask, mBannerView.autoTurningTime);
}
}
}
}
对指示器的操作主要如下所示:
/**
* 底部指示器资源图片
* @param page_indicatorId
* @return
*/
public BannerView setPagerIndicator(int[] page_indicatorId){
loPageTurningPoint.removeAllViews();
mPointViews.clear();
this.page_indicatorId = page_indicatorId;
if (null == mDatas) {
return this;
}
for (int i = 0; i < mDatas.size(); i++) {
//翻页指示的点
ImageView mImageView = new ImageView(getContext());
mImageView.setPadding(5,0,5,0);
if (mPointViews.isEmpty()) {
mImageView.setImageResource(page_indicatorId[1]);
} else {
mImageView.setImageResource(page_indicatorId[0]);
}
mPointViews.add(mImageView);
loPageTurningPoint.addView(mImageView);
}
mBannerPageChangeListener = new BannerPageChangeListener(page_indicatorId, mPointViews);
mViewPager.addOnPageChangeListener(mBannerPageChangeListener);
mBannerPageChangeListener.onPageSelected(mViewPager.getRealItem());
if (null != mBannerPageChangeListener) {
mBannerPageChangeListener.addOnPageChangeListener(onPageChangeListener);
}
return this;
}
/**
* 设置底部指示器是否可见
* @param visible
* @return
*/
public BannerView setPointViewVisible(boolean visible){
loPageTurningPoint.setVisibility(visible?VISIBLE:GONE);
return this;
}
/**
* 指示器的方向
* @param mAlign
* @return
*/
public BannerView setPageIndicatorAlign(PageIndicatorAlign mAlign){
RelativeLayout.LayoutParams mLayoutParams = (RelativeLayout.LayoutParams) loPageTurningPoint.getLayoutParams();
mLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, mAlign == PageIndicatorAlign.ALIGN_PARENT_LEFT ? RelativeLayout.TRUE : 0);
mLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, mAlign == PageIndicatorAlign.ALIGN_PARENT_RIGHT ? RelativeLayout.TRUE : 0);
mLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL, mAlign == PageIndicatorAlign.CENTER_HORIZONTAL ? RelativeLayout.TRUE : 0);
loPageTurningPoint.setLayoutParams(mLayoutParams);
return this;
}
重写dispatchTouchEvent用于拦截事件,实现当用户手指滑动的时候停止自动翻页,停止滑动继续自动翻页。
/**
* 触碰控件的时候,翻页应该停止
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int mAction = ev.getAction();
if (MotionEvent.ACTION_UP == mAction || MotionEvent.ACTION_CANCEL == mAction || MotionEvent.ACTION_OUTSIDE == mAction) {
//开始翻页
if (autoTurn) {
startTurning(autoTurningTime);
}
} else if (MotionEvent.ACTION_DOWN==mAction) {
//停止翻页
if (autoTurn) {
stopTurning();
}
}
return super.dispatchTouchEvent(ev);
}
还有一些其他的方法
/**
* 设置Item的点击事件
* @param mOnItemClickListener
* @return
*/
public BannerView setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
if (null == mOnItemClickListener) {
mViewPager.setOnItemClickListener(null);
return this;
}
mViewPager.setOnItemClickListener(mOnItemClickListener);
return this;
}
/**
* 设置翻页数据
* @param mDatas
* @param mCreator
* @return
*/
public BannerView setDatas(List<T> mDatas, BannerHolderCreator mCreator) {
this.mDatas = mDatas;
mAdapter = new BannerPageAdapter(mDatas, mCreator);
mViewPager.setAdapter(mAdapter,canLoop);
if (null != page_indicatorId) {
setPagerIndicator(page_indicatorId);
}
return this;
}
/**
* 自定义翻页动画效果
* @param mTransformer
* @return
*/
public BannerView<T> setPageTransformer(ViewPager.PageTransformer mTransformer) {
mViewPager.setPageTransformer(true,mTransformer);
return this;
}
3.4基本调用方法
private BannerView mBannerView;
private List<Integer> mDatas = new ArrayList<>();
mDatas.add(R.drawable.banner1);
mDatas.add(R.drawable.banner2);
mDatas.add(R.drawable.banner3);
mBannerView = (BannerView) findViewById(R.id.banner);
mBannerView.setDatas(mDatas, new BannerHolderCreator() {
@Override
public BannerHolder createHolder() {
return new LocalImageHolderView();
}
}).startTurning(3000)//设置翻页时间
.setPageTransformer(new OutPageTransFormer())//设置翻页动画
.setPageIndicatorAlign(BannerView.PageIndicatorAlign.CENTER_HORIZONTAL)//设置指示器方向
.setPagerIndicator(new int[]{R.drawable.dot_blur,R.drawable.dot_focus});//设置指示器图片
//设置点击事件
mBannerView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(int position) {
Toast.makeText(MainActivity.this, "你点击的是第"+position+"个Item", Toast.LENGTH_SHORT).show();
}
});
四、上传代码
将已经写好的代码提交到GitHub上,怎么将AS配置GitHub请移步我的另一篇文章给你的Android Studio配置上GitHub吧!!!
可以更快捷的上传您的代码。
五、配置相关内容
5.1在项目的根路径下的build.gradle中添加classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'

5.2在library中的build.gradle中添加
apply plugin: 'com.github.dcendents.android-maven'
group='com.github.YourUsername'’

5.3配置后以后将代码push到GitHub
六、新建release
6.1进入到GitHub上的项目中,点击release

6.2点击Create a new release

6.3填写版本信息,然后点击public release

七、将自己的library上传到jitPack.io
7.1复制项目地址;

7.2打开jitPack.io的官网,将项目地址粘贴到输入框中,然后点击look up;

7.3现在已经可以看到自己创建的release了,点击get it。然后就会定位到How to;

7.4按照How to中的方式进行配置即可。
GitHub地址:https://github.com/HunterArley/BannerView
八、第一次写库,可能有不对的地方,请指教,谢谢!!!最后打个广告,欢迎大家关注我的微信公众号,谢谢。
