当前位置: 首页 > 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.jsqmd.com/news/63935/

相关文章:

  • pbootcms如何实现留言内容自动发送到QQ邮箱(PbootCMS留言自动发送至QQ邮箱的实现方法)
  • 网站打开提示“No input file specified.”
  • Cisco Firepower 1000 Series FTD Software 7.7.11 - 思科 Firepower 威胁防御系统软件
  • VMware vSAN 9.0.1.0 - 数据中心存储虚拟化
  • 2025厨余处理器怎么选?十大热门款处理器推荐
  • PbootCMS在阿里云主机上邮件发送失败:服务器已经禁用stream_socket_client和fsockopen
  • PbootCMS模版制作:当天发布的文章显示红色的方法
  • 2025年12月全自动纸袋机厂家权威推荐榜:手提纸袋机/环保纸袋制袋机/牛皮纸制袋机/纸袋加工设备,高效智能生产解决方案深度解析
  • 2025 年 12 月制氮碳分子筛厂家权威推荐榜:高效吸附与长寿命性能的工业节能之选
  • 揭秘!5大正规有彩片专利艺术漆品牌,打造梦幻家居新体验
  • 2025年黑龙江艺考培训校长能力排行榜:姜伟博校长的决策能力
  • PbootCMS授权码设置,PbootCMS如何绑定多个域名
  • 后台图片上传提示“上传失败:存储目录创建失败!”
  • 艺术漆品牌真实排名:5大优质品牌,助你轻松打造理想家居空间
  • PbootCMS登入失败:表单提交校验失败,请刷新后重试!
  • 2025权威推荐:十大艺术涂料品牌推广服务商,形象好服务佳
  • 2025 年 12 月卷包机厂家权威推荐榜:卷包机/压缩卷包机/棉被卷包机/枕头卷包机/全自动卷包机/床垫压缩卷包机,高效智能封装解决方案精选
  • Qt材料可视化深度解析:从图表到表格的完整指南
  • 权威揭秘!进口艺术涂料TOP5品牌,哪个才是投资价值NO.1?
  • PbootCMS调用内容中换行符“”不换行怎么办(PbootCMS内容中换行符不生效的解决方法)
  • Goer-系列1-Dockerfile构建自定义镜像 - 详解
  • 2025 年 12 月苏州泡沫包装厂家权威推荐榜:定制异型保温箱与环保板材,专业加工与创新设计深度解析
  • 地域为根,协作成魂:HEBE 百年制表背后的汝拉社群智慧
  • 如何修改网站文件的发表日期(如何修改网站文章的发表时间)
  • PbootCms模板中怎么写PHP代码(PbootCMS 模板中嵌入 PHP 代码的方法与注意事项)
  • 1
  • 2025 年 12 月冷链仓储服务商权威推荐榜:生鲜食品、电商物流高效保鲜与智慧配送解决方案精选
  • 2025年12月软瓷砖厂家权威推荐榜:MCM软瓷/外墙软瓷/锦埴软瓷/仿木石纹/柔性石材,甄选高弹耐候与艺术质感兼备的源头品牌!
  • 2025 年 12 月高效混合机厂家权威推荐榜:盘条式/无重力/犁刀式/锥形/螺带/连续式混合机,专业应对粉体与粒状固体混合难题
  • 2025年中国装配式建筑房屋设计安装公司TOP5推荐:装配式