Как напрямую (без анимации) установить поворот и цвет значка FAB и фон FAB во время выполнения?

Фон

Я делаю POC, проводя FAB вверх и вниз, подобно тому, как это работает в приложении «Телефон», когда вам звонят:

введите здесь описание изображения введите здесь описание изображения

Проблема

Хотя я обрабатывал сенсорные события (позволяя перемещать FAB вверх и вниз) и анимировать обратно, когда вы перестаете касаться, мне также нужно изменить цвет фона FAB, а также вращение и цвет значка FAB.

Что я нашел

Я нашел только решения для анимации цвета фона FAB и вращения его значка:

Однако я не нашел, как изменить цвет самой иконки.

Но в моем случае это невозможно сделать, используя только анимацию, потому что FAB имеет изменение координаты Y на основе касания, поэтому значения должны адаптироваться сразу, без анимации.

Вот мой текущий код:

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
            }
        })
    }
}

А вот демонстрация того, как это работает:

введите здесь описание изображения

Одним из возможных обходных путей было бы иметь несколько представлений вместо одного FAB, который я выбираю, как анимировать между ними, но это всего лишь обходной путь...

Вопросы

  1. Как я могу изменить цвет фона FAB во время выполнения, как и в приложении «Телефон», используя только процент того, как далеко я продвинулся?

  2. Как я могу изменить поворот и цвет значка FAB во время выполнения, как и в приложении «Телефон», используя только процент того, как далеко я продвинулся?


РЕДАКТИРОВАТЬ: увидев принятый ответ, следующий код будет делать то, что я написал, в функции updateView:

                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)}")

person android developer    schedule 10.07.2018    source источник
comment
Почему минус?   -  person android developer    schedule 10.07.2018


Ответы (1)


Как я могу изменить цвет фона FAB во время выполнения, как и в приложении «Телефон», используя только процент того, как далеко я продвинулся?

вы можете использовать ArgbEvaluator для расчета цвета для текущей позиции и установить возвращаемое значение evaluate в качестве нового фона.

Как я могу изменить поворот значка FAB во время выполнения, как и в приложении «Телефон», используя только процент того, как далеко я продвинулся?

как и выше, используйте Interpolator для расчета поворота и используйте View.setRotation для его поворота.

person Blackbelt    schedule 10.07.2018
comment
Что касается цветов, я думаю, что использование backgroundTintList для фона FAB работает. Но что я должен использовать для значка внутри? Кажется, setColorFilter работает, но правильно ли это? Что касается View.setRotation, некоторые люди утверждали, что он не работает на старых версиях Android, поскольку он вращает тень, но я протестировал его, и он работает нормально даже на Android 4.2. Действительно ли это безопасно? - person android developer; 10.07.2018
comment
для значка вы можете попробовать подкрасить (DrawableCompat.setTint() рисуемый (Fab - это ImageView и имеет метод getDrawable()). не знаю получится ли, но я бы попробовал - person Blackbelt; 10.07.2018
comment
Но, может быть, setColorFilter тоже подойдет? Я протестировал его, и он отлично работает на разных версиях Android, от 4.2 до P. - person android developer; 10.07.2018