You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

保留CardView圆角状态样式并添加ShineAnimation的方法

问题背景

我原本使用如下CardView布局:

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cardRoot"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginVertical="8dp"
    card_view:cardCornerRadius="16dp"
    card_view:contentPaddingLeft="15dp"
    card_view:contentPaddingRight="15dp"
    >
<Text...>
    />

并在Adapter中通过updateStyle方法手动处理点击时的样式切换:

private fun updateStyle(isSelected: Boolean) {
    if (isSelected) {
        binding.title.setTextColor(
            ContextCompat.getColor(
                binding.root.context,
                R.color.blue,
            ),
        )
        binding.cardRoot.setBackgroundColor(
            ContextCompat.getColor(
                binding.root.context,
                R.color.blue_disabled,
            ),
        )
    } else {
        binding.title.setTextColor(
            ContextCompat.getColor(
                binding.root.context,
                R.color.dark_grey,
            ),
        )
        binding.cardRoot.setBackgroundColor(
            ContextCompat.getColor(
                binding.root.context,
                R.color.grey,
            ),
        )
    }
}

当前实现运行正常,但现在需要添加**ShineAnimation(扫光动画)**效果。尝试用Button等其他View实现时,出现圆角与背景重叠的问题,参考相关方案后仍未解决。请问如何在保留原有功能(最好继续使用CardView)的前提下添加该动画效果?

解决方案

核心思路

CardView的圆角是通过裁剪实现的,直接在它上层加动画会导致动画内容超出圆角范围。所以需要把动画层放在CardView内部,让动画层继承CardView的圆角裁剪属性,避免重叠问题。

步骤1:修改CardView布局

在CardView内部添加一个承载扫光动画的View,铺满整个CardView,同时设置android:clipToOutline="true"继承圆角裁剪:

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cardRoot"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginVertical="8dp"
    card_view:cardCornerRadius="16dp"
    card_view:contentPaddingLeft="15dp"
    card_view:contentPaddingRight="15dp"
    android:clipChildren="true" <!-- 确保子View被裁剪 -->
    >

    <!-- 扫光动画层 -->
    <View
        android:id="@+id/shineLayer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/shine_gradient"
        android:clipToOutline="true" <!-- 继承CardView的圆角 -->
        android:visibility="gone" /> <!-- 默认隐藏 -->

    <!-- 原有的文本View -->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingVertical="12dp"
        tools:text="示例文本" />

</androidx.cardview.widget.CardView>

步骤2:创建扫光渐变Drawable

在res/drawable下创建shine_gradient.xml,定义透明到高亮再到透明的线性渐变:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:angle="0"
        android:endColor="@android:color/transparent"
        android:centerColor="#80FFFFFF"
        android:startColor="@android:color/transparent" />
</shape>

步骤3:实现ShineAnimation逻辑

在Adapter中添加动画控制方法,同时保留原有样式切换逻辑:

// 初始化扫光动画
private fun initShineAnimation() {
    val shineLayer = binding.shineLayer
    val anim = TranslateAnimation(
        Animation.RELATIVE_TO_SELF, -1.0f,
        Animation.RELATIVE_TO_SELF, 1.0f,
        Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.0f
    )
    anim.duration = 1000 // 动画时长
    anim.fillAfter = false
    anim.interpolator = AccelerateDecelerateInterpolator()
    shineLayer.animation = anim
}

// 点击触发动画+样式切换
fun onCardClicked(isSelected: Boolean) {
    updateStyle(isSelected)
    // 显示并播放扫光动画
    binding.shineLayer.visibility = View.VISIBLE
    binding.shineLayer.animation.start()
    // 动画结束后隐藏扫光层
    binding.shineLayer.animation.setAnimationListener(object : Animation.AnimationListener {
        override fun onAnimationStart(animation: Animation?) {}
        override fun onAnimationEnd(animation: Animation?) {
            binding.shineLayer.visibility = View.GONE
        }
        override fun onAnimationRepeat(animation: Animation?) {}
    })
}

// 保留原有样式切换方法,修正CardView背景设置方式
private fun updateStyle(isSelected: Boolean) {
    if (isSelected) {
        binding.title.setTextColor(
            ContextCompat.getColor(binding.root.context, R.color.blue)
        )
        binding.cardRoot.setCardBackgroundColor(
            ContextCompat.getColor(binding.root.context, R.color.blue_disabled)
        )
    } else {
        binding.title.setTextColor(
            ContextCompat.getColor(binding.root.context, R.color.dark_grey)
        )
        binding.cardRoot.setCardBackgroundColor(
            ContextCompat.getColor(binding.root.context, R.color.grey)
        )
    }
}

注意:这里把setBackgroundColor替换为CardView专属的setCardBackgroundColor,避免直接设置背景破坏圆角结构。

步骤4:绑定点击事件

在Adapter的onBindViewHolder中初始化动画并设置点击逻辑:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = getItem(position)
    holder.binding.title.text = item.title
    // 初始化动画
    holder.initShineAnimation()
    // 设置初始选中状态
    holder.updateStyle(item.isSelected)
    // 绑定点击事件
    holder.binding.cardRoot.setOnClickListener {
        item.isSelected = !item.isSelected
        holder.onCardClicked(item.isSelected)
        notifyItemChanged(position)
    }
}

关键注意点

  • 扫光层必须设置android:clipToOutline="true",否则动画内容会超出CardView圆角。
  • CardView要开启android:clipChildren="true",确保子View被裁剪。
  • 始终使用setCardBackgroundColor修改CardView背景,不要直接调用setBackgroundColor。

内容的提问来源于stack exchange,提问作者StuartDTO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 00:12:02