findViewById 所有可能的 null

news/2025/12/6 12:40:41/文章来源:https://www.cnblogs.com/zshsboke/p/19315451

findViewById 所有可能的 null

情况1

MainActivity.kt

package io.github.helloxmlimport android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompatprivate const val TAG: String = "MainActivity"internal class MainActivity internal constructor() : AppCompatActivity() {internal companion object {internal fun startActivity(context: Context) {context.startActivity(Intent(context, MainActivity::class.java))}}private val tvClick: TextView? by lazy {findViewById<TextView>(R.id.tv_click)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContentView(R.layout.activity_main)ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v: View, insets: WindowInsetsCompat ->val systemBars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)Log.i(TAG, "onCreate -> v: ${v.javaClass}")insets}initListener()}private fun initListener() {tvClick?.setOnClickListener {Log.i(TAG, "initListener container: ${findViewById<FrameLayout>(R.id.container)}")}}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/tv_click"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><io.github.helloxml.MyFrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="200dp"app:layout_constraintTop_toBottomOf="@id/tv_click"/></androidx.constraintlayout.widget.ConstraintLayout>

MyFrameLayout.kt

package io.github.helloxmlimport android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayoutinternal class MyFrameLayout internal constructor(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) // 1
internal class MyFrameLayout internal constructor(context: Context, attributeSet: AttributeSet) : FrameLayout(context) // 2

分别观察1和2看log是否返回MyFrameLayout,下面我们进行原理剖析。

打断点查看

AppCompatActivity.java

@NonNull
public AppCompatDelegate getDelegate() {if (mDelegate == null) {mDelegate = AppCompatDelegate.create(this, this);}return mDelegate;
}
@Override
public <T extends View> T findViewById(@IdRes int id) {return getDelegate().findViewById(id);
}

上面的findViewById返回值应该使用@Nullable标注,但是没有非常奇怪,新手可能会遇到空指针异常。
AppCompatDelegate.java

@Nullable
public abstract <T extends View> T findViewById(@IdRes int id);

查看ViewGroup.java


/*** {@hide}*/
@Override
protected <T extends View> T findViewTraversal(@IdRes int id) {if (id == mID) { // 1return (T) this;}final View[] where = mChildren;final int len = mChildrenCount;for (int i = 0; i < len; i++) {View v = where[i];if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {v = v.findViewById(id);if (v != null) {return (T) v;}}}return null;
}

情况1和情况2的唯一区别是mID一个为-1,一个不为-1;
接下来查看View.java

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {this(context);mSourceLayoutId = Resources.getAttributeSetSourceResId(attrs);// ...final int N = a.getIndexCount();for (int i = 0; i < N; i++) {int attr = a.getIndex(i);switch (attr) {// ...case com.android.internal.R.styleable.View_id:mID = a.getResourceId(attr, NO_ID);break;// ...}}
}

因为internal class MyFrameLayout internal constructor(context: Context, attributeSet: AttributeSet) : FrameLayout(context),没有走到com.android.internal.R.styleable.View_id:分支,所有findViewById为null.
解决了为什么findViewBy为null的原因1.

情况2 比较常见

错误引用另一个布局文件的id
假设存在另一个activity_main1.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/tv_click"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><io.github.helloxml.MyFrameLayoutandroid:id="@+id/container1"android:layout_width="match_parent"android:layout_height="200dp"app:layout_constraintTop_toBottomOf="@id/tv_click"/></androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

...
Log.i(TAG, "initListener container: ${findViewById<FrameLayout>(R.id.container1)}")
...

结果一定为null.

解决方法

可以使用viewBinding
配置如下

android {buildFeatures { viewBinding = true}
}

这样几乎不会出现viewId为空的任何问题.

当然标准的自定义View一般像下面那样写。

package io.github.helloxmlimport android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayoutinternal class MyFrameLayout @JvmOverloads internal constructor(context: Context,attributeSet: AttributeSet? = null,defStyleAttr: Int = 0
) : FrameLayout(context, attributeSet, defStyleAttr)

双向数据绑定

有些公司使用数据绑定dataBinding做双向数据绑定
例如

android {buildFeatures {viewBinding = truedataBinding = true}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout><data><variablename="name"type="String" /></data><androidx.constraintlayout.widget.ConstraintLayout 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"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/tv_click"android:text="@={name}"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><io.github.helloxml.MyFrameLayoutandroid:id="@+id/container1"android:layout_width="match_parent"android:layout_height="200dp"app:layout_constraintTop_toBottomOf="@id/tv_click"/></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

MainActivity

package io.github.helloxmlimport android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import io.github.helloxml.databinding.ActivityMain1Binding
import io.github.helloxml.databinding.ActivityMainBinding
import kotlin.random.Randomprivate const val TAG: String = "MainActivity"internal class MainActivity internal constructor() : AppCompatActivity() {internal companion object {internal fun startActivity(context: Context) {context.startActivity(Intent(context, MainActivity::class.java))}}private val binding: ActivityMain1Binding by lazy {ActivityMain1Binding.inflate(layoutInflater)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContentView(binding.root)ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v: View, insets: WindowInsetsCompat ->val systemBars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)Log.i(TAG, "onCreate -> v: ${v.javaClass}")insets}initListener()}private fun initListener() {binding.name = "Hello World"binding.tvClick.setOnClickListener {Log.i(TAG, "initListener container: ${binding.container1}")binding.name = Random.nextInt().toString()}}
}

还有一种使用的Kotlin synthetics

个人感觉还不如findViewById,因为各种空问题都解决不了还容易导入其他布局的id.
迁移文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/990098.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pbootcms如何实现留言内容自动发送到QQ邮箱(PbootCMS留言自动发送至QQ邮箱的实现方法)

1. 准备工作 将准备工作以列表形式呈现,方便用户快速了解需要完成的任务。准备两个QQ邮箱一个用于发件 一个用于收件开通服务发件邮箱需开通以下服务:POP3/SMTP服务 IMAP/SMTP服务获取邮箱授权码(非QQ登录密码)2. …

从资质、工艺到口碑严格筛选,2025年这份上海装修公司精选榜单请收好

装修的品质,始于企业的资质,成于施工的工艺,终于业主的口碑 —— 这是 2025 年上海装修公司精选榜单的筛选核心逻辑。为避免业主被 “虚假宣传” 误导,我们从企业资质合规性、施工工艺标准化、服务口碑真实性三大维…

网站打开提示“No input file specified.”

可能原因根目录存在 .user.ini 文件导致冲突。 伪静态规则配置错误。解决方法删除根目录下的 .user.ini 文件: rm /path/to/website/.user.ini检查伪静态规则是否正确(参考官方文档)。 如果问题仍未解决,尝试重启 …

Cisco Firepower 1000 Series FTD Software 7.7.11 - 思科 Firepower 威胁防御系统软件

Cisco Firepower 1000 Series FTD Software 7.7.11 - 思科 Firepower 威胁防御系统软件Cisco Firepower 1000 Series FTD Software 10.0.0 & ASA Software 9.24.1 Firepower Threat Defense (FTD) Software - 思科…

VMware vSAN 9.0.1.0 - 数据中心存储虚拟化

VMware vSAN 9.0.1.0 - 数据中心存储虚拟化VMware vSAN 9.0.1.0 - 数据中心存储虚拟化 vSAN 9 with Express Storage Architecture 请访问原文链接:https://sysin.org/blog/vmware-vsan-9/ 查看最新版。原创作品,转载…

2025厨余处理器怎么选?十大热门款处理器推荐

在2025年的厨余垃圾处理器市场中,面对众多品牌与型号,如何选择一台真正适合中餐厨房的高效、静音、防堵设备,已成为许多家庭关注的焦点。本文基于五大关键选购要点,结合十大热门品牌的综合对比,为您梳理出一份清晰…

PbootCMS在阿里云主机上邮件发送失败:服务器已经禁用stream_socket_client和fsockopen

报错原因报错提示明确指出:stream_socket_client 和 fsockopen 函数被禁用。 这两个函数通常用于通过 SMTP 协议发送邮件,如果都被禁用,邮件发送功能将无法正常工作。环境变化你提到刚更换了主机到阿里云,这可能是…

PbootCMS模版制作:当天发布的文章显示红色的方法

PbootCMS模版制作:当天发布的文章显示红色的方法{pboot:if([list:date style=m-d]==<?php echo date("m-d");?>)}style="color:red"{else}{/pboot:if}扫码添加技术【解决问题】专注网站运…

2025 年 12 月制氮碳分子筛厂家权威推荐榜:高效吸附与长寿命性能的工业节能之选

2025 年 12 月制氮碳分子筛厂家权威推荐榜:高效吸附与长寿命性能的工业节能之选 在工业气体分离领域,尤其是变压吸附(PSA)制氮技术中,碳分子筛作为核心吸附材料,其性能直接决定了氮气纯度、设备能耗、运行稳定性…

揭秘!5大正规有彩片专利艺术漆品牌,打造梦幻家居新体验

艺术漆品牌实力大揭秘:荷兰蔻帝领衔,打造高品质家居美学 在艺术漆市场蓬勃发展的今天,消费者对环保、品质与个性化的需求日益提升。如何从众多品牌中挑选出真正值得信赖的产品?本文以行业标杆品牌荷兰蔻帝为核心,…

2025年黑龙江艺考培训校长能力排行榜:姜伟博校长的决策能力

为助力艺考生及家长精准选择靠谱的艺考培训平台,本文聚焦校长核心能力维度(含决策前瞻性、团队凝聚力、口碑公信力),结合机构办学成果、师生满意度、行业影响力等指标,评选出2025年黑龙江地区艺考培训领域校长能力…

PbootCMS授权码设置,PbootCMS如何绑定多个域名

1. 授权码的作用PbootCMS通过授权码绑定域名,确保程序在指定域名下合法使用。 每个域名需要一个独立的授权码。2. 绑定多个域名的设置方法如果需要绑定多个域名,可以在后台设置多个授权码。 设置方式:将多个授权码用…

后台图片上传提示“上传失败:存储目录创建失败!”

可能原因静态资源目录(static 文件夹)权限不足。解决方法给根目录下的 static 文件夹设置权限为 755 或 777。bashchmod -R 755 /path/to/website/static推荐使用 755 权限以兼顾安全性和功能性。扫码添加技术【解决…

艺术漆品牌真实排名:5大优质品牌,助你轻松打造理想家居空间

在装修中,墙面装饰是提升空间质感的关键环节。艺术漆凭借其独特的纹理、丰富的色彩和优异的环保性能,逐渐成为高端家庭装修的首选。然而,面对市场上琳琅满目的品牌,如何选择一款真正优质的艺术漆?本文将基于产品性…

2025年12月全自动纸袋机厂家权威推荐榜:手提纸袋机/环保纸袋制袋机/牛皮纸制袋机/纸袋加工设备,高效智能生产解决方案深度解析

2025年12月全自动纸袋机厂家权威推荐榜:手提纸袋机/环保纸袋制袋机/牛皮纸制袋机/纸袋加工设备,高效智能生产解决方案深度解析 随着全球范围内“限塑令”的持续深化与消费者环保意识的普遍觉醒,纸包装行业正迎来前所…

PbootCMS登入失败:表单提交校验失败,请刷新后重试!

如果出现 登入失败,表单提交校验失败 ,请检查您的服务器环境,然后刷新页面重试 或者是删除 runtime 文件夹,然后刷新页面重试扫码添加技术【解决问题】专注网站运营、网站安全十余年。专业解决各种疑难杂症,您有任…

2025权威推荐:十大艺术涂料品牌推广服务商,形象好服务佳

在艺术涂料市场中,荷兰蔻帝无疑是一颗耀眼的明星。凭借其卓越的产品质量和服务水平,荷兰蔻帝在中国市场迅速崛起,成为行业内的佼佼者。本文将详细介绍荷兰蔻帝及其他几大优秀的艺术涂料品牌,帮助消费者更好地了解和…

2025 年 12 月卷包机厂家权威推荐榜:卷包机/压缩卷包机/棉被卷包机/枕头卷包机/全自动卷包机/床垫压缩卷包机,高效智能封装解决方案精选

2025 年 12 月卷包机厂家权威推荐榜:高效智能封装解决方案精选 在当今追求效率与空间优化的制造业与仓储物流领域,卷包机及其衍生的压缩卷包机、棉被卷包机、枕头卷包机、全自动卷包机、床垫压缩卷包机等设备,已成为…

Qt材料可视化深度解析:从图表到表格的完整指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

权威揭秘!进口艺术涂料TOP5品牌,哪个才是投资价值NO.1?

在装修市场中,艺术涂料凭借其独特的装饰效果和环保性能,越来越受到消费者的青睐。众多进口艺术涂料品牌纷纷涌入中国市场,让人眼花缭乱。今天,我们就来揭秘进口艺术涂料TOP10品牌,探寻哪个才是真正具有高投资价值…