931 lines
32 KiB
Kotlin
931 lines
32 KiB
Kotlin
package com.angcyo.tablayout
|
|
|
|
import android.content.Context
|
|
import android.graphics.Canvas
|
|
import android.graphics.Color
|
|
import android.graphics.drawable.Drawable
|
|
import android.graphics.drawable.GradientDrawable
|
|
import android.util.AttributeSet
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import androidx.core.graphics.withSave
|
|
import java.util.*
|
|
import kotlin.math.absoluteValue
|
|
import kotlin.math.max
|
|
|
|
/**
|
|
* 指示器
|
|
* Email:angcyo@126.com
|
|
* @author angcyo
|
|
* @date 2019/11/25
|
|
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
|
*/
|
|
open class DslTabIndicator(val tabLayout: DslTabLayout) : DslGradientDrawable() {
|
|
|
|
companion object {
|
|
|
|
/**非颜色值*/
|
|
const val NO_COLOR = -2
|
|
|
|
//---style---
|
|
|
|
/**不绘制指示器*/
|
|
const val INDICATOR_STYLE_NONE = 0
|
|
|
|
/**指示器绘制在[itemView]的顶部*/
|
|
const val INDICATOR_STYLE_TOP = 0x1
|
|
|
|
/**指示器绘制在[itemView]的底部*/
|
|
const val INDICATOR_STYLE_BOTTOM = 0x2
|
|
|
|
/**默认样式,指示器绘制在[itemView]的中心*/
|
|
const val INDICATOR_STYLE_CENTER = 0x4
|
|
|
|
/**前景绘制,
|
|
* 默认是背景绘制, 指示器绘制[itemView]的背部, [itemView] 请不要设置background, 否则可能看不见*/
|
|
const val INDICATOR_STYLE_FOREGROUND = 0x1000
|
|
|
|
//---gravity---
|
|
|
|
/**指示器重力在开始的位置(横向左边, 纵向上边)*/
|
|
const val INDICATOR_GRAVITY_START = 0x1
|
|
|
|
/**指示器重力在结束的位置(横向右边, 纵向下边)*/
|
|
const val INDICATOR_GRAVITY_END = 0x2
|
|
|
|
/**指示器重力在中间*/
|
|
const val INDICATOR_GRAVITY_CENTER = 0x4
|
|
}
|
|
|
|
/**指示器绘制的样式*/
|
|
var indicatorStyle = INDICATOR_STYLE_NONE //初始化
|
|
|
|
/**[indicatorStyle]*/
|
|
val _indicatorDrawStyle: Int
|
|
get() = indicatorStyle.remove(INDICATOR_STYLE_FOREGROUND)
|
|
|
|
/**优先将指示器显示在[DslTabLayout]的什么位置
|
|
* [INDICATOR_GRAVITY_START] 开始的位置
|
|
* [INDICATOR_GRAVITY_END] 结束的位置
|
|
* [INDICATOR_GRAVITY_CENTER] 中间的位置*/
|
|
var indicatorGravity = INDICATOR_GRAVITY_CENTER
|
|
|
|
/**
|
|
* 指示器在流向下一个位置时, 是否采用[Flow]流线的方式改变宽度
|
|
* */
|
|
var indicatorEnableFlow: Boolean = false
|
|
|
|
/**指示器闪现效果, 从当前位置直接跨越到目标位置*/
|
|
var indicatorEnableFlash: Boolean = false
|
|
|
|
/**使用clip的方式绘制闪现效果*/
|
|
var indicatorEnableFlashClip: Boolean = true
|
|
|
|
/**当目标和当前的索引差值<=此值时, [Flow]效果才有效*/
|
|
var indicatorFlowStep: Int = 1
|
|
|
|
/**指示器绘制实体*/
|
|
var indicatorDrawable: Drawable? = null
|
|
set(value) {
|
|
field = tintDrawableColor(value, indicatorColor)
|
|
}
|
|
|
|
/**过滤[indicatorDrawable]的颜色*/
|
|
var indicatorColor: Int = NO_COLOR
|
|
set(value) {
|
|
field = value
|
|
indicatorDrawable = indicatorDrawable
|
|
}
|
|
|
|
/**
|
|
* 指示器的宽度
|
|
* WRAP_CONTENT: [childView]内容的宽度,
|
|
* MATCH_PARENT: [childView]的宽度
|
|
* 40dp: 固定值
|
|
* */
|
|
var indicatorWidth = 0 //初始化
|
|
|
|
/**宽度补偿*/
|
|
var indicatorWidthOffset = 0
|
|
|
|
/**
|
|
* 指示器的高度
|
|
* WRAP_CONTENT: [childView]内容的高度,
|
|
* MATCH_PARENT: [childView]的高度
|
|
* 40dp: 固定值
|
|
* */
|
|
var indicatorHeight = 0 //初始化
|
|
|
|
/**高度补偿*/
|
|
var indicatorHeightOffset = 0
|
|
|
|
/**XY轴方向补偿*/
|
|
var indicatorXOffset = 0
|
|
|
|
/**会根据[indicatorStyle]自动取负值*/
|
|
var indicatorYOffset = 0
|
|
|
|
/**
|
|
* 宽高[WRAP_CONTENT]时, 内容view的定位索引
|
|
* */
|
|
var indicatorContentIndex = -1
|
|
var indicatorContentId = View.NO_ID
|
|
|
|
/**切换时是否需要动画的支持*/
|
|
var indicatorAnim = true
|
|
|
|
/**在获取锚点view的宽高时, 是否需要忽略对应的padding属性*/
|
|
var ignoreChildPadding: Boolean = true
|
|
|
|
init {
|
|
callback = tabLayout
|
|
}
|
|
|
|
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
|
val typedArray =
|
|
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
|
|
|
indicatorDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_indicator_drawable)
|
|
indicatorColor =
|
|
typedArray.getColor(R.styleable.DslTabLayout_tab_indicator_color, indicatorColor)
|
|
indicatorStyle = typedArray.getInt(
|
|
R.styleable.DslTabLayout_tab_indicator_style,
|
|
if (tabLayout.isHorizontal()) INDICATOR_STYLE_BOTTOM else INDICATOR_STYLE_TOP
|
|
)
|
|
indicatorGravity = typedArray.getInt(
|
|
R.styleable.DslTabLayout_tab_indicator_gravity,
|
|
indicatorGravity
|
|
)
|
|
|
|
//初始化指示器的高度和宽度
|
|
if (indicatorStyle.have(INDICATOR_STYLE_FOREGROUND)) {
|
|
//前景绘制
|
|
indicatorWidth = typedArray.getLayoutDimension(
|
|
R.styleable.DslTabLayout_tab_indicator_width,
|
|
if (tabLayout.isHorizontal()) ViewGroup.LayoutParams.MATCH_PARENT else 3 * dpi
|
|
)
|
|
indicatorHeight = typedArray.getLayoutDimension(
|
|
R.styleable.DslTabLayout_tab_indicator_height,
|
|
if (tabLayout.isHorizontal()) 3 * dpi else ViewGroup.LayoutParams.MATCH_PARENT
|
|
)
|
|
indicatorXOffset = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_x_offset,
|
|
if (tabLayout.isHorizontal()) 0 else 2 * dpi
|
|
)
|
|
indicatorYOffset = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_y_offset,
|
|
if (tabLayout.isHorizontal()) 2 * dpi else 0
|
|
)
|
|
} else {
|
|
//背景绘制样式
|
|
if (tabLayout.isHorizontal()) {
|
|
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
|
|
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
|
|
} else {
|
|
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
|
|
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
|
|
}
|
|
indicatorWidth = typedArray.getLayoutDimension(
|
|
R.styleable.DslTabLayout_tab_indicator_width,
|
|
indicatorWidth
|
|
)
|
|
indicatorHeight = typedArray.getLayoutDimension(
|
|
R.styleable.DslTabLayout_tab_indicator_height,
|
|
indicatorHeight
|
|
)
|
|
indicatorXOffset = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_x_offset,
|
|
indicatorXOffset
|
|
)
|
|
indicatorYOffset = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_y_offset,
|
|
indicatorYOffset
|
|
)
|
|
}
|
|
|
|
ignoreChildPadding = typedArray.getBoolean(
|
|
R.styleable.DslTabLayout_tab_indicator_ignore_child_padding,
|
|
!indicatorStyle.have(INDICATOR_STYLE_CENTER)
|
|
)
|
|
|
|
indicatorFlowStep =
|
|
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_flow_step, indicatorFlowStep)
|
|
indicatorEnableFlow = typedArray.getBoolean(
|
|
R.styleable.DslTabLayout_tab_indicator_enable_flow,
|
|
indicatorEnableFlow
|
|
)
|
|
indicatorEnableFlash = typedArray.getBoolean(
|
|
R.styleable.DslTabLayout_tab_indicator_enable_flash,
|
|
indicatorEnableFlash
|
|
)
|
|
indicatorEnableFlashClip = typedArray.getBoolean(
|
|
R.styleable.DslTabLayout_tab_indicator_enable_flash_clip,
|
|
indicatorEnableFlashClip
|
|
)
|
|
|
|
indicatorWidthOffset = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_width_offset,
|
|
indicatorWidthOffset
|
|
)
|
|
indicatorHeightOffset = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_height_offset,
|
|
indicatorHeightOffset
|
|
)
|
|
indicatorContentIndex = typedArray.getInt(
|
|
R.styleable.DslTabLayout_tab_indicator_content_index,
|
|
indicatorContentIndex
|
|
)
|
|
indicatorContentId = typedArray.getResourceId(
|
|
R.styleable.DslTabLayout_tab_indicator_content_id,
|
|
indicatorContentId
|
|
)
|
|
indicatorAnim = typedArray.getBoolean(
|
|
R.styleable.DslTabLayout_tab_indicator_anim,
|
|
indicatorAnim
|
|
)
|
|
|
|
//代码构建Drawable
|
|
gradientShape =
|
|
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_shape, gradientShape)
|
|
gradientSolidColor =
|
|
typedArray.getColor(
|
|
R.styleable.DslTabLayout_tab_indicator_solid_color,
|
|
gradientSolidColor
|
|
)
|
|
gradientStrokeColor =
|
|
typedArray.getColor(
|
|
R.styleable.DslTabLayout_tab_indicator_stroke_color,
|
|
gradientStrokeColor
|
|
)
|
|
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_stroke_width,
|
|
gradientStrokeWidth
|
|
)
|
|
gradientDashWidth = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_dash_width,
|
|
gradientDashWidth.toInt()
|
|
).toFloat()
|
|
gradientDashGap = typedArray.getDimensionPixelOffset(
|
|
R.styleable.DslTabLayout_tab_indicator_dash_gap,
|
|
gradientDashGap.toInt()
|
|
).toFloat()
|
|
|
|
val gradientRadius =
|
|
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_indicator_radius, 0)
|
|
if (gradientRadius > 0) {
|
|
Arrays.fill(gradientRadii, gradientRadius.toFloat())
|
|
} else {
|
|
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_radii)?.let {
|
|
_fillRadii(gradientRadii, it)
|
|
}
|
|
}
|
|
|
|
val gradientColors =
|
|
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_gradient_colors)
|
|
|
|
this.gradientColors = if (gradientColors.isNullOrEmpty()) {
|
|
val startColor = typedArray.getColor(
|
|
R.styleable.DslTabLayout_tab_indicator_gradient_start_color,
|
|
Color.TRANSPARENT
|
|
)
|
|
val endColor = typedArray.getColor(
|
|
R.styleable.DslTabLayout_tab_indicator_gradient_end_color,
|
|
Color.TRANSPARENT
|
|
)
|
|
if (startColor != endColor) {
|
|
intArrayOf(startColor, endColor)
|
|
} else {
|
|
this.gradientColors
|
|
}
|
|
} else {
|
|
_fillColor(gradientColors) ?: this.gradientColors
|
|
}
|
|
//...end
|
|
|
|
typedArray.recycle()
|
|
|
|
if (indicatorDrawable == null && isValidConfig()) {
|
|
updateOriginDrawable()
|
|
}
|
|
}
|
|
|
|
override fun updateOriginDrawable(): GradientDrawable? {
|
|
val drawable = super.updateOriginDrawable()
|
|
indicatorDrawable = originDrawable
|
|
return drawable
|
|
}
|
|
|
|
open fun tintDrawableColor(drawable: Drawable?, color: Int): Drawable? {
|
|
if (drawable == null || color == NO_COLOR) {
|
|
return drawable
|
|
}
|
|
return drawable.tintDrawableColor(color)
|
|
}
|
|
|
|
/**指示器需要参考的目标控件*/
|
|
open fun indicatorContentView(childView: View): View? {
|
|
val lp = childView.layoutParams as DslTabLayout.LayoutParams
|
|
|
|
val contentId =
|
|
if (lp.indicatorContentId != View.NO_ID) lp.indicatorContentId else indicatorContentId
|
|
|
|
if (contentId != View.NO_ID) {
|
|
return childView.findViewById(contentId)
|
|
}
|
|
|
|
//如果child强制指定了index, 就用指定的.
|
|
val contentIndex =
|
|
if (lp.indicatorContentIndex >= 0) lp.indicatorContentIndex else indicatorContentIndex
|
|
|
|
return if (contentIndex >= 0 && childView is ViewGroup && contentIndex in 0 until childView.childCount) {
|
|
//有指定
|
|
val contentChildView = childView.getChildAt(contentIndex)
|
|
contentChildView
|
|
} else {
|
|
//没有指定
|
|
null
|
|
}
|
|
}
|
|
|
|
/**根据指定[index]索引, 获取目标[View]*/
|
|
open fun targetChildView(
|
|
index: Int,
|
|
onChildView: (childView: View, contentChildView: View?) -> Unit
|
|
) {
|
|
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
|
onChildView(childView, indicatorContentView(childView))
|
|
}
|
|
}
|
|
|
|
open fun getChildTargetPaddingLeft(childView: View): Int =
|
|
if (ignoreChildPadding) childView.paddingLeft else 0
|
|
|
|
open fun getChildTargetPaddingRight(childView: View): Int =
|
|
if (ignoreChildPadding) childView.paddingRight else 0
|
|
|
|
open fun getChildTargetPaddingTop(childView: View): Int =
|
|
if (ignoreChildPadding) childView.paddingTop else 0
|
|
|
|
open fun getChildTargetPaddingBottom(childView: View): Int =
|
|
if (ignoreChildPadding) childView.paddingBottom else 0
|
|
|
|
open fun getChildTargetWidth(childView: View): Int =
|
|
if (ignoreChildPadding) childView.viewDrawWidth else childView.measuredWidth
|
|
|
|
open fun getChildTargetHeight(childView: View): Int =
|
|
if (ignoreChildPadding) childView.viewDrawHeight else childView.measuredHeight
|
|
|
|
/**
|
|
* [childview]对应的中心x坐标
|
|
* */
|
|
open fun getChildTargetX(index: Int, gravity: Int = indicatorGravity): Int {
|
|
|
|
var result = if (index > 0) tabLayout.maxWidth else 0
|
|
|
|
targetChildView(index) { childView, contentChildView ->
|
|
result = if (contentChildView == null) {
|
|
when (gravity) {
|
|
INDICATOR_GRAVITY_START -> childView.left
|
|
INDICATOR_GRAVITY_END -> childView.right
|
|
else -> childView.left + getChildTargetPaddingLeft(childView) + getChildTargetWidth(
|
|
childView
|
|
) / 2
|
|
}
|
|
} else {
|
|
when (gravity) {
|
|
INDICATOR_GRAVITY_START -> childView.left + contentChildView.left
|
|
INDICATOR_GRAVITY_END -> childView.left + contentChildView.right
|
|
else -> childView.left + contentChildView.left + getChildTargetPaddingLeft(
|
|
contentChildView
|
|
) + getChildTargetWidth(
|
|
contentChildView
|
|
) / 2
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
open fun getChildTargetY(index: Int, gravity: Int = indicatorGravity): Int {
|
|
|
|
var result = if (index > 0) tabLayout.maxHeight else 0
|
|
|
|
targetChildView(index) { childView, contentChildView ->
|
|
result = if (contentChildView == null) {
|
|
when (gravity) {
|
|
INDICATOR_GRAVITY_START -> childView.top
|
|
INDICATOR_GRAVITY_END -> childView.bottom
|
|
else -> childView.top + getChildTargetPaddingTop(childView) + getChildTargetHeight(
|
|
childView
|
|
) / 2
|
|
}
|
|
} else {
|
|
when (gravity) {
|
|
INDICATOR_GRAVITY_START -> childView.top + contentChildView.top
|
|
INDICATOR_GRAVITY_END -> childView.top + childView.bottom
|
|
else -> childView.top + contentChildView.top + getChildTargetPaddingTop(
|
|
contentChildView
|
|
) + getChildTargetHeight(
|
|
contentChildView
|
|
) / 2
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
open fun getIndicatorDrawWidth(index: Int): Int {
|
|
var result = indicatorWidth
|
|
|
|
when (indicatorWidth) {
|
|
ViewGroup.LayoutParams.WRAP_CONTENT -> {
|
|
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
|
result = getChildTargetWidth(indicatorContentView(childView) ?: childView)
|
|
}
|
|
}
|
|
ViewGroup.LayoutParams.MATCH_PARENT -> {
|
|
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
|
result = childView.measuredWidth
|
|
}
|
|
}
|
|
}
|
|
|
|
return result + indicatorWidthOffset
|
|
}
|
|
|
|
open fun getIndicatorDrawHeight(index: Int): Int {
|
|
var result = indicatorHeight
|
|
|
|
when (indicatorHeight) {
|
|
ViewGroup.LayoutParams.WRAP_CONTENT -> {
|
|
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
|
result = getChildTargetHeight(indicatorContentView(childView) ?: childView)
|
|
}
|
|
}
|
|
ViewGroup.LayoutParams.MATCH_PARENT -> {
|
|
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
|
|
result = childView.measuredHeight
|
|
}
|
|
}
|
|
}
|
|
|
|
return result + indicatorHeightOffset
|
|
}
|
|
|
|
override fun draw(canvas: Canvas) {
|
|
//super.draw(canvas)
|
|
if (!isVisible || _indicatorDrawStyle == INDICATOR_STYLE_NONE || indicatorDrawable == null) {
|
|
//不绘制
|
|
return
|
|
}
|
|
|
|
if (tabLayout.isHorizontal()) {
|
|
drawHorizontal(canvas)
|
|
} else {
|
|
drawVertical(canvas)
|
|
}
|
|
}
|
|
|
|
fun drawHorizontal(canvas: Canvas) {
|
|
val childSize = tabLayout.dslSelector.visibleViewList.size
|
|
|
|
var currentIndex = currentIndex
|
|
|
|
if (_targetIndex in 0 until childSize) {
|
|
currentIndex = max(0, currentIndex)
|
|
}
|
|
|
|
if (currentIndex in 0 until childSize) {
|
|
|
|
} else {
|
|
//无效的index
|
|
return
|
|
}
|
|
|
|
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
|
|
|
|
val drawTargetX = getChildTargetX(currentIndex)
|
|
val drawWidth = getIndicatorDrawWidth(currentIndex)
|
|
val drawHeight = getIndicatorDrawHeight(currentIndex)
|
|
|
|
val drawLeft = drawTargetX - drawWidth / 2 + indicatorXOffset
|
|
|
|
//动画过程中的left
|
|
var animLeft = drawLeft
|
|
//width
|
|
var animWidth = drawWidth
|
|
//动画执行过程中, 高度额外变大的值
|
|
var animExHeight = 0
|
|
|
|
//end value
|
|
val nextDrawTargetX = getChildTargetX(_targetIndex)
|
|
val nextDrawWidth = getIndicatorDrawWidth(_targetIndex)
|
|
val nextDrawLeft = nextDrawTargetX - nextDrawWidth / 2 + indicatorXOffset
|
|
|
|
var animEndWidth = nextDrawWidth
|
|
var animEndLeft = nextDrawLeft
|
|
|
|
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
|
|
|
|
//动画过程参数计算变量
|
|
val animStartLeft = drawLeft
|
|
val animStartWidth = drawWidth
|
|
|
|
val animEndHeight = getIndicatorDrawHeight(_targetIndex)
|
|
|
|
if (indicatorEnableFlash) {
|
|
//闪现效果
|
|
animWidth = (animWidth * (1 - positionOffset)).toInt()
|
|
animEndWidth = (animEndWidth * positionOffset).toInt()
|
|
|
|
animLeft = drawTargetX - animWidth / 2 + indicatorXOffset
|
|
animEndLeft = nextDrawLeft
|
|
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
|
|
//激活了流动效果
|
|
|
|
val flowEndWidth: Int
|
|
if (_targetIndex > currentIndex) {
|
|
flowEndWidth = animEndLeft - animStartLeft + animEndWidth
|
|
|
|
//目标在右边
|
|
animLeft = if (positionOffset >= 0.5) {
|
|
(animStartLeft + (animEndLeft - animStartLeft) * (positionOffset - 0.5) / 0.5f).toInt()
|
|
} else {
|
|
animStartLeft
|
|
}
|
|
} else {
|
|
flowEndWidth = animStartLeft - animEndLeft + animStartWidth
|
|
|
|
//目标在左边
|
|
animLeft = if (positionOffset >= 0.5) {
|
|
animEndLeft
|
|
} else {
|
|
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset / 0.5f).toInt()
|
|
}
|
|
}
|
|
|
|
animWidth = if (positionOffset >= 0.5) {
|
|
(flowEndWidth - (flowEndWidth - animEndWidth) * (positionOffset - 0.5) / 0.5f).toInt()
|
|
} else {
|
|
(animStartWidth + (flowEndWidth - animStartWidth) * positionOffset / 0.5f).toInt()
|
|
}
|
|
} else {
|
|
//默认平移效果
|
|
if (_targetIndex > currentIndex) {
|
|
//目标在右边
|
|
animLeft =
|
|
(animStartLeft + (animEndLeft - animStartLeft) * positionOffset).toInt()
|
|
} else {
|
|
//目标在左边
|
|
animLeft =
|
|
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset).toInt()
|
|
}
|
|
|
|
//动画过程中的宽度
|
|
animWidth =
|
|
(animStartWidth + (animEndWidth - animStartWidth) * positionOffset).toInt()
|
|
}
|
|
|
|
animExHeight = ((animEndHeight - drawHeight) * positionOffset).toInt()
|
|
}
|
|
|
|
//前景
|
|
val drawTop = when (_indicatorDrawStyle) {
|
|
//底部绘制
|
|
INDICATOR_STYLE_BOTTOM -> viewHeight - drawHeight - indicatorYOffset
|
|
//顶部绘制
|
|
INDICATOR_STYLE_TOP -> 0 + indicatorYOffset
|
|
//居中绘制
|
|
else -> paddingTop + viewDrawHeight / 2 - drawHeight / 2 + indicatorYOffset -
|
|
animExHeight +
|
|
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
|
|
}
|
|
|
|
indicatorDrawable?.apply {
|
|
if (indicatorEnableFlash) {
|
|
//flash
|
|
if (indicatorEnableFlashClip) {
|
|
drawIndicatorClipHorizontal(
|
|
this,
|
|
canvas,
|
|
drawLeft,
|
|
drawTop,
|
|
drawLeft + drawWidth,
|
|
drawTop + drawHeight + animExHeight,
|
|
animWidth,
|
|
1 - positionOffset
|
|
)
|
|
} else {
|
|
drawIndicator(
|
|
this, canvas, animLeft,
|
|
drawTop,
|
|
animLeft + animWidth,
|
|
drawTop + drawHeight + animExHeight,
|
|
1 - positionOffset
|
|
)
|
|
}
|
|
|
|
if (_targetIndex in 0 until childSize) {
|
|
if (indicatorEnableFlashClip) {
|
|
drawIndicatorClipHorizontal(
|
|
this,
|
|
canvas,
|
|
nextDrawLeft,
|
|
drawTop,
|
|
nextDrawLeft + nextDrawWidth,
|
|
drawTop + drawHeight + animExHeight,
|
|
animEndWidth,
|
|
positionOffset
|
|
)
|
|
} else {
|
|
drawIndicator(
|
|
this, canvas, animEndLeft,
|
|
drawTop,
|
|
animEndLeft + animEndWidth,
|
|
drawTop + drawHeight + animExHeight,
|
|
positionOffset
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
//normal
|
|
drawIndicator(
|
|
this, canvas, animLeft,
|
|
drawTop,
|
|
animLeft + animWidth,
|
|
drawTop + drawHeight + animExHeight,
|
|
1 - positionOffset
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun drawIndicator(
|
|
indicator: Drawable,
|
|
canvas: Canvas,
|
|
l: Int,
|
|
t: Int,
|
|
r: Int,
|
|
b: Int,
|
|
offset: Float
|
|
) {
|
|
indicator.apply {
|
|
if (this is ITabIndicatorDraw) {
|
|
setBounds(l, t, r, b)
|
|
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
|
|
} else {
|
|
val width = r - l
|
|
val height = b - t
|
|
setBounds(0, 0, width, height)
|
|
canvas.withSave {
|
|
translate(l.toFloat(), t.toFloat())
|
|
draw(canvas)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun drawIndicatorClipHorizontal(
|
|
indicator: Drawable,
|
|
canvas: Canvas,
|
|
l: Int,
|
|
t: Int,
|
|
r: Int,
|
|
b: Int,
|
|
endWidth: Int,
|
|
offset: Float
|
|
) {
|
|
indicator.apply {
|
|
canvas.save()
|
|
val dx = (r - l - endWidth) / 2
|
|
canvas.clipRect(l + dx, t, r - dx, b)
|
|
setBounds(l, t, r, b)
|
|
if (this is ITabIndicatorDraw) {
|
|
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
|
|
} else {
|
|
draw(canvas)
|
|
}
|
|
canvas.restore()
|
|
}
|
|
}
|
|
|
|
fun drawIndicatorClipVertical(
|
|
indicator: Drawable,
|
|
canvas: Canvas,
|
|
l: Int,
|
|
t: Int,
|
|
r: Int,
|
|
b: Int,
|
|
endHeight: Int,
|
|
offset: Float
|
|
) {
|
|
indicator.apply {
|
|
canvas.save()
|
|
val dy = (b - t - endHeight) / 2
|
|
canvas.clipRect(l, t + dy, r, b - dy)
|
|
setBounds(l, t, r, b)
|
|
if (this is ITabIndicatorDraw) {
|
|
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
|
|
} else {
|
|
draw(canvas)
|
|
}
|
|
canvas.restore()
|
|
}
|
|
}
|
|
|
|
fun drawVertical(canvas: Canvas) {
|
|
val childSize = tabLayout.dslSelector.visibleViewList.size
|
|
|
|
var currentIndex = currentIndex
|
|
|
|
if (_targetIndex in 0 until childSize) {
|
|
currentIndex = max(0, currentIndex)
|
|
}
|
|
|
|
if (currentIndex in 0 until childSize) {
|
|
|
|
} else {
|
|
//无效的index
|
|
return
|
|
}
|
|
|
|
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
|
|
|
|
val drawTargetY = getChildTargetY(currentIndex)
|
|
val drawWidth = getIndicatorDrawWidth(currentIndex)
|
|
val drawHeight = getIndicatorDrawHeight(currentIndex)
|
|
|
|
val drawTop = drawTargetY - drawHeight / 2 + indicatorYOffset
|
|
|
|
//动画过程中的top
|
|
var animTop = drawTop
|
|
//height
|
|
var animHeight = drawHeight
|
|
//动画执行过程中, 宽度额外变大的值
|
|
var animExWidth = 0
|
|
|
|
//end value
|
|
val nextDrawTargetY = getChildTargetY(_targetIndex)
|
|
val nextDrawHeight = getIndicatorDrawHeight(_targetIndex)
|
|
val nextDrawTop = nextDrawTargetY - nextDrawHeight / 2 + indicatorYOffset
|
|
|
|
var animEndHeight = nextDrawHeight
|
|
var animEndTop = nextDrawTop
|
|
|
|
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
|
|
|
|
//动画过程参数计算变量
|
|
val animStartTop = drawTop
|
|
val animStartHeight = drawHeight
|
|
|
|
val animEndWidth = getIndicatorDrawWidth(_targetIndex)
|
|
|
|
if (indicatorEnableFlash) {
|
|
//闪现效果
|
|
animHeight = (animHeight * (1 - positionOffset)).toInt()
|
|
animEndHeight = (animEndHeight * positionOffset).toInt()
|
|
|
|
animTop = drawTargetY - animHeight / 2 + indicatorXOffset
|
|
animEndTop = nextDrawTargetY - animEndHeight / 2 + indicatorXOffset
|
|
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
|
|
//激活了流动效果
|
|
|
|
val flowEndHeight: Int
|
|
if (_targetIndex > currentIndex) {
|
|
flowEndHeight = animEndTop - animStartTop + animEndHeight
|
|
|
|
//目标在下边
|
|
animTop = if (positionOffset >= 0.5) {
|
|
(animStartTop + (animEndTop - animStartTop) * (positionOffset - 0.5) / 0.5f).toInt()
|
|
} else {
|
|
animStartTop
|
|
}
|
|
} else {
|
|
flowEndHeight = animStartTop - animEndTop + animStartHeight
|
|
|
|
//目标在上边
|
|
animTop = if (positionOffset >= 0.5) {
|
|
animEndTop
|
|
} else {
|
|
(animStartTop - (animStartTop - animEndTop) * positionOffset / 0.5f).toInt()
|
|
}
|
|
}
|
|
|
|
animHeight = if (positionOffset >= 0.5) {
|
|
(flowEndHeight - (flowEndHeight - animEndHeight) * (positionOffset - 0.5) / 0.5f).toInt()
|
|
} else {
|
|
(animStartHeight + (flowEndHeight - animStartHeight) * positionOffset / 0.5f).toInt()
|
|
}
|
|
} else {
|
|
if (_targetIndex > currentIndex) {
|
|
//目标在下边
|
|
animTop = (animStartTop + (animEndTop - animStartTop) * positionOffset).toInt()
|
|
} else {
|
|
//目标在上边
|
|
animTop = (animStartTop - (animStartTop - animEndTop) * positionOffset).toInt()
|
|
}
|
|
|
|
//动画过程中的宽度
|
|
animHeight =
|
|
(animStartHeight + (animEndHeight - animStartHeight) * positionOffset).toInt()
|
|
}
|
|
|
|
animExWidth = ((animEndWidth - drawWidth) * positionOffset).toInt()
|
|
}
|
|
|
|
val drawLeft = when (_indicatorDrawStyle) {
|
|
INDICATOR_STYLE_BOTTOM -> {
|
|
//右边/底部绘制
|
|
viewWidth - drawWidth - indicatorXOffset
|
|
}
|
|
INDICATOR_STYLE_TOP -> {
|
|
//左边/顶部绘制
|
|
0 + indicatorXOffset
|
|
}
|
|
else -> {
|
|
//居中绘制
|
|
paddingLeft + indicatorXOffset + (viewDrawWidth / 2 - drawWidth / 2) -
|
|
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
|
|
}
|
|
}
|
|
|
|
indicatorDrawable?.apply {
|
|
//flash
|
|
if (indicatorEnableFlash) {
|
|
if (indicatorEnableFlashClip) {
|
|
drawIndicatorClipVertical(
|
|
this, canvas, drawLeft,
|
|
drawTop,
|
|
drawLeft + drawWidth + animExWidth,
|
|
drawTop + drawHeight,
|
|
animHeight,
|
|
1 - positionOffset
|
|
)
|
|
} else {
|
|
drawIndicator(
|
|
this, canvas, drawLeft,
|
|
animTop,
|
|
drawLeft + drawWidth + animExWidth,
|
|
animTop + animHeight,
|
|
1 - positionOffset
|
|
)
|
|
}
|
|
|
|
if (_targetIndex in 0 until childSize) {
|
|
if (indicatorEnableFlashClip) {
|
|
drawIndicatorClipVertical(
|
|
this, canvas, drawLeft,
|
|
nextDrawTop,
|
|
drawLeft + drawWidth + animExWidth,
|
|
nextDrawTop + nextDrawHeight,
|
|
animEndHeight,
|
|
positionOffset
|
|
)
|
|
} else {
|
|
drawIndicator(
|
|
this, canvas, drawLeft,
|
|
animEndTop,
|
|
drawLeft + drawWidth + animExWidth,
|
|
animEndTop + animEndHeight,
|
|
positionOffset
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
drawIndicator(
|
|
this, canvas, drawLeft,
|
|
animTop,
|
|
drawLeft + drawWidth + animExWidth,
|
|
animTop + animHeight,
|
|
1 - positionOffset
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun _childConvexHeight(index: Int): Int {
|
|
if (attachView is ViewGroup) {
|
|
((attachView as ViewGroup).getChildAt(index).layoutParams as? DslTabLayout.LayoutParams)?.apply {
|
|
return layoutConvexHeight
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
/**
|
|
* 距离[_targetIndex]的偏移比例.[0->1]的过程
|
|
* */
|
|
var positionOffset: Float = 0f
|
|
set(value) {
|
|
field = value
|
|
invalidateSelf()
|
|
}
|
|
|
|
/**当前绘制的index*/
|
|
var currentIndex: Int = -1
|
|
|
|
/**滚动目标的index*/
|
|
var _targetIndex = -1
|
|
} |