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

如何解决Android中java.lang.NullPointerException: null无法转为TextView问题

问题分析与解决方案

崩溃原因

直接触发崩溃的代码是MainActivity第25行的val tvTodoTitle = findViewById<TextView>(R.id.tvTodoTitle):

  • tvTodoTitle是RecyclerView列表项布局item_todo.xml中的元素,不存在于MainActivity的布局activity_main.xml中
  • 调用findViewById会返回null,而Kotlin的非null类型强制转换(as TextView)直接抛出NullPointerException

此外还有几个逻辑问题:

  1. 原TaskViewModel仅保存单个任务的标题/描述,不符合Todo应用管理任务列表的需求
  2. NewTaskSheet的onViewCreated方法错误调用了super.onCreate(savedInstanceState),而非super.onViewCreated(view, savedInstanceState)
  3. Adapter中使用findViewById绑定视图效率低下,未利用ViewBinding的优势

修复步骤与代码修改

1. 补充缺失的Todo数据类与TaskViewModel

// Todo.kt
data class Todo(
    val title: String,
    val description: String,
    var isChecked: Boolean
)
// TaskViewModel.kt
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class TaskViewModel : ViewModel() {
    // 用LiveData管理任务列表
    val taskList = MutableLiveData<MutableList<Todo>>(mutableListOf())

    // 添加新任务的方法
    fun addTask(title: String, description: String) {
        val currentList = taskList.value ?: mutableListOf()
        currentList.add(Todo(title, description, false))
        taskList.postValue(currentList)
    }
}

2. 修改MainActivity,移除错误代码并观察任务列表

// MainActivity.kt
package com.bignerdranch.android.taskmaster

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.bignerdranch.android.taskmaster.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var todoAdapter: TodoAdapter
    private lateinit var binding: ActivityMainBinding
    private lateinit var taskViewModel: TaskViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        taskViewModel = ViewModelProvider(this).get(TaskViewModel::class.java)

        todoAdapter = TodoAdapter(mutableListOf())
        binding.rvTodoItems.adapter = todoAdapter
        binding.rvTodoItems.layoutManager = LinearLayoutManager(this)

        binding.btnAddTodo.setOnClickListener {
            NewTaskSheet().show(supportFragmentManager, "NewTaskTag")
        }

        // 观察任务列表变化,更新RecyclerView
        taskViewModel.taskList.observe(this) { todos ->
            todoAdapter.updateTodos(todos)
        }
    }
}

3. 优化TodoAdapter,使用ViewBinding并添加列表更新方法

// TodoAdapter.kt
package com.bignerdranch.android.taskmaster

import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bignerdranch.android.taskmaster.databinding.ItemTodoBinding

class TodoAdapter(
    private val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    class TodoViewHolder(private val binding: ItemTodoBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(todo: Todo) {
            binding.tvTodoTitle.text = todo.title
            binding.cbDone.isChecked = todo.isChecked
            toggleStrikeThrough(binding.tvTodoTitle, todo.isChecked)

            binding.cbDone.setOnCheckedChangeListener { _, isChecked ->
                toggleStrikeThrough(binding.tvTodoTitle, isChecked)
                todo.isChecked = isChecked
            }

            binding.cbDone.setOnClickListener {
                todos.removeAll { it.isChecked }
                notifyDataSetChanged()
            }
        }

        private fun toggleStrikeThrough(tvTodoTitle: android.widget.TextView, isChecked: Boolean) {
            if (isChecked) {
                tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
            } else {
                tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        val binding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return TodoViewHolder(binding)
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        holder.bind(todos[position])
    }

    override fun getItemCount(): Int {
        return todos.size
    }

    // 更新任务列表的方法
    fun updateTodos(newTodos: List<Todo>) {
        todos.clear()
        todos.addAll(newTodos)
        notifyDataSetChanged()
    }
}

4. 修复NewTaskSheet的生命周期调用并完善保存逻辑

// NewTaskSheet.kt
package com.bignerdranch.android.taskmaster

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import com.bignerdranch.android.taskmaster.databinding.FragmentNewTaskSheetBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

class NewTaskSheet : BottomSheetDialogFragment() {

    private lateinit var binding: FragmentNewTaskSheetBinding
    private lateinit var taskViewModel: TaskViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentNewTaskSheetBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState) // 修复生命周期调用错误
        val activity = requireActivity()
        taskViewModel = ViewModelProvider(activity).get(TaskViewModel::class.java)
        
        binding.btnSave.setOnClickListener {
            saveAction()
        }
    }

    private fun saveAction() {
        val title = binding.title.text.toString().trim()
        val description = binding.description.text.toString().trim()
        if (title.isNotEmpty()) { // 校验标题不为空
            taskViewModel.addTask(title, description)
            binding.title.setText("")
            binding.description.setText("")
            dismiss()
        }
    }
}

5. 补充NewTaskSheet的布局文件(fragment_new_task_sheet.xml)

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

    <EditText
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Task Title"
        android:textSize="18sp"/>

    <EditText
        android:id="@+id/description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Task Description"
        android:layout_marginTop="8dp"
        android:minLines="3"/>

    <Button
        android:id="@+id/btnSave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Save"
        android:layout_gravity="end"
        android:layout_marginTop="16dp"/>
</LinearLayout>

内容的提问来源于stack exchange,提问作者Владислава Черкасова

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:54:55