import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import kotlin.math.roundToInt

class DotIndicatorView @JvmOverloads constructor(
    context: Context, attr: AttributeSet?, def: Int = 0
) : LinearLayout(context, attr, def) {

    companion object {
        private const val DOT_MAX_SIZE = 5
    }

    private val dotSizeSelected = 10.dp
    private val dotSizeNormal = 5.dp
    private val dotSizeMiddle = 5.dp
    private val dotSizeSmall = 3.dp

    private val dotMargin = 5.dp

    private var curSelected = 0

    private val dotList = arrayListOf<ImageView>()
    private val dotSize
        get() = dotList.size

    init {
        this.gravity = Gravity.CENTER_VERTICAL
    }


    fun setCount(count: Int) {
        if (count <= 1) {
            visibility = View.GONE
            return
        }
        visibility = View.VISIBLE

        removeAllViews()
        dotList.clear()
        val width = if (count >= DOT_MAX_SIZE) {
            dotSizeSelected + (DOT_MAX_SIZE - 1) * dotSizeNormal + (DOT_MAX_SIZE - 1) * dotMargin
        } else {
            dotSizeSelected + (count - 1) * dotSizeNormal + (count - 1) * dotMargin
        }
        layoutParams.width = width
        Log.d("DotIndicatorView", "setCount: $count, width: $width")

        for (i in 0 until count) {
            val dot = createDotView(i)
            addView(dot)
            dotList.add(dot)
        }
        setDotSelected(0)
        if (count <= DOT_MAX_SIZE) return
        setDotSmall(DOT_MAX_SIZE - 1)
        setDotMiddle(DOT_MAX_SIZE - 2)
    }

    fun setSelectedIndex(index: Int) {
        Log.d("DotIndicatorView", "setSelectedIndex: $index")
        if (index == curSelected) return
        if (index !in 0 until dotSize) {
            return
        }

        if (index == 0) {
            setCount(dotSize)
            return
        }

        dotList.forEachIndexed { i, _ ->
            setDotNormal(i)
        }

        when (index) {
            1, 2 -> { // 前三个.
                setDotSmall(DOT_MAX_SIZE - 1)
                setDotMiddle(DOT_MAX_SIZE - 2)
            }

            dotSize - 1, dotSize - 2, dotSize - 3 -> { // 倒数三个.
                setDotSmall(dotSize - DOT_MAX_SIZE)
                setDotMiddle(dotSize - DOT_MAX_SIZE + 1)
            }

            else -> { // 中间部分.
                setDotSmall(index - 2)
                if (index + 2 < dotSize) {
                    setDotSmall(index + 2)
                }
            }
        }

        setDotSelected(index)

        this.post {
            translationDot(getTranslationX(index))
        }
    }

    private fun getTranslationX(selectedIndex: Int): Int {
        val ivSelected = dotList[selectedIndex]
        val selectedDotCenterX = ivSelected.x + ivSelected.width / 2f
        val targetX: Float = when (selectedIndex) {
            0 -> selectedDotCenterX
            1 -> dotSizeNormal + dotMargin + ivSelected.width / 2f
            2 -> dotSizeNormal * 2 + dotMargin * 2 + ivSelected.width / 2f
            dotSize - 2 -> this.width - dotSizeNormal - dotMargin - ivSelected.width / 2f
            dotSize - 1 -> this.width - ivSelected.width / 2f
            else -> this.width / 2f
        }
        return (targetX - selectedDotCenterX).toInt()
    }

    private fun setDotSelected(index: Int) {
        curSelected = index
        dotList.getOrNull(index)?.apply {
            setImageResource(R.drawable.shape_selected)
            layoutParams = layoutParams.apply { width = dotSizeSelected; height = dotSizeNormal }
        }
    }

    private fun setDotSmall(index: Int) {
        if (dotSize <= DOT_MAX_SIZE) {
            return
        }
        dotList.getOrNull(index)?.apply {
            setImageResource(R.drawable.shape_small)
            layoutParams = layoutParams.apply { width = dotSizeSmall;height = dotSizeSmall }
        }
    }

    private fun setDotMiddle(index: Int) {
        if (dotSize <= DOT_MAX_SIZE) {
            return
        }
        dotList.getOrNull(index)?.apply {
            setImageResource(R.drawable.shape_middle)
            layoutParams = layoutParams.apply { width = dotSizeMiddle;height = dotSizeMiddle }
        }
    }

    private fun setDotNormal(index: Int) {
        dotList.getOrNull(index)?.apply {
            setImageResource(R.drawable.shape_normal)
            layoutParams = layoutParams.apply { width = dotSizeNormal;height = dotSizeNormal }
        }
    }

    private fun createDotView(index: Int): ImageView {
        val lp = LayoutParams(dotSizeNormal, dotSizeNormal)
        if (index > 0) {
            lp.setMargins(dotMargin, 0, 0, 0)
        }
        val imageView = ImageView(context)
        imageView.setImageResource(R.drawable.shape_normal)
        imageView.layoutParams = lp
        return imageView
    }

    private fun translationDot(dx: Int) {
        val animatorSet = AnimatorSet()
        dotList.forEach { vDot ->
            animatorSet.playTogether(
                ObjectAnimator.ofFloat(
                    vDot,
                    "translationX",
                    vDot.translationX,
                    vDot.translationX + dx
                )
            )
        }
        animatorSet.duration = 200L
        animatorSet.start()
    }
}

inline val Number.dpFloat
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        toFloat(),
        Resources.getSystem().displayMetrics
    )


inline val Number.dp
    get() = dpFloat.roundToInt()

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐