banner 轮播图

banner 轮播图控件

自定义组合控件实现轮播图,内部可以用recycleview或者viewpage实现,其主要需要完成的有三个点:
  1. 无限循环方式的实现
  2. 指示器的实现
  3. item view的transform操作
第一个实现无限循环的方式:

内部用viewpager实现轮播的话,可以在数据集合的大小方面做处理。一般做法是在第一个数据之前加上最后一条数据,最后一条数据之后再加上第一条数据,即数据集合实际大小为size + 2。然后再监听到position为0 或者 是最后一条数据时,移动currentItem至实际位置。这样做存在一个问题,再重新移动currentItem时,对应的移动监听会回调两次。
网上大部分的写法是,集合大小给一个较大的值,例如几百,然后再处理position,这样不会需要经常触发setCurrentItem。
adapter 中 重写getItemCount() 方法

override fun getItemCount(): Int {
        return if (isCanLoop) BannerUtils.MAX_VALUE_SIZE else dataList.size
    }

在select监听中处理:

override fun onPageSelected(position: Int) {
          super.onPageSelected(position)
          val realPosition = BannerUtils.getRealPosition(position,mBannerSize)
          if (position == 0 || position == BannerUtils.MAX_VALUE_SIZE - 1) {
              resetCurrentItem(realPosition)  // 重新移动至中间位置
          }
          if (inDecorateViews.isNotEmpty()) {  // 设置指示器 选中状态
              inDecorateViews[currentPosition].isSelected = false
              inDecorateViews[realPosition].isSelected = true
          }
          currentPosition = realPosition
          pageChangeCallback?.onPageSelected(realPosition)
      }
private fun resetCurrentItem(item: Int) {
        mViewPager.setCurrentItem(getResetItem(item,adapter.dataList.size),false)
    }

    private fun getResetItem(item: Int, pageSize: Int) : Int {  // 轮播从中间部分开始
        return if (!isCanLoop || pageSize <=  0) item
        else BannerUtils.MAX_VALUE_SIZE / 2 - BannerUtils.MAX_VALUE_SIZE / 2 % pageSize + item
    }

内部用recycleview实现,可以重写layoutManager以实现无限循环.
layoutManager 主要工作是

1.测量布局子view

测量布局itemView 主要在方法onLayoutChilder 里操作。
通过 addView方法添加view
measureChildWithMargins 方法测量itemView
layoutDecorated 方法布局itemView。

2.滚动事件的处理

在 canScrollHorizontally 方法判断是否可以横向滚动。
在 scrollHorizontallyBy 方法里处理滚动事件。第一个参数为正为向右滚动,
为负为向左滚动。
正常操作滑动至边界后,返回0不在移动。
可以在这里处理当滑动至边界后,重新取第一个view,继续布局。

final View scrap = recycler.getViewForPosition(adapterPosition);
            measureChildWithMargins(scrap, 0, 0);
            resetViewProperty(scrap);
            // we need i to calculate the real offset of current view
            final float targetOffset = getProperty(i) - mOffset;
            layoutScrap(scrap, targetOffset);
            final float orderWeight = mEnableBringCenterToFront ?
                    setViewElevation(scrap, targetOffset) : adapterPosition;
            if (orderWeight > lastOrderWeight) {
                addView(scrap);
            } else {
                addView(scrap, 0);
            }
            if (i == currentPos) currentFocusView = scrap;
            lastOrderWeight = orderWeight;
            positionCache.put(i, scrap);  // 缓存view
3.缓存并重用子view

layoutManager中 用recycle 来缓存itemView。

第二个指示器的实现:

一般是自定义的banner内加一个LinearLayout,再在里面添加View。
因为需要viewpager 的adapter 中数据集合的大小确定实际view的个数,所以需要在设置了adapter数据之后,再设置指示器。

private fun initInDecoration(drawables: Array<Drawable>) {  // 指示器
    val inLinear = LinearLayout(context)
    inLinear.orientation = inDecorateOrientation
    val params = LayoutParams(-2,-2).apply {
        this.gravity = inDecorateGravity
        bottomMargin = inDecorateBtMargin
        topMargin = inDecorateTopMargin
        leftMargin = inDecorateLgMargin
        rightMargin = inDecorateRgMargin
    } // 自适应布局
    addInDecorateIv(inLinear,drawables)
    if (inDecorateViews.size > currentPosition) inDecorateViews[currentPosition].isSelected = true
    addView(inLinear,params)
}

private fun addInDecorateIv(ll: LinearLayout,drawable: Array<Drawable>) {
    drawable.forEach {
        val iv = ImageView(context)
        iv.background = it
        val params = LinearLayout.LayoutParams(-2,-2).apply {
            rightMargin = decorateRgMargin
        }
        inDecorateViews.add(iv)
        ll.addView(iv,params)
    }
}

设置指示器view的background,默认用一个圆点显示,创建drawable的代码:

fun createDrawable(color: Int,pointSize: Int) : Drawable{
        val drawable = GradientDrawable()
        drawable.setColor(color)
        drawable.setBounds(0,0, pointSize, pointSize)
        drawable.shape = GradientDrawable.OVAL
        drawable.cornerRadius = (pointSize / 2).toFloat()
        drawable.setSize(pointSize,pointSize)
        return drawable
    }
第三个transform的实现:

这个其实只需要回调viewpager2的设置transform的方法就行了。
设置transform 需要重写transform方法,
第一个参数:滑动的itemView。
第二个参数:position:view移动偏移量。
范围:从-∞ 到 +∞。
主要根据position,对itemView进行一些缩放平移操作。

when{
            position <= -1 -> {  // page 已滑至最左边
                page.apply {
//                    scaleX = MIN_SCALE
                    scaleY = MIN_SCALE
//                    rotationY = rotate
                }
            }
            position < 0 -> {  // page 逐渐向左滑动  position : -1 -> 0 scale : 0.8 -> 1  position 为0 则滑至中间位置
                page.apply {
//                    scaleX = scale
                    scaleY = MIN_SCALE + (1 + position) * (1 - MIN_SCALE)
//                    rotationY = rotate
                }
            }
            position < 1 -> {  // page 逐渐向右滑动  0 -> 1   1 -> 0.8
                page.apply {
//                    scaleX = scale
                    scaleY = MIN_SCALE + (1 - position) * (1 - MIN_SCALE)
//                    rotationY = -rotate
                }
            }
            else -> {  // page 位于最右边
                page.apply {
//                    scaleX = scale
                    scaleY = MIN_SCALE
//                    rotationY = -rotate
                }
            }

在viewpager 中有一个设置pageMargin的方法。在viewpager2中没有,
想要设置它的话,可以通过在transform中设置x轴平移来完成。

val offset = position * pageMargin
    // 如果布局方向为RTL  则为 -offset
    // viewpager2  中没有设置pagerMargin 得方法  用这个方法设置view 得偏移量,作为page 得间距
    page.translationX = offset           

项目代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值