Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 01df9aa3 authored by Catherine Liang's avatar Catherine Liang
Browse files

Add floating sheet animation to Icons

Also create animation binder to keep animation consistent across clock
and app icon floating sheets.

Flag: com.android.systemui.shared.extendible_theme_manager
Bug: 397782741
Test: manually verified with flag on and off
Test: manually verified in app icons and clock floating sheets
Change-Id: Iee5e01af229eb0baea25598990b4a4e636f79b64
parent 0301a44a
Loading
Loading
Loading
Loading
+78 −78
Original line number Diff line number Diff line
@@ -21,13 +21,12 @@
    android:paddingHorizontal="@dimen/floating_sheet_horizontal_padding"
    android:orientation="vertical">

    <LinearLayout
    <FrameLayout
        android:id="@+id/floating_sheet_content_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingVertical="@dimen/floating_sheet_content_vertical_padding"
        android:background="@drawable/floating_sheet_content_background"
        android:orientation="vertical"
        android:clipToPadding="false"
        android:clipChildren="false">

@@ -35,10 +34,8 @@
            android:id="@+id/app_icon_style_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp"
            android:clipToPadding="false"
            android:clipChildren="false"
            android:visibility="gone">
            android:clipChildren="false">

            <!--
            This is just an invisible placeholder put in place so that the parent keeps its height
@@ -51,8 +48,6 @@
            -->
            <include
                layout="@layout/icon_style_option2"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:visibility="invisible" />

            <androidx.recyclerview.widget.RecyclerView
@@ -64,11 +59,17 @@
                android:clipChildren="false" />
        </FrameLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:clipToPadding="false"
            android:clipChildren="false">

            <FrameLayout
                android:id="@+id/app_shape_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
            android:layout_marginBottom="12dp"
                android:clipToPadding="false"
                android:clipChildren="false">

@@ -83,8 +84,6 @@
                -->
                <include
                    layout="@layout/shape_option2"
                android:layout_width="64dp"
                android:layout_height="64dp"
                    android:visibility="invisible" />

                <androidx.recyclerview.widget.RecyclerView
@@ -146,6 +145,7 @@
                    android:theme="@style/Theme.Material3.DynamicColors.DayNight" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </LinearLayout>
    </FrameLayout>

    <com.android.wallpaper.picker.customization.ui.view.FloatingToolbar
        android:id="@+id/floating_toolbar"
+93 −5
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.res.ColorStateList
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageView
import android.widget.TextView
import androidx.core.graphics.drawable.DrawableCompat
@@ -38,7 +39,7 @@ import com.android.themepicker.R
import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption.APP_ICONS
import com.android.wallpaper.customization.ui.view.ShapeTileDrawable
import com.android.wallpaper.customization.ui.viewmodel.AppIconPickerViewModel
import com.android.wallpaper.customization.ui.viewmodel.AppIconPickerViewModel.Tab
import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder
import com.android.wallpaper.picker.customization.ui.view.FloatingToolbar
@@ -48,6 +49,8 @@ import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter2
import com.google.android.material.materialswitch.MaterialSwitch
import java.lang.ref.WeakReference
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

object AppIconFloatingSheetBinder {
@@ -137,6 +140,53 @@ object AppIconFloatingSheetBinder {
        val themedIconTitle = view.requireViewById<TextView>(R.id.themed_icon_toggle_title)
        val themedIconBetaLabel = view.requireViewById<TextView>(R.id.themed_icon_beta_title)

        data class FloatingSheetHeightsViewModel(
            val styleContentHeight: Int? = null,
            val shapeContentHeight: Int? = null,
        )
        val floatingSheetHeights: MutableStateFlow<FloatingSheetHeightsViewModel> =
            MutableStateFlow(FloatingSheetHeightsViewModel())

        if (isExtendibleThemeManager) {
            styleContent.viewTreeObserver.addOnGlobalLayoutListener(
                object : OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        if (
                            styleContent.height != 0 &&
                                floatingSheetHeights.value.styleContentHeight != styleContent.height
                        ) {
                            floatingSheetHeights.value =
                                floatingSheetHeights.value.copy(
                                    styleContentHeight = styleContent.height
                                )
                            // Keep the height of the style floating sheet fixed so text renders
                            // correctly after changing tabs.
                            styleContent.layoutParams =
                                styleContent.layoutParams.apply { height = styleContent.height }
                            styleContent.viewTreeObserver.removeOnGlobalLayoutListener(this)
                        }
                    }
                }
            )

            shapeContent.viewTreeObserver.addOnGlobalLayoutListener(
                object : OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        if (
                            shapeContent.height != 0 &&
                                floatingSheetHeights.value.shapeContentHeight != shapeContent.height
                        ) {
                            floatingSheetHeights.value =
                                floatingSheetHeights.value.copy(
                                    shapeContentHeight = shapeContent.height
                                )
                            shapeContent.viewTreeObserver.removeOnGlobalLayoutListener(this)
                        }
                    }
                }
            )
        }

        lifecycleOwner.lifecycleScope.launch {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
@@ -176,13 +226,51 @@ object AppIconFloatingSheetBinder {
                    }

                    launch {
                        viewModel.selectedTab.collect {
                            // TODO (b/397782741): add animation when switching tabs
                            styleContent.isVisible = (it == AppIconPickerViewModel.Tab.STYLE)
                            shapeContent.isVisible = (it == AppIconPickerViewModel.Tab.SHAPE)
                        val verticalPadding =
                            view.resources.getDimensionPixelSize(
                                R.dimen.floating_sheet_content_vertical_padding
                            )
                        var currentTab: Tab? = null
                        combine(floatingSheetHeights, viewModel.selectedTab, ::Pair).collect {
                            (heights, selectedTab) ->
                            val (styleContentHeight, shapeContentHeight) = heights
                            styleContentHeight ?: return@collect
                            shapeContentHeight ?: return@collect
                            selectedTab ?: return@collect

                            styleContent.isVisible = (currentTab == Tab.STYLE)
                            shapeContent.isVisible = (currentTab == Tab.SHAPE)

                            val fromHeight = floatingSheetContainer.height
                            val toHeight =
                                when (selectedTab) {
                                    Tab.STYLE -> styleContentHeight
                                    Tab.SHAPE -> shapeContentHeight
                                } + 2 * verticalPadding
                            val currentContent: View? =
                                when (currentTab) {
                                    Tab.STYLE -> styleContent
                                    Tab.SHAPE -> shapeContent
                                    else -> null
                                }
                            val selectedContent: View =
                                when (selectedTab) {
                                    Tab.STYLE -> styleContent
                                    Tab.SHAPE -> shapeContent
                                }
                            FloatingSheetHeightAnimationBinder.bind(
                                floatingSheetContainer,
                                fromHeight,
                                toHeight,
                                currentContent,
                                selectedContent,
                            )
                            currentTab = selectedTab
                        }
                    }
                } else {
                    styleContent.isVisible = false

                    launch {
                        viewModel.isShapeOptionsAvailable.collect { shapeAvailable ->
                            shapeContent.isVisible = shapeAvailable
+19 −39
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

package com.android.wallpaper.customization.ui.binder

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.view.View
import android.view.ViewGroup
@@ -71,7 +68,6 @@ import kotlinx.coroutines.launch
object ClockFloatingSheetBinder {
    private const val SLIDER_ENABLED_ALPHA = 1f
    private const val SLIDER_DISABLED_ALPHA = .3f
    private const val ANIMATION_DURATION = 200L

    private val _clockFloatingSheetHeights: MutableStateFlow<ClockFloatingSheetHeightsViewModel> =
        MutableStateFlow(ClockFloatingSheetHeightsViewModel())
@@ -318,6 +314,10 @@ object ClockFloatingSheetBinder {
                            clockSizeContentHeight ?: return@collect
                            axisPresetSliderHeight ?: return@collect

                            clockStyleContent.isVisible = currentTab == Tab.STYLE
                            clockColorContent.isVisible = currentTab == Tab.COLOR
                            clockSizeContent.isVisible = currentTab == Tab.SIZE

                            val fromHeight = floatingSheetContainer.height
                            val toHeight =
                                when (selectedTab) {
@@ -339,38 +339,22 @@ object ClockFloatingSheetBinder {
                                    Tab.COLOR -> clockColorContent
                                    Tab.SIZE -> clockSizeContent
                                }
                            val shouldCurrentContentFadeOut = currentTab != selectedTab
                            // Start to animate the content height
                            ValueAnimator.ofInt(fromHeight, toHeight)
                                .apply {
                                    addUpdateListener { valueAnimator ->
                                        val value = valueAnimator.animatedValue as Int
                                        floatingSheetContainer.layoutParams =
                                            floatingSheetContainer.layoutParams.apply {
                                                height = value
                                            }
                                        if (shouldCurrentContentFadeOut) {
                                            currentContent.alpha =
                                                getAlpha(fromHeight, toHeight, value)
                                        }
                                    }
                                    duration = ANIMATION_DURATION
                                    addListener(
                                        object : AnimatorListenerAdapter() {
                                            override fun onAnimationEnd(animation: Animator) {
                                                clockStyleContent.isVisible =
                                                    selectedTab == Tab.STYLE
                                                clockStyleContent.alpha = 1f
                                                clockColorContent.isVisible =
                                                    selectedTab == Tab.COLOR
                                                clockColorContent.alpha = 1f
                                                clockSizeContent.isVisible = selectedTab == Tab.SIZE
                                                clockSizeContent.alpha = 1f
                                            }
                            val selectedContent: View =
                                when (selectedTab) {
                                    Tab.STYLE -> clockStyleContent
                                    Tab.COLOR -> clockColorContent
                                    Tab.SIZE -> clockSizeContent
                                }
                            val fromContent =
                                if (currentTab != selectedTab) currentContent else null
                            val toContent = if (currentTab != selectedTab) selectedContent else null
                            FloatingSheetHeightAnimationBinder.bind(
                                floatingSheetContainer,
                                fromHeight,
                                toHeight,
                                fromContent,
                                toContent,
                            )
                                }
                                .start()
                            currentTab = selectedTab
                        }
                }
@@ -582,8 +566,4 @@ object ClockFloatingSheetBinder {
            colorUpdateViewModel = WeakReference(colorUpdateViewModel),
            shouldAnimateColor = shouldAnimateColor,
        )

    // Alpha is 1 when current height is from height, and 0 when current height is to height.
    private fun getAlpha(fromHeight: Int, toHeight: Int, currentHeight: Int): Float =
        (1 - (currentHeight - fromHeight).toFloat() / (toHeight - fromHeight).toFloat())
}
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wallpaper.customization.ui.binder

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.view.View
import androidx.core.view.isVisible

object FloatingSheetHeightAnimationBinder {
    private const val ANIMATION_DURATION = 200L

    fun bind(
        floatingSheetContainer: View,
        fromHeight: Int,
        toHeight: Int,
        fromContent: View? = null,
        toContent: View? = null,
    ) {
        ValueAnimator.ofInt(fromHeight, toHeight)
            .apply {
                addUpdateListener { valueAnimator ->
                    val value = valueAnimator.animatedValue as Int
                    floatingSheetContainer.layoutParams =
                        floatingSheetContainer.layoutParams.apply { height = value }
                    if (fromContent != null && toContent != null) {
                        fromContent.alpha = getAlpha(fromHeight, toHeight, value)
                    }
                }
                duration = ANIMATION_DURATION
                addListener(
                    object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            fromContent?.isVisible = false
                            fromContent?.alpha = 1f
                            toContent?.isVisible = true
                            toContent?.alpha = 1f
                        }
                    }
                )
            }
            .start()
    }

    // Alpha is 1 when current height is from height, and 0 when current height is to height.
    private fun getAlpha(fromHeight: Int, toHeight: Int, currentHeight: Int): Float =
        (1 - (currentHeight - fromHeight).toFloat() / (toHeight - fromHeight).toFloat())
}