JustPaste
HomeCategoriesAboutDonateContactTerms of UsePrivacy Policy
JustPaste

Free online notepad — write and share instantly

Navigate

  • Home
  • Timeline
  • Categories

Info

  • About
  • Donate
  • Contact

Legal

  • Terms of Use
  • Privacy Policy

© 2026 JustPaste.app. All rights reserved.

Made with ♥ by JustPaste

Untitled Page | JustPaste.app
about 2 months ago1 views
👨‍💻Programming
package com.yourdomain.app.ui

import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.ViewGroup
import android.view.animation.OvershootInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout

/**
 * Drop this inside your XML layout.
 * Note on Frost/Blur: Pure Android views cannot do dynamic background blur natively 
 * before Android 12 without killing performance. 
 * Wrap this entire view in 'com.github.Dimezis:BlurView' in your XML to get that frosted glass.
 */
class AnimatedBottomNav @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    private val pills = mutableListOf<NavPill>()
    private var activeIndex = 1

    init {
        orientation = HORIZONTAL
        gravity = Gravity.CENTER
        // The translucent pill container background
        background = GradientDrawable().apply {
            shape = GradientDrawable.RECTANGLE
            cornerRadius = dpToPx(32f)
            setColor(Color.parseColor("#992A2A2A")) // 60% opacity dark gray
            setStroke(dpToPx(1f).toInt(), Color.parseColor("#1AFFFFFF")) // Subtle rim light
        }
        setPadding(dpToPx(8f).toInt(), dpToPx(8f).toInt(), dpToPx(8f).toInt(), dpToPx(8f).toInt())
    }

    fun addTabs(icons: List<Int>, defaultIndex: Int = 1) {
        activeIndex = defaultIndex
        icons.forEachIndexed { index, iconRes ->
            val pill = NavPill(context).apply {
                setIcon(iconRes)
                setOnClickListener { selectTab(index) }
            }
            pills.add(pill)
            addView(pill)
            pill.setActive(index == activeIndex, animate = false)
        }
    }

    private fun selectTab(index: Int) {
        if (index == activeIndex) return
        pills[activeIndex].setActive(false, animate = true)
        activeIndex = index
        pills[activeIndex].setActive(true, animate = true)
    }

    private fun dpToPx(dp: Float): Float = dp * context.resources.displayMetrics.density
}

/**
 * The individual pill that handles its own spring animations.
 */
private class NavPill(context: Context) : FrameLayout(context) {

    private val iconView = ImageView(context)
    
    // Colors
    private val activeBgColor = Color.parseColor("#26FFFFFF") // 15% White
    private val inactiveBgColor = Color.TRANSPARENT
    private val activeIconColor = Color.WHITE
    private val inactiveIconColor = Color.parseColor("#9CA3AF") // Gray

    // Sizes
    private val activeWidth = dpToPx(80f).toInt()
    private val inactiveWidth = dpToPx(48f).toInt()
    private val height = dpToPx(48f).toInt()

    private val pillBackground = GradientDrawable().apply {
        shape = GradientDrawable.RECTANGLE
        cornerRadius = dpToPx(24f)
        setColor(inactiveBgColor)
    }

    init {
        layoutParams = LinearLayout.LayoutParams(inactiveWidth, height).apply {
            marginEnd = dpToPx(4f).toInt()
            marginStart = dpToPx(4f).toInt()
        }
        background = pillBackground

        iconView.layoutParams = LayoutParams(dpToPx(24f).toInt(), dpToPx(24f).toInt()).apply {
            gravity = Gravity.CENTER
        }
        iconView.imageTintList = ColorStateList.valueOf(inactiveIconColor)
        addView(iconView)
    }

    fun setIcon(resId: Int) {
        iconView.setImageResource(resId)
    }

    fun setActive(isActive: Boolean, animate: Boolean) {
        val targetWidth = if (isActive) activeWidth else inactiveWidth
        val targetBgColor = if (isActive) activeBgColor else inactiveBgColor
        val targetIconColor = if (isActive) activeIconColor else inactiveIconColor

        if (!animate) {
            layoutParams.width = targetWidth
            requestLayout()
            pillBackground.setColor(targetBgColor)
            iconView.imageTintList = ColorStateList.valueOf(targetIconColor)
            return
        }

        // 1. Spring Animation for Width
        ValueAnimator.ofInt(layoutParams.width, targetWidth).apply {
            duration = 350
            interpolator = OvershootInterpolator(1.2f) // Bouncy!
            addUpdateListener { anim ->
                layoutParams.width = anim.animatedValue as Int
                requestLayout()
            }
            start()
        }

        // 2. Crossfade for Background Color
        ValueAnimator.ofObject(ArgbEvaluator(), (pillBackground.color?.defaultColor ?: inactiveBgColor), targetBgColor).apply {
            duration = 300
            addUpdateListener { anim -> pillBackground.setColor(anim.animatedValue as Int) }
            start()
        }

        // 3. Crossfade for Icon Color
        ValueAnimator.ofObject(ArgbEvaluator(), iconView.imageTintList?.defaultColor ?: inactiveIconColor, targetIconColor).apply {
            duration = 300
            addUpdateListener { anim -> iconView.imageTintList = ColorStateList.valueOf(anim.animatedValue as Int) }
            start()
        }
    }

    private fun dpToPx(dp: Float): Float = dp * context.resources.displayMetrics.density
}

⚠️Content was pasted as plain text and auto-formatted as a code block. Use the Code Block button in the editor for proper formatting.

← Back to timeline