先放上效果图, 因简书不支持webp格式的图片,如上传gif图片太大而无法显示,可移步掘金或GitHub查看动态效果图。
动画分析
- 未选中页面对应的指示符宽度小,形状为圆形,选中页面对应的指示符宽度大,形状为椭圆。
- 当页面A->B,A页面对应的指示符宽度由大->小,B页面对应的指示符宽度由小->大,指示符宽度和颜色随滑动而不断变化。
- 当从最后一个页面手指向右滑动,页面向左时,最后一个页面对应的指示符宽度由大->小,第一个页面对应的指示符宽度由小->大,其余指示符向右平移,有联动效果。
- 当从第一个页面手指向左滑动,页面向右时,第一个页面对应的指示符宽度由大->小,最后一个对应的指示符宽度由小->大,其余指示符向左平移,有联动效果。
代码实现
TKBanner
实现无限轮播图功能,默认支持圆点和数字指示符。
仿知乎日报APP轮播图 | 仿品玩APP轮播图 | 仿虎嗅APP轮播图 |
---|---|---|
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
方法实现ViewPager
和Indicator
绑定
fun setUp(count:Int) {
mIndicatorCount = count
requestLayout()
}
GitHub
完整的代码可以在GitHub上获取,喜欢的可以考虑给我点个赞^
https://github.com/kongpf8848/ViewWorld