为什么`findViewById` 返回null?(使用`by lazy`)

Aug*_*rmo 5 android android-custom-view kotlin

我以那个小项目为例:

主活动.kt

package com.example.scratch

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
    }
}
Run Code Online (Sandbox Code Playgroud)

布局/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.scratch.CustomViewWithContent
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>
Run Code Online (Sandbox Code Playgroud)

CustomViewWithContainer.kt

package com.example.scratch

import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout

open class CustomViewWithContainer : ConstraintLayout {

    private val contentContainer by lazy {
        findViewById<FrameLayout>(R.id.content_container)
    }

    constructor(context: Context?) : super(context) {
        commonInit(context)
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        commonInit(context)
    }

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        commonInit(context)
    }

    protected open fun commonInit(context: Context?) {
        if (context == null) {
            return
        }

        inflate(context, R.layout.custom_view_with_container, this)

        if (isInEditMode) {
            return
        }
    }

    protected fun setContent(contentView: View) {
        contentContainer.removeAllViews()
        contentContainer.addView(contentView)

        (contentView.layoutParams as FrameLayout.LayoutParams).gravity = Gravity.CENTER
    }
}
Run Code Online (Sandbox Code Playgroud)

布局/custom_view_content.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is a text view" />

</FrameLayout>
Run Code Online (Sandbox Code Playgroud)

CustomViewWithContent.kt

package com.example.scratch

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.TextView

class CustomViewWithContent : CustomViewWithContainer {

    private lateinit var contentView: View

    private val textView by lazy {
        contentView.findViewById<TextView>(R.id.text_view)
    }

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    override fun commonInit(context: Context?) {
        super.commonInit(context)

        if (context == null) {
            return
        }

        contentView = inflate(context, R.layout.custom_view_content, null)

        if (isInEditMode) {
            return
        }

        setContent(contentView)

        Log.d(
            CustomViewWithContent::class.java.simpleName,
            "TextView id: ${textView.id}"
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

布局/custom_view_with_container

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

    <FrameLayout
        android:id="@+id/content_container"
        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" />

</merge>
Run Code Online (Sandbox Code Playgroud)

但是当我运行它时,它崩溃了,显示以下日志: 崩溃日志

我不知道为什么它会崩溃。我正在获取的视图的 ID 与可以在布局中找到的 ID 完全相同.xml

我很想知道懒惰返回null而不是视图本身的原因,因为它没有意义:/


截屏

通过那个gif,我们可以看到:

  • textView(这是 a lazy val)被认为是空的;
  • notNullTextView不为空,它运行在惰性 val 初始化块中找到的相同代码text view
  • textView在IDE的Watches中运行相同的初始化代码,它也返回null。

And*_*ana 0

findViewById事实上,你的问题根本不相关。你可以将你的懒惰替换为

private val textView by lazy {
    TextView(contentView.context)
}
Run Code Online (Sandbox Code Playgroud)

你会得到同样的错误!我认为这与lazy实现以及将未完全创建的对象传递给它然后尝试调用这一事实有关。不幸的是,我不能说到底错在哪里。

您可以轻松修复崩溃,lateinit例如:

class CustomViewWithContent : CustomViewWithContainer {

    private lateinit var contentView: View

    private lateinit var textView: TextView

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    override fun commonInit(context: Context?) {
        super.commonInit(context)

        if (context == null) {
            return
        }

        contentView = inflate(context, R.layout.custom_view_content, null)
        textView = contentView.findViewById(R.id.text_view)

        if (isInEditMode) {
            return
        }

        setContent(contentView)

        Log.d(
            CustomViewWithContent::class.java.simpleName,
            "TextView id: ${textView.id}"
        )
    }
}
Run Code Online (Sandbox Code Playgroud)