Material Chipgroup 中的 Animate Chip 检查(Android)

问题描述 投票:0回答:2

我正在编写一个 Android 应用程序,用户必须使用

ChipGroup
Chips
选择一个选项。 一切工作正常,只是有点笨拙,因为除了选择
Chip
时的默认波纹之外没有动画。

我已阅读Material Design 3 文档并且发现此视频展示了我想要实现的漂亮动画,但我不知道如何实现。

我已经尝试过:

  1. 启用

    android:animateLayoutChanges="true"
    

    但这只是添加和删除

    Chip
    的动画,而不是检查和取消检查的动画。

  2. 使用

    TransitionManager.beginDelayedTransition(chipGroup);
    

    这在chipGroup上工作得很好,但是

    Chip
    的内容(刻度线出现和文本重新缩放)没有动画。


如果我做错了什么,请告诉我,这也是我用来添加和选择这些的方法

Chips

ChipAdapter adapter = new ChipAdapter(getContext());

    for(int i = 0; i < adapter.getCount(); i++){
        View chip = adapter.getView(i, chipGroup, chipGroup);
        if(chip instanceof Chip) {
            chip.setId(i);
            chip.setOnClickListener(v -> {
                for(int p = 0; p < chipGroup.getChildCount(); p++){
                    chipGroup.getChildAt(p).setSelected(false);
                }
                chip.setSelected(true);
            });
            chipGroup.addView(chip);
        }
    }
java android android-studio kotlin material-design
2个回答
3
投票

更新: 附加 Jetpack Compose 答案。

使用 XML

据我所知,没有嵌入方法可以简单地启用此动画,但我找到了两种方法来模仿链接视频中显示的动画

结果

选项1

选项2

代码及说明

选项1

此选项的工作原理是启用包含芯片的 ChipGroup 的

animateLayoutChanges
选项

android:animateLayoutChanges="true"

并为您的

ChipGroup
添加以下代码:

for (view in chipGroup.children) {
    val chip = view as Chip
    chip.setOnCheckedChangeListener { buttonView, _ ->
        val index = chipGroup.indexOfChild(buttonView)
        chipGroup.removeView(buttonView)
        chipGroup.addView(buttonView, index)
    }
}

每当芯片的选择状态发生变化时,此代码将自动删除芯片并立即将其添加回到ChipGroup。

缺点

  • 动画更像是 stateBefore ->无形 ->stateAfter 形式的过渡,而不是 stateBefore -> stateAfter 导致芯片“闪烁”

选项2

对于此选项,请将以下 自定义芯片类 (Kotlin) 添加到您的项目中,并将您的芯片更改为

CheckAnimationChip
而不是
com.google.android.material.chip.Chip
的实例:

import android.animation.ObjectAnimator
import android.content.Context
import android.util.AttributeSet
import androidx.core.animation.doOnEnd
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable

private const val CHIP_ICON_SIZE_PROPERTY_NAME = "chipIconSize"

// A value of '0f' would be interpreted as 'use the default size' by the ChipDrawable, so use a slightly larger value.
private const val INVISIBLE_CHIP_ICON_SIZE = 0.00001f

/**
 * Custom Chip class which will animate transition between the [isChecked] states.
 */
class CheckAnimationChip @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = com.google.android.material.R.attr.chipStyle
) : Chip(context, attrs, defStyleAttr) {

    private var onCheckedChangeListener: OnCheckedChangeListener? = null
    private var _chipDrawable: ChipDrawable
    private var defaultCheckedIconSize: Float
    private var currentlyScalingDown = false

    var animationDuration = 200L

    init {
        // Set default values for this category of chip.
        isCheckable = true
        isCheckedIconVisible = true

        _chipDrawable = chipDrawable as ChipDrawable
        defaultCheckedIconSize = _chipDrawable.chipIconSize

        super.setOnCheckedChangeListener { buttonView, isChecked ->
            if (currentlyScalingDown) {
                // Block the changes caused by the scaling-down animation.
                return@setOnCheckedChangeListener
            }
            onCheckedChangeListener?.onCheckedChanged(buttonView, isChecked)

            if (isChecked) {
                scaleCheckedIconUp()
            } else if (!isChecked) {
                scaleCheckedIconDown()
            }
        }
    }

    /**
     * Scale the size of the Checked-Icon from invisible to its default size.
     */
    private fun scaleCheckedIconUp() {
        ObjectAnimator.ofFloat(_chipDrawable, CHIP_ICON_SIZE_PROPERTY_NAME,
            INVISIBLE_CHIP_ICON_SIZE, defaultCheckedIconSize)
            .apply {
                duration =  animationDuration
                start()
                doOnEnd {
                    _chipDrawable.chipIconSize = defaultCheckedIconSize
                }
            }
    }

    /**
     * Scale the size of the Checked-Icon from its default size down to invisible. To achieve this, the
     * [isChecked] property needs to be manipulated. It is set to be true till the animation has ended.
     */
    private fun scaleCheckedIconDown() {
        currentlyScalingDown = true
        isChecked = true
        ObjectAnimator.ofFloat(_chipDrawable, CHIP_ICON_SIZE_PROPERTY_NAME,
            defaultCheckedIconSize, INVISIBLE_CHIP_ICON_SIZE)
            .apply {
                duration =  animationDuration
                start()
                doOnEnd {
                    isChecked = false
                    currentlyScalingDown = false
                    _chipDrawable.chipIconSize = defaultCheckedIconSize
                }
            }
    }

    override fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
        onCheckedChangeListener = listener
    }
}

此类使用ObjectAnimator

更改芯片图标
的大小。因此,它访问芯片的
ChipDrawable
并使用动画器更改
chipIconSize
属性。

缺点(相当挑剔)

  • 这只会对图标大小进行动画处理,而不是像链接视频中那样在芯片的可绘制对象之间实现完整的过渡(例如,在此实现中,没有边框或背景的平滑过渡)。
  • 您可以在动画期间观察到相邻芯片的闪烁(请参阅 gif 中的芯片“过去 4 周”),但是我只能在模拟器上观察到此问题,并且没有在物理设备上注意到它 .

Jetpack 撰写

在 Jetpack Compose 中,您可以使用

animateConentSize()
修饰符:

FilterChip(
    selected = selected,
    onClick = { /* Handle Click */ },
    leadingIcon = {
        Box(
            Modifier.animateContentSize(keyframes { durationMillis = 200 })
        ) {
            if (selected) {
                Icon(
                    imageVector = Icons.Default.Done,
                    contentDescription = null,
                    modifier = Modifier.size(FilterChipDefaults.IconSize)
                )
            }
        }
    },
    label = { /* Text */ }
)

这里重要的部分是始终有一个用于

Box
的可组合项(此处为
leadingIcon
),如果选择了芯片,则它保存复选图标,如果没有选择,则为空。然后可以使用
animateContentSize()
修改器平滑地设置该可组合项的动画效果。


0
投票

使用内置的布局过渡,您可以实现非常令人满意的动画像这样

首先,将

animateLayoutChanges="true"
添加到 XML 中的 ChipGroup。 这很重要,因为它实际上会向您的视图添加一个 LayoutTransition,否则您将得到一个空指针异常。

现在在您的 Activity 或 Fragment 中,您可以为 ChipGroup 设置转换类型:

ChipGroup filterChipGroup = view.findViewById(R.id.chip_group);

filterChipGroup.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);

您可以使用任何类型的 ViewGroup 来执行此操作。 (例如 LinearLayout、ConstraintLayout ...)

© www.soinside.com 2019 - 2024. All rights reserved.