收藏夹大牛聚集之地Android知识

自定义一个轮播图开源库

2017-07-12  本文已影响154人  程序员丶星霖

自定义一个轮播图开源库

一、前言

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

努力爬坑.jpg

二、建库

  1. File——>New——>New Model......
新建Model.png
  1. 选择Android Library ,然后点击Next。
创建Android Library.png
  1. 设置Library的名称,然后点击Finish。
设置名称.png
  1. 这样一个Library就创建完成了。
创建完成.png

三、编辑

Library已经创建完成了,下面我们也该开始编写我们的代码了吧!

这次写的是一个简单的广告栏控件,其主要功能有循环滚动播放图片,地洞滚动以及翻页效果,支持第三方图片库的加载方式,可以设置点击事件等等。

现在广告栏的开源库有很多,但是都不太适用我现在的项目。此控件采用ViewPager+ImageView+LinearLayout的方式来实现的,通过View Pager的setCurrentItem()方法来设置当前的显示页面来实现循环翻页。

3.1、自定义ViewPager

自定义的ViewPager继承自V4包下的ViewPager类,主要设置各种事件以及对事件的操作。

构造函数.png

在初始化方法中设置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'
根目录添加配置.png
5.2在library中的build.gradle中添加
apply plugin: 'com.github.dcendents.android-maven'  

 group='com.github.YourUsername'’
library中添加配置.png
5.3配置后以后将代码push到GitHub

六、新建release

6.1进入到GitHub上的项目中,点击release
release.png
6.2点击Create a new release
新建Release.png
6.3填写版本信息,然后点击public release
填写相关内容.png

七、将自己的library上传到jitPack.io

7.1复制项目地址;
复制项目地址.png
7.2打开jitPack.io的官网,将项目地址粘贴到输入框中,然后点击look up;
打开官网.png
7.3现在已经可以看到自己创建的release了,点击get it。然后就会定位到How to;
使用方法.png
7.4按照How to中的方式进行配置即可。

GitHub地址:https://github.com/HunterArley/BannerView

八、第一次写库,可能有不对的地方,请指教,谢谢!!!最后打个广告,欢迎大家关注我的微信公众号,谢谢。

二维码.jpg
上一篇 下一篇

猜你喜欢

热点阅读