当前位置: 首页 > news >正文

findViewById 所有可能的 null

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.rkmt.cn/news/74488.html

相关文章:

  • pbootcms如何实现留言内容自动发送到QQ邮箱(PbootCMS留言自动发送至QQ邮箱的实现方法)
  • 网站打开提示“No input file specified.”
  • VMware vSAN 9.0.1.0 - 数据中心存储虚拟化
  • 揭秘!5大正规有彩片专利艺术漆品牌,打造梦幻家居新体验
  • 2025年黑龙江艺考培训校长能力排行榜:姜伟博校长的决策能力
  • 2025年12月全自动纸袋机厂家权威推荐榜:手提纸袋机/环保纸袋制袋机/牛皮纸制袋机/纸袋加工设备,高效智能生产解决方案深度解析
  • Qt材料可视化深度解析:从图表到表格的完整指南
  • 2025 年 12 月苏州泡沫包装厂家权威推荐榜:定制异型保温箱与环保板材,专业加工与创新设计深度解析
  • 2025 年 12 月冷链仓储服务商权威推荐榜:生鲜食品、电商物流高效保鲜与智慧配送解决方案精选
  • 2025年12月软瓷砖厂家权威推荐榜:MCM软瓷/外墙软瓷/锦埴软瓷/仿木石纹/柔性石材,甄选高弹耐候与艺术质感兼备的源头品牌!
  • 2025 年 12 月高效混合机厂家权威推荐榜:盘条式/无重力/犁刀式/锥形/螺带/连续式混合机,专业应对粉体与粒状固体混合难题
  • pbootcms如何设置发布内容不自动提取缩略图
  • pbootcms模板发布文章显示的默认作者如何修改?
  • 2025年12月羽毛粉设备厂家推荐:行业权威排行榜单与选购策略分析
  • 第六章 verilog HDL学习笔记
  • 域适应新突破:比例渐进式伪标签法打造通用模型
  • 2025年广州GEO,广州GEO训练营,广州GEO实战培训厂商推荐,实战工艺与市场口碑深度解析
  • 杭州助听器验配中心深度测评:从专业度到售后,4家优质机构帮你避坑
  • virtio windows驱动(virtio-win官方iso包)
  • 2025年12月无害化设备厂家推荐:行业领先企业排行榜单及选购指南
  • 2025年12月改性包覆设备厂家推荐:权威排行榜单及深度对比评测
  • 2025年12月佛山GEO软件系统,佛山GEO训练营,佛山GEO内容代运营厂商推荐:产业带适配方案与合规能力解析
  • pbootcms模板报错提示PHP Warning: Unknown: open_basedir restriction
  • 2025年12月乐山美食店推荐:五大热门品牌深度对比评测与口碑排行榜
  • SQL数据查询语句
  • 河北诚铸机械集团有限公司联系方式: 使用指南与风险提示
  • 成都悦享源科技有限公司 联系方式: 家政培训服务咨询前准备事项
  • 2025年天津抖音账号代运营选哪家?十大专业抖音代运营服务公
  • 四川霖澳律师事务所怎么样?2025年最新实地探访与深度服务模式解析报告
  • 数据类型转换笔记