XBanner源码详解

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的实现原理了。

你可能感兴趣的:(前端,android源码分析,动画,前端,XBanner,图片轮播android)