如何在NavigationDrawer中使用RecyclerView并修复空指针异常
目标效果

实现抽屉内包含输入框、添加按钮、支持滑动删除的列表,因条目数量有限无需做性能优化。
问题根因
你遇到的NullPointerException原因非常明确:你把RecyclerView放在了NavigationView的app:headerLayout对应的nav_header.xml里,这类header布局是延迟加载的,不会随Activity的setContentView同步实例化,直接在Activity根视图调用findViewById(R.id.recyclerView)必然返回null。
另外你当前的布局结构本身存在设计问题:NavigationView的header默认是固定在抽屉顶部的自适应高度区域,你给nav_header根布局设了match_parent高度再塞RecyclerView,后续滑动、布局都会出现异常,根本达不到预期效果。
最优实现方案
直接放弃NavigationView套自定义布局的写法。NavigationView本身是为固定菜单项设计的控件,你需要带输入、按钮、动态列表的抽屉,直接把自定义布局作为DrawerLayout的start侧子视图即可,结构更简单,也省掉视图查找的额外坑。
具体实现步骤
1. 修正主布局activity_main.xml
移除原有NavigationView,直接将输入框、按钮、RecyclerView作为左侧抽屉的内容,从根源解决控件查找不到的问题:
<?xml version="1.0" encoding="utf-8"?> <androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawerLayout" android:layout_height="match_parent" tools:context=".MainActivity"> <!-- 主页面内容区域 --> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"/> <!-- 左侧自定义抽屉 --> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="320dp" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" android:background="@android:color/white"> <Button android:id="@+id/btnAdd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:layout_marginEnd="5dp" android:text="添加" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/etInput" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:ems="10" android:inputType="text" android:minHeight="48dp" android:hint="输入条目名称" app:layout_constraintBottom_toBottomOf="@+id/btnAdd" app:layout_constraintEnd_toStartOf="@+id/btnAdd" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/btnAdd" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="1dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/etInput" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.drawerlayout.widget.DrawerLayout>
改完后所有控件都属于Activity视图层级,直接调用findViewById就能获取,空指针问题直接解决,原有的nav_header.xml可以删除。
2. 改造RecyclerAdapter.java,支持数据增删
你当前的Adapter写死返回10个条目,没有绑定数据源,需要补充数据结构和增删方法:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{ // 列表条目数据结构,可根据需求自行扩展字段 public static class ItemData{ public String title; public String desc; public ItemData(String title,String desc){ this.title = title; this.desc = desc; } } private List<ItemData> mDataList = new ArrayList<>(); // 新增条目方法 public void addItem(ItemData data){ mDataList.add(data); notifyItemInserted(mDataList.size()-1); } // 删除指定位置条目方法 public void removeItem(int position){ if(position >=0 && position < mDataList.size()){ mDataList.remove(position); notifyItemRemoved(position); } } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item,parent,false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { ItemData data = mDataList.get(position); holder.t_Title.setText(data.title); holder.t_Description.setText(data.desc); // 可在此处给imageView设置对应图标 } @Override public int getItemCount() { return mDataList.size(); } class ViewHolder extends RecyclerView.ViewHolder{ ImageView imageView; TextView t_Title,t_Description; public ViewHolder(@NonNull View itemView) { super(itemView); imageView=itemView.findViewById(R.id.imageView); t_Description=itemView.findViewById(R.id.t_Description); t_Title=itemView.findViewById(R.id.t_Title); } } }
row_item.xml无需修改,保持原有代码即可。
3. 完善MainActivity.java逻辑,实现添加、滑动删除功能
绑定控件,添加按钮点击逻辑,借助官方ItemTouchHelper实现滑动删除:
public class MainActivity extends AppCompatActivity { ActionBarDrawerToggle toggle=null; RecyclerView recyclerView; RecyclerAdapter recyclerAdapter; EditText etInput; Button btnAdd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DrawerLayout drawerLayout=findViewById(R.id.drawerLayout); toggle= new ActionBarDrawerToggle(this,drawerLayout,R.string.drawerOpen,R.string.drawerClose); drawerLayout.addDrawerListener(toggle); toggle.syncState(); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); // 初始化控件 etInput = findViewById(R.id.etInput); btnAdd = findViewById(R.id.btnAdd); recyclerView=findViewById(R.id.recyclerView); recyclerAdapter= new RecyclerAdapter(); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(recyclerAdapter); // 添加条目逻辑 btnAdd.setOnClickListener(v -> { String content = etInput.getText().toString().trim(); if(!content.isEmpty()){ recyclerAdapter.addItem(new RecyclerAdapter.ItemData(content,"默认描述")); etInput.setText(""); } }); // 滑动删除逻辑 支持左右滑动删除 new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { int adapterPosition = viewHolder.getAdapterPosition(); recyclerAdapter.removeItem(adapterPosition); } }).attachToRecyclerView(recyclerView); } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if(toggle.onOptionsItemSelected(item)){ return true; } return super.onOptionsItemSelected(item); } }
如果你一定要保留NavigationView的写法,必须先调用
NavigationView navigationView = findViewById(R.id.nav_view); View headerView = navigationView.getHeaderView(0);拿到header的根视图,再通过headerView.findViewById(R.id.recyclerView)获取RecyclerView实例,但这种写法后续给header内控件加监听都要多一步获取header的操作,灵活性远不如直接用自定义抽屉布局。
内容的提问来源于stack exchange,提问作者UsualSquare

