如何为TextView创建自定义视图并实现点击动态切换状态及动画恢复
实现方案说明
你确实需要添加init{}代码块来完成视图初始化、初始状态配置、点击事件绑定等基础逻辑,完整实现步骤和代码如下:
实现步骤
- 先声明内部视图引用、状态标记位、两种状态对应的资源常量,避免重复点击冲突
- 在
init块中完成TextView初始化、初始状态设置、点击事件绑定 - 完善
onClickToCopy逻辑:点击时切换到激活状态,协程等待5秒后通过淡入淡出动画恢复初始状态
完整实现代码
class MyCustomTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, val coroutineScope: CoroutineScope, ) : ConstraintLayout( context, attrs, defStyleAttr ) { // 内部承载文字和drawable的TextView private val contentTv: TextView // 状态标记:避免重复触发点击逻辑 private var isInActivatedState = false // 状态资源常量,可替换为你项目的实际资源,也可以通过自定义属性从xml配置 private val normalTextColor = ContextCompat.getColor(context, R.color.green) private val activatedTextColor = ContextCompat.getColor(context, R.color.gray) // 替换为点击后文字颜色 private val normalDrawable = ContextCompat.getDrawable(context, R.drawable.ic_normal)!! private val activatedDrawable = ContextCompat.getDrawable(context, R.drawable.ic_activated)!! private val normalTint = ContextCompat.getColor(context, R.color.green) private val activatedTint = ContextCompat.getColor(context, R.color.gray) // 替换为点击后drawable着色 init { // 初始化TextView,也可以加载自定义xml布局替代手动创建 contentTv = TextView(context).apply { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) // 初始状态配置 setCompoundDrawablesRelativeWithIntrinsicBounds(normalDrawable, null, null, null) compoundDrawableTintList = ColorStateList.valueOf(normalTint) setTextColor(normalTextColor) compoundDrawablePadding = 8.dp2px(context) // 文字和drawable的间距,按需调整 text = "点击复制" // 按需修改展示文字 } addView(contentTv) // 绑定点击事件 setOnClickListener { onClickToCopy() } } fun onClickToCopy(){ if (isInActivatedState) return isInActivatedState = true // 切换到激活状态 updateViewState(activatedTextColor, activatedDrawable, activatedTint) coroutineScope.launch { delay(5000L) // 切回主线程执行恢复动画 withContext(Dispatchers.Main) { // 300ms淡入淡出动画,时长可调整 val fadeOut = AlphaAnimation(1f, 0f).apply { duration = 150 } val fadeIn = AlphaAnimation(0f, 1f).apply { duration = 150; startOffset = 150 } val animSet = AnimationSet(true).apply { addAnimation(fadeOut) addAnimation(fadeIn) } // 淡出结束后更新状态,再淡入展示初始状态 fadeOut.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationStart(animation: Animation?) {} override fun onAnimationRepeat(animation: Animation?) {} override fun onAnimationEnd(animation: Animation?) { updateViewState(normalTextColor, normalDrawable, normalTint) } }) startAnimation(animSet) isInActivatedState = false } } } // 统一更新视图状态的工具方法 private fun updateViewState(textColor: Int, startDrawable: Drawable, tintColor: Int) { contentTv.apply { setTextColor(textColor) setCompoundDrawablesRelativeWithIntrinsicBounds(startDrawable, null, null, null) compoundDrawableTintList = ColorStateList.valueOf(tintColor) } } // dp转px扩展方法,按需保留 private fun Int.dp2px(context: Context): Int { return (this * context.resources.displayMetrics.density).toInt() } }
注意事项
- 如果你已经有对应xml布局,
init块可以直接加载布局并通过findViewById获取TextView,不需要手动创建 - 传入的
coroutineScope建议和视图生命周期绑定,避免视图销毁后协程仍执行导致内存泄漏,推荐使用视图对应的LifecycleCoroutineScope
内容的提问来源于stack exchange,提问作者StuartDTO
相关产品推荐
相关产品推荐

