我一直在网上跟踪创建RecyclerView的示例。我唯一不同的是将RecyclerView放入Fragment中,而不是将其放入MainActivity中。 RecyclerView可以很好地显示数据。但是,当我导航到另一个Fragment时,应用程序崩溃并显示与RecyclerView相关的异常:
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.support.v7.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null object reference
这是一个复制的最小示例:
MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
}
}
main_layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="package.RecyclerFragment"
android:id="@+id/fragment"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
RecyclerFragment:
class RecyclerFragment : Fragment() {
private val data = listOf("Moscow", "Washington")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
setHasOptionsMenu(true)
val view = inflater.inflate(R.layout.recycler_list, container, false)
view.findViewById<RecyclerView>(R.id.list)?.apply {
adapter = RecyclerAdapter(data)
}
return view
}
override fun onCreateOptionsMenu(menu: Menu?, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.navigate -> {
fragmentManager?.beginTransaction()?.replace(R.id.fragment, HelloFragment())?.commit()
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
recycler_list:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/list"
android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
RecyclerAdapter:
class RecyclerAdapter(private val data: List<String>):
RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
inner class ViewHolder(val view: CardView): RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(root: ViewGroup, viewType: Int): ViewHolder {
val listItem = LayoutInflater.from(root.context)
.inflate(R.layout.list_item, root, false) as CardView
return ViewHolder(listItem)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.view.findViewById<TextView>(R.id.text).text = data[position]
}
override fun getItemCount() = data.size
}
list_item:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="5sp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20sp"
android:textSize="20sp"
android:id="@+id/text"/>
</android.support.v7.widget.CardView>
菜单:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/navigate"
android:title="Navigate"
app:showAsAction="ifRoom"/>
</menu>
HelloFragment:
class HelloFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.hello, container, false)
}
}
你好:
<?xml version="1.0" encoding="utf-8"?>
<TextView android:text="Hello"
android:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"/>
此实现是否存在问题?如何在片段中使用RecyclerView?
答案 0 :(得分:2)
@Jeel和@Birju向您展示了使用片段的正确方法,但是如果您想更深入地了解您的实现为何无效的情况,我还是留下我的答案。
原因:
首先,查看main_layout:
<ConstraintLayout>
<fragment class="package.RecyclerFragment"
android:id="@+id/fragment"
... />
</ConstraintLayout>
当main_layout
在MainActivity中膨胀时,<fragment>
元素将简单地由RecyclerFragment的布局(也称为recycler_list
布局)中包含的任何元素替换。
因此main_layout
实际上将变为:
<ConstraintLayout>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/list"
android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ConstraintLayout>
如果将这些代码放在MainActivity的onResume()中,则可以清楚地看到它:
override fun onResume() {
super.onResume()
val parent = findViewById<ConstraintLayout>(R.id.constraint)
val numChild = parent.childCount
val childView = parent.getChildAt(0)
Log.d("Parent", parent.toString())
Log.d("NumChild", numChild.toString())
Log.d("ChildView", childView.toString())
return
}
// Log
D/Parent: androidx.constraintlayout.widget.ConstraintLayout{a6e5545 V.E...... ......I. 0,0-0,0 #7f07004d app:id/constraint}
D/NumChild: 1
D/ChildView: androidx.recyclerview.widget.RecyclerView{753849a VFED..... ......I. 0,0-0,0 #7f070060 app:id/fragment}
因此,当您呼叫此行时:
fragmentManager?.beginTransaction()?.replace(R.id.fragment, HelloFragment())?.commit()
实际上,它把RecyclerView作为容器视图组,并将HelloFragment布局中的所有内容添加到RecyclerView
作为证据,您可以在 FragmentManager 类中查看以下几行:
// mContainerId here is R.id.fragment in your layout
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
// Now container is RecyclerView
...
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState)
// After this line, f.mView is the view inside hello.xml
...
container.addView(f.mView);
// Add layout in hello.xml into RecyclerView
由于RecyclerView旨在保留从适配器中的数据创建的ViewHolders,因此即使在Hello的片段视图添加到RecyclerView内并删除了所有ViewHolders之后,它仍然保留名为 childCount 的变量(在这种情况下,= 3)从它。
添加新视图后,RecyclerView调度新布局,然后调用名为findMinMaxChildLayoutPositions()的函数
private void findMinMaxChildLayoutPositions(int[] into) {
final int count = mChildHelper.getChildCount();
...
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
如您所见,由于所有ViewHolders均已删除,因此holder will be null
和NPE将在行if (holder.shouldIgnore()) {
处抛出
感谢您阅读冗长的答案!
答案 1 :(得分:1)
方法1
在RecyclerView
布局中,请勿将RecyclerFragment
用作父项。像这样将其包装在LinearLayout
中:
recycler_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
方法2
在您当前的实现中,您已经从xml中添加了RecyclerFragment
,但是当您尝试将其替换为HelloFragment
时,它不会被替换,而是会在其上方或下方添加新的片段。>
要正确实施此操作,应从活动的RecyclerFragment
方法中添加onCreate
,如下所示,并将其从xml中删除:
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.add(R.id.root,RecyclerFragment())
.commit()
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
当您要替换选项项目上的片段时,请执行以下操作:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.navigate -> {
requireActivity().supportFragmentManager.beginTransaction().replace(R.id.root, HelloFragment(), "Hello")
.commit()
true
}
else -> super.onOptionsItemSelected(item)
}
}
这样,您之前的片段将被删除,新的片段将被添加。
答案 2 :(得分:1)
如果您想在Fragment
内部动态更改Activity
,我建议您稍作改动。
要更改活动中的片段时,请在MainActivity
的布局文件(main_layout)中获取FrameLayout,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fragment_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
然后在MainActivity
中以编程方式替换片段,最初,我们将RecyclerFragment
加载到fragment_container
中。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, RecyclerFragment())
}
}
向ViewGroup
片段提供RecyclerFragment
(可选,但建议):
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/list"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
现在,运行您的项目,一切将正常运行。
关于您的崩溃:
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.support.v7.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null object reference
之所以NullPointerException
是因为当您导航到下一个片段HelloFragment
时,RecyclerFragment
进入分离状态,并且对象RecyclerView
当时为 null 实例。