2

I'm trying to replace menu icon with cross icon, but I don't know better solution than replacing source in ImageView and futher more I cannot find done libraries which converts images.

Menu icon Cross icon

Any help will be appreciateg.

4 Answers4

2

Android has a drawable for animating between hamburger and arrow: android.support.v7.graphics.drawable.DrawerArrowDrawable This drawable uses very generic approach with canvas drawing. If you have some spare time and ready for some tedious work, you can animate pretty much anything by looking at this example.

For instance, here is "hamburger" to cross drawable:

/**
 * Simple animating drawable between the "hamburger" icon and cross icon
 *
 * Based on [android.support.v7.graphics.drawable.DrawerArrowDrawable]
 */
class HamburgerCrossDrawable(
        /** Width and height of the drawable (the drawable is always square) */
        private val size: Int,
        /** Thickness of each individual line */
        private val barThickness: Float,
        /** The space between bars when they are parallel */
        private val barGap: Float
) : Drawable() {

    private val paint = Paint()
    private val thick2 = barThickness / 2.0f

    init {
        paint.style = Paint.Style.STROKE
        paint.strokeJoin = Paint.Join.MITER
        paint.strokeCap = Paint.Cap.BUTT
        paint.isAntiAlias = true

        paint.strokeWidth = barThickness
    }

    override fun draw(canvas: Canvas) {
        if (progress < 0.5) {
            drawHamburger(canvas)
        } else {
            drawCross(canvas)
        }
    }

    private fun drawHamburger(canvas: Canvas) {
        val bounds = bounds
        val centerY = bounds.exactCenterY()
        val left = bounds.left.toFloat() + thick2
        val right = bounds.right.toFloat() - thick2

        // Draw middle line
        canvas.drawLine(
                left, centerY,
                right, centerY,
                paint)

        // Calculate Y offset to top and bottom lines
        val offsetY = barGap * (2 * (0.5f - progress))

        // Draw top & bottom lines
        canvas.drawLine(
                left, centerY - offsetY,
                right, centerY - offsetY,
                paint)
        canvas.drawLine(
                left, centerY + offsetY,
                right, centerY + offsetY,
                paint)
    }

    private fun drawCross(canvas: Canvas) {
        val bounds = bounds
        val centerX = bounds.exactCenterX()
        val centerY = bounds.exactCenterY()
        val crossHeight = barGap * 2 + barThickness * 3
        val crossHeight2 = crossHeight / 2

        // Calculate current cross position
        val distanceY = crossHeight2 * (2 * (progress - 0.5f))
        val top = centerY - distanceY
        val bottom = centerY + distanceY
        val left = centerX - crossHeight2
        val right = centerX + crossHeight2

        // Draw cross
        canvas.drawLine(
                left, top,
                right, bottom,
                paint)
        canvas.drawLine(
                left, bottom,
                right, top,
                paint)
    }

    override fun setAlpha(alpha: Int) {
        if (alpha != paint.alpha) {
            paint.alpha = alpha
            invalidateSelf()
        }
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
        invalidateSelf()
    }

    override fun getIntrinsicWidth(): Int {
        return size
    }

    override fun getIntrinsicHeight(): Int {
        return size
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSLUCENT
    }

    /**
     * Drawable color
     * Can be animated
     */
    var color: Int = 0xFFFFFFFF.toInt()
        set(value) {
            field = value
            paint.color = value
            invalidateSelf()
        }

    /**
     * Animate this property to transition from hamburger to cross
     * 0 = hamburger
     * 1 = cross
     */
    var progress: Float = 0.0f
        set(value) {
            field = value.coerceIn(0.0f, 1.0f)
            invalidateSelf()
        }

}

You can use this drawable like any other drawable, for example by setting ImageView src to this drawable:

imageView = AppCompatImageView(context)
addView(imageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
hamburgerDrawable = HamburgerCrossDrawable(
        size = dpToPx(20).toInt(),
        barThickness = dpToPx(2),
        barGap = dpToPx(5)
)
hamburgerDrawable.color = hamburgerColor
imageView.setImageDrawable(hamburgerDrawable)

To make the drawable actually change from hamburger to cross and back you'll need to change HamburgerCrossDrawable.progress (0 stands for hamburger and 1 stands for cross):

val animator = ValueAnimator.ofFloat(0.0f, 1.0f)
animator.interpolator = AccelerateDecelerateInterpolator()
animator.duration = 300
animator.addUpdateListener {
    val progress = it.animatedValue as Float
    val color = interpolateColor(hamburgerColor, crossColor, progress)
    hamburgerDrawable.color = color
    hamburgerDrawable.progress = progress
}
animator.start()
Alexey
  • 7,262
  • 4
  • 48
  • 68
2

I seem to be a little late to the show, but I prefer a declarative approach with an animated selector for icon animations. It seems a lot clearer and the only thing you need to care about are the View or Button's states.

TL;DR: I've created a gist with all of the classes needed to achieve the animation.

Here's an example of the selector which you use as a drawable:

<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/open"
        android:drawable="@drawable/ic_drawer_closed"
        android:state_selected="true"/>

    <item
        android:id="@+id/closed"
        android:drawable="@drawable/ic_drawer"/>

    <transition
        android:drawable="@drawable/avd_drawer_close"
        android:fromId="@id/open"
        android:toId="@id/closed"/>

    <transition
        android:drawable="@drawable/avd_drawer_open"
        android:fromId="@id/closed"
        android:toId="@id/open"/>

</animated-selector>

And here's the animation itself: Drawer (≡) to close (x) animated vector drawable

Schadenfreude
  • 1,522
  • 1
  • 20
  • 41
  • Can you explain how to apply the animation to the DrawerLayout hamburger icon? Thanks! – grago Jun 13 '20 at 05:56
  • Take a look at the gist, you have all the files needed there. You just need to use `asl_drawer` (asl = AnimatedStateListDrawable) as the src drawable and then change the value of the `selected` state when you click the hamburger icon. – Schadenfreude Jun 15 '20 at 11:36
  • The thing is that there's now way to set the src drawable to the app bar navigation hamburger button. So i ended up creating a custom button, and it worked like a charm. Thanks! – grago Jul 14 '20 at 20:36
0

You shoud use existing AnimatedVectorDrawable on the internet. Or you should create it with your icons on Shape Shifter web site. I recommend you to watch a tutrial on youtube for this process: ShapeShifter Tutorial.

Samir Alakbarov
  • 1,120
  • 11
  • 21
0

Handle the state change of the button by setting a state value in the tag of the button and change the Animatable vector drawable resource according to the value of the current solution may also work.

btnMenuView.setOnClickListener {

        val state = it.getTag(R.string.meta_tag_menu_button_state).toString().trim()

            context?.let {

                var animDrawable =
                    AnimatedVectorDrawableCompat.create(it, R.drawable.avd_drawer_open)

                if (state != "open") {

                    animDrawable =
                        AnimatedVectorDrawableCompat.create(it, R.drawable.avd_drawer_close)

                    btnMenuView.setTag(R.string.meta_tag_menu_button_state, "open")
                } else {
                    btnMenuView.setTag(R.string.meta_tag_menu_button_state, "close")
                }

                btnMenuView.setImageDrawable(animDrawable)
                val animatable: Drawable? = btnMenuView.drawable

                if (animatable is Animatable) {
                    animatable.start()
                }



        }

        ---Rest of your code

    }
Jayakrishnan
  • 4,457
  • 29
  • 29