高仿知乎日报无限轮播图+指示符切换动画效果

4,065 阅读2分钟

前言

最近抽时间模仿实现了知乎日报的无限轮播图和指示符切换动画效果_^_,数据来源于知乎日报API

动画分析

  • 未选中页面对应的指示符宽度小,形状为圆形,选中页面对应的指示符宽度大,形状为椭圆。
  • 当页面A->B,A页面对应的指示符宽度由大->小,B页面对应的指示符宽度由小->大,指示符宽度和颜色随滑动而不断变化。
  • 当从最后一个页面手指向右滑动,页面向左时,最后一个页面对应的指示符宽度由大->小,第一个页面对应的指示符宽度由小->大,其余指示符向右平移,有联动效果。
  • 当从第一个页面手指向左滑动,页面向右时,第一个页面对应的指示符宽度由大->小,最后一个对应的指示符宽度由小->大,其余指示符向左平移,有联动效果。

代码实现

TKBanner

实现无限轮播图功能,默认支持圆点和数字指示符。

仿知乎日报APP轮播图仿品玩APP轮播图仿虎嗅APP轮播图
banner_zhihu_daily.webpbanner_pingwest.webpbanner_huxiu.webp

CuteIndicator

自定义View,实现ViewPager页面滑动过程中指示符切换动画效果。

相关属性

属性说明默认值
IndicatorColor未选中页面对应的指示符颜色Color.GRAY
IndicatorSelectedColor选中页面对应的指示符颜色Color.WHITE
IndicatorWidth未选中页面对应的指示符宽度5dp
IndicatorSelectedWidth选中页面对应的指示符宽度20dp
IndicatorHeight指示符高度5dp
IndicatorMargin指示符之间的间隔5dp
IndicatorShowAnimation是否显示指示符切换动画true

关键代码

  • 覆盖onMeasure方法,计算设置指示符宽度和高度。目前没有根据测量模式去判断计算,简单的计算指示符总宽度=(指示符数量-1)*(未选中指示符宽度+指示符之间的间隔)+选中指示符的宽度,此处还有很多优化空间。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec)
     if(mIndicatorCount>0) {
        val width = ((mIndicatorCount - 1) * (mIndicatorMargin + mIndicatorWidth) + mIndicatorSelectedWidth).toInt()
        setMeasuredDimension(width, mIndicatorHeight.toInt())
     }
}
  • 覆盖onDraw方法,绘制指示符,针对第一个页面和最后一个页面滑动时特别处理。
override fun onDraw(canvas: Canvas) {
     super.onDraw(canvas)
     if (mIndicatorCount <= 0) {
         return
     }
     var left=0f
     var right=0f
     if (position == (mIndicatorCount - 1) && positionOffset > 0f) {
         for (i in 0 until mIndicatorCount) {
           if(i==0){
              left=0f
              right=left+mIndicatorWidth+(mIndicatorSelectedWidth - mIndicatorWidth) * positionOffset
              mIndicatorPaint.color = ColorUtils.blendARGB(mIndicatorColor, mIndicatorSelectedColor, positionOffset)
           }
           else if(i<position){
              right=left+mIndicatorWidth
              mIndicatorPaint.color = mIndicatorColor
           }
           else if(i==position){
              right=i*(mIndicatorWidth+mIndicatorMargin)+mIndicatorSelectedWidth
              mIndicatorPaint.color = ColorUtils.blendARGB(mIndicatorColor, mIndicatorSelectedColor, 1-positionOffset)
           }
           canvas.drawRoundRect(RectF(left, 0f, right, mIndicatorHeight), mIndicatorWidth / 2, mIndicatorWidth / 2, mIndicatorPaint)
           left=right+mIndicatorMargin
        }
     } else {
        for (i in 0 until mIndicatorCount) {
            if (i < position) {
                left = i * (mIndicatorWidth + mIndicatorMargin)
                right = left + mIndicatorWidth
                mIndicatorPaint.color = mIndicatorColor
             } else if (i == position) {
                left = i * (mIndicatorWidth + mIndicatorMargin)
                right = left + mIndicatorWidth + (mIndicatorSelectedWidth - mIndicatorWidth) * (1 - positionOffset)
                    mIndicatorPaint.color = ColorUtils.blendARGB(mIndicatorColor, mIndicatorSelectedColor, 1 - positionOffset)
             } else if (i == (position + 1)) {
                left = (i - 1) * (mIndicatorMargin + mIndicatorWidth) + mIndicatorWidth + (mIndicatorSelectedWidth - mIndicatorWidth) * (1 - positionOffset) + mIndicatorMargin
                right = i * (mIndicatorMargin + mIndicatorWidth) + mIndicatorSelectedWidth
                mIndicatorPaint.color = ColorUtils.blendARGB(mIndicatorColor, mIndicatorSelectedColor, positionOffset)
             } else {
                left = (i - 1) * (mIndicatorWidth + mIndicatorMargin) + (mIndicatorSelectedWidth + mIndicatorMargin)
                right = left + mIndicatorWidth
                mIndicatorPaint.color = mIndicatorColor
             }
             canvas.drawRoundRect(RectF(left, 0f, right, mIndicatorHeight), mIndicatorWidth / 2, mIndicatorWidth / 2, mIndicatorPaint)
        }
    }
}

  • setUp方法实现ViewPagerIndicator绑定
fun setUp(count:Int) {
    mIndicatorCount = count
    requestLayout()
}

GitHub

完整的代码可以在GitHub上获取,喜欢的可以考虑给我点个赞_^_

github.com/kongpf8848/…

参考

BGABanner

CuteIndicator