android developer
android developer

Reputation: 115990

How to directly (without animation) set FAB icon rotation&color and FAB background, at runtime?

Background

I'm making a POC of swiping a FAB up and down, similar to how it works on the Phone app, when you get a phone call:

enter image description here enter image description here

The problem

While I've handled the touch events (allowing to move the fab up and down), and animate back when you stop touching, I also need to change the background color of the FAB and rotation&color of the icon of the FAB.

What I've found

I've found only solutions for animating of the FAB background color and its icon rotation:

I didn't find how to change the color of the icon itself, though.

But in my case, it can't be done using just animation, because the FAB has its Y coordinate change based on touch, so the values need to adapt right away, without animation.

Here's my current code:

activity_main.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fabTouchingAreaView" android:layout_width="match_parent" android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal" android:layout_margin="@dimen/fab_margin"
        android:clipChildren="false" android:clipToPadding="false" tools:background="#33ff0000">

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab" android:layout_width="match_parent" android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|bottom" android:layout_marginBottom="20dp"
            android:layout_marginTop="50dp" android:tint="#0f0" app:backgroundTint="#fff"
            app:srcCompat="@android:drawable/stat_sys_phone_call"/>
    </FrameLayout>
</FrameLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val yToResetTo = (fab.layoutParams as ViewGroup.MarginLayoutParams).topMargin.toFloat()
        val argbEvaluator = ArgbEvaluator()
        fabTouchingAreaView.setOnTouchListener(object : View.OnTouchListener {
            val WHITE_COLOR = 0xffffffff.toInt()
            val RED_COLOR = 0xffff0000.toInt()
            val GREEN_COLOR = 0xff00ff00.toInt()
            var startTouchY = Float.MIN_VALUE
            var startViewY = Float.MIN_VALUE
            var maxToGoTo = Float.MIN_VALUE
            var animator: ObjectAnimator? = null

            fun updateView() {
                val newY = fab.y
                val percentDown = if (newY <= yToResetTo) 0.0f else ((newY - yToResetTo) / (maxToGoTo - yToResetTo))
                val percentUp = if (newY >= yToResetTo) 0.0f else (1.0f - (newY / yToResetTo))
                // need code here to update content
            }

            override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
                when (motionEvent.action) {
                    MotionEvent.ACTION_DOWN -> {
                        if (animator != null) {
                            animator!!.cancel()
                            animator = null
                        }
                        startTouchY = motionEvent.rawY
                        startViewY = fab.y
                        maxToGoTo = fabTouchingAreaView.height.toFloat() - fab.height
                    }
                    MotionEvent.ACTION_UP -> {
                        if (animator == null) {
                            animator = ObjectAnimator.ofFloat(fab, View.Y, yToResetTo)
                            //TODO use normal animate() when minSdk is at least 19
                            animator!!.addUpdateListener { animation -> updateView() }
                            animator!!.start()
                        }
                    }
                    MotionEvent.ACTION_MOVE -> {
                        val newY = Math.min(Math.max(startViewY + (motionEvent.rawY - startTouchY), 0.0f), maxToGoTo)
                        fab.y = newY
                        updateView()
                    }
                }
                return true
            }
        })
    }
}

And here's a demonstration of how it works:

enter image description here

One possible workaround would be to have multiple views instead of a single FAB, which I choose how to animate between them, but this is just a workaround...

The questions

  1. How can I change the background color of the FAB, at runtime, similar to the Phone app, by using just the percentage of how far I got ?

  2. How can I change the rotation&color of the icon of the FAB, at runtime, similar to the Phone app, by using just the percentage of how far I got ?


EDIT: seeing the accepted answer, the next code will do what I've written, in the updateView function:

                val fabBackgroundColor = when {
                    percentDown > 0f -> argbEvaluator.evaluate(percentDown, WHITE_COLOR, RED_COLOR) as Int
                    percentUp > 0f -> argbEvaluator.evaluate(percentUp, WHITE_COLOR, GREEN_COLOR) as Int
                    else -> WHITE_COLOR
                }
                val fabIconColor = when {
                    percentDown > 0f -> argbEvaluator.evaluate(percentDown, GREEN_COLOR, WHITE_COLOR) as Int
                    percentUp > 0f -> argbEvaluator.evaluate(percentUp, GREEN_COLOR, WHITE_COLOR) as Int
                    else -> GREEN_COLOR
                }
                fab.setColorFilter(fabIconColor)
                fab.backgroundTintList = ColorStateList.valueOf(fabBackgroundColor)
                fab.rotation = percentDown * 135
                Log.d("appLog", "y:" + fab.y + " percentDown:$percentDown percentUp:$percentUp " +
                        "fabBackgroundColor:${Integer.toHexString(fabBackgroundColor)} fabIconColor:${Integer.toHexString(fabIconColor)}")

Upvotes: 1

Views: 526

Answers (1)

Blackbelt
Blackbelt

Reputation: 157467

How can I change the background color of the FAB, at runtime, similar to the Phone app, by using just the percentage of how far I got ?

you can use ArgbEvaluator to calculate the color for the current position, and set the return value of evaluate as new Background.

How can I change the rotation of the icon of the FAB, at runtime, similar to the Phone app, by using just the percentage of how far I got ?

as above, use an Interpolator to calculate the rotation and the use View.setRotation to rotate it

Upvotes: 1

Related Questions