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

Kotlin自定义Android视图中Visibility属性与onClick事件失效问题排查

Kotlin自定义Android视图中Visibility属性与onClick事件失效问题排查

我来帮你分析一下问题所在,以及对应的解决办法:

问题根源分析

1. Visibility属性失效的原因

你的自定义View继承了LinearLayout,但又在布局里嵌套了一层LinearLayout(ll_root)作为根容器。虽然理论上父View设置gone后子View也会隐藏,但这种冗余的层级可能导致布局属性传递异常,而且你后续的代码操作都针对子容器ll_root,没有确保自定义View本身的visibility状态同步到子容器。

2. onClick事件失效的原因

  • 子容器ll_root设置了clickable="true",会拦截点击事件,导致自定义View本身的点击监听无法触发;
  • 你重写setOnClickListener并将事件转发到ll_root的方式,无法兼容XML中android:onClick的设置——XML的点击监听是绑定到自定义View本身的,而事件被ll_root消费后无法传递到父View。

最优解决方案:移除冗余布局层级

推荐使用<merge>标签替代原布局的根LinearLayout,让自定义View本身作为根容器,彻底解决层级冗余带来的问题。

步骤1:修改自定义View的布局文件(view_card_button.xml)

<?xml version="1.0" encoding="utf-8"?>
<merge 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">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:src="@drawable/ic_search"
        app:tint="@color/neutral_color_00" />

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textColor="@color/neutral_color_00"
        android:textSize="16sp"
        tools:text="Impresso" />
</merge>

步骤2:修改自定义View的Kotlin代码

移除冗余的点击事件转发逻辑,将原根容器的属性转移到自定义View本身:

import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import android.content.res.Resources

// 添加dp转px的扩展函数
fun Int.dpToPx(): Int {
    val scale = Resources.getSystem().displayMetrics.density
    return (this * scale + 0.5f).toInt()
}

class CardButtonView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    private lateinit var mTvText: TextView
    private lateinit var mIvImage: ImageView

    @DrawableRes var src: Int = 0
    var text: String? = null
    var active: Boolean = false

    init {
        initView(context, attrs)
    }

    private fun initView(context: Context, attrs: AttributeSet?) {
        LayoutInflater.from(context).inflate(R.layout.view_card_button, this, true)

        mTvText = findViewById(R.id.tv_text)
        mIvImage = findViewById(R.id.iv_image)

        // 将原ll_root的属性转移到自定义View本身
        orientation = LinearLayout.VERTICAL
        gravity = Gravity.CENTER_HORIZONTAL
        setPadding(16.dpToPx(), 16.dpToPx(), 16.dpToPx(), 16.dpToPx())
        isClickable = true
        isFocusable = true

        attrs?.let {
            val typedArray = context.obtainStyledAttributes(
                it,
                R.styleable.CardButtonView,
                0,
                0
            )
            src = typedArray.getResourceId(R.styleable.CardButtonView_icon_src, 0)
            text = typedArray.getString(R.styleable.CardButtonView_text)
            active = typedArray.getBoolean(R.styleable.CardButtonView_active, false)
            typedArray.recycle()
        }

        text?.let { mTvText.text = it }
        if (src != 0) {
            mIvImage.setImageResource(src)
        }
        setActiveState(active)
    }

    fun setActiveState(isActive: Boolean) {
        active = isActive
        val foreColor = if (active) {
            ContextCompat.getColor(context, R.color.ColorNeutralColor00)
        } else {
            ContextCompat.getColor(context, R.color.brand_color_primary_pure)
        }

        // 将背景设置到自定义View本身
        setBackgroundResource(
            if (active) R.drawable.card_button_background_highlight 
            else R.drawable.card_button_background_outline
        )
        mTvText.setTextColor(foreColor)
        mIvImage.setColorFilter(foreColor)
    }
}

步骤3:验证使用方式

你的XML使用代码无需修改,保持原写法即可:

<br.com.company.toolbox.ux.view.CardButtonView
    android:id="@+id/btn_top_sale"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:visibility="gone"
    app:active="false"
    app:icon_src="@drawable/ic_trophy"
    app:layout_constraintTop_toBottomOf="@id/btn_product_update"
    app:text="@string/button_top_sales" />

修改后的效果

  1. Visibility属性正常生效:自定义View本身作为根容器,设置visibility会直接控制整个View的显示/隐藏,没有层级干扰;
  2. onClick事件正常触发:自定义View本身设置了clickable="true",无论是代码中调用setOnClickListener还是XML中使用android:onClick,都能正常响应点击;
  3. 优化布局性能:移除了冗余的LinearLayout嵌套,减少了布局层级。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 08:18:02