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.
Any help will be appreciateg.
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.
Any help will be appreciateg.
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()
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>
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.
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
}