RecyclerView:在转换片段期间调用 notifyDataSetChanged == 冻结
RecyclerView: call notifyDataSetChanged during transition fragment == freezing
没有找到类似的问题所以问一下。
我有:
FirstFragment、SecondFragment 和转换:
<navigation 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/nav_graph"
app:startDestination="@id/FirstFragment">
<fragment
android:id="@+id/FirstFragment"
android:name="testrenderinglistontransition.FirstFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first">
<action
app:enterAnim="@anim/right_in"
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment"/>
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="testrenderinglistontransition.SecondFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second">
<action
app:enterAnim="@anim/right_in"
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/FirstFragment"/>
</fragment>
</navigation>
在 SecondFragment 上,我模拟获取数据并在转换期间显示数据:
class 第二个片段:片段(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerview : RecyclerView = view.findViewById(R.id.recyclerview)
val progress : ProgressBar = view.findViewById(R.id.progress)
val adapter = CustomAdapter(arrayOf())
progress.visibility =View.VISIBLE
recyclerview.adapter = adapter
recyclerview.layoutManager = LinearLayoutManager(context).apply {
orientation = LinearLayoutManager.VERTICAL
}
val data = getData()
Handler().postDelayed(Runnable {
progress.visibility =View.GONE
adapter.dataSet = data
adapter.notifyDataSetChanged()
},250) // transition time is 300 so we should call notifyDataSetChanged before ending of transition
}
private fun getData() : Array<String>{
val list = mutableListOf<String>()
for(i in 0 .. 1000){
list.add(UUID.randomUUID().toString())
}
return list.toTypedArray()
}
}
我有:
你知道如何解决吗?我想我应该等待过渡结束。
您可以查看适配器和布局,但它们确实最简单:
class CustomAdapter(var dataSet: Array<String>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.textView)
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item, viewGroup, false)
return ViewHolder(view)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
viewHolder.textView.text = dataSet[position]
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = dataSet.size
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/image"
android:src="@android:drawable/alert_dark_frame"
android:layout_width="30dp"
android:layout_height="30dp"/>
<TextView
android:layout_alignLeft="@+id/image"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
如果您想等待 动画完成 ,您可以重写 Fragment
的 onCreateAnimator#onAnimationEnd
,然后调用一个方法来“初始化”您的像这样的观点:
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator? {
var animator = super.onCreateAnimator(transit, enter, nextAnim)
if (enter) {
animator?.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
initView()
}
})
}
return animator
}
private fun initView() {
// TODO create your recycler views etc here
}
但是这一行:
val data = getData()
应该从一个单独的线程调用,因为这确实是导致 UI 冻结的原因(您在 UI 线程上做了很多工作),例如:
fun loadData() = CoroutineScope(Dispatchers.Main).launch {
// TODO show progress bar here
val task = async(Dispatchers.IO) {
getData()
}
data = task.await()
adapter.dataSet = data
adapter.notifyDataSetChanged()
// TODO hide progress bar here
}
现在可以调用 loadData()
来代替所有这些行:
val data = getData()
Handler().postDelayed(Runnable {
progress.visibility =View.GONE
adapter.dataSet = data
adapter.notifyDataSetChanged()
},250) // transition time is 300 so we should call notifyDataSetChanged before ending of transition
David Kroukamp 的回答对我没有帮助,因为 super.onCreateAnimator(transit, enter, nextAnim) returns null.
但我有另一个解决方案:
Handler().postDelayed(Runnable {
progress.visibility =View.GONE
adapter.dataSet = data
adapter.notifyDataSetChanged()
},300) // transition time is 300 so we will wait end of transition.
有一个缺点:
如果数据加载需要200ms,我们会在200+300ms后绘制数据,所以200ms我们无缘无故地等待。但是我们可以测量加载数据的时间。
没有找到类似的问题所以问一下。 我有: FirstFragment、SecondFragment 和转换:
<navigation 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/nav_graph"
app:startDestination="@id/FirstFragment">
<fragment
android:id="@+id/FirstFragment"
android:name="testrenderinglistontransition.FirstFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first">
<action
app:enterAnim="@anim/right_in"
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment"/>
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="testrenderinglistontransition.SecondFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_second">
<action
app:enterAnim="@anim/right_in"
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/FirstFragment"/>
</fragment>
</navigation>
在 SecondFragment 上,我模拟获取数据并在转换期间显示数据:
class 第二个片段:片段(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerview : RecyclerView = view.findViewById(R.id.recyclerview)
val progress : ProgressBar = view.findViewById(R.id.progress)
val adapter = CustomAdapter(arrayOf())
progress.visibility =View.VISIBLE
recyclerview.adapter = adapter
recyclerview.layoutManager = LinearLayoutManager(context).apply {
orientation = LinearLayoutManager.VERTICAL
}
val data = getData()
Handler().postDelayed(Runnable {
progress.visibility =View.GONE
adapter.dataSet = data
adapter.notifyDataSetChanged()
},250) // transition time is 300 so we should call notifyDataSetChanged before ending of transition
}
private fun getData() : Array<String>{
val list = mutableListOf<String>()
for(i in 0 .. 1000){
list.add(UUID.randomUUID().toString())
}
return list.toTypedArray()
}
}
我有:
你知道如何解决吗?我想我应该等待过渡结束。
您可以查看适配器和布局,但它们确实最简单:
class CustomAdapter(var dataSet: Array<String>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.textView)
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item, viewGroup, false)
return ViewHolder(view)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
viewHolder.textView.text = dataSet[position]
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = dataSet.size
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/image"
android:src="@android:drawable/alert_dark_frame"
android:layout_width="30dp"
android:layout_height="30dp"/>
<TextView
android:layout_alignLeft="@+id/image"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
如果您想等待 动画完成 ,您可以重写 Fragment
的 onCreateAnimator#onAnimationEnd
,然后调用一个方法来“初始化”您的像这样的观点:
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator? {
var animator = super.onCreateAnimator(transit, enter, nextAnim)
if (enter) {
animator?.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
initView()
}
})
}
return animator
}
private fun initView() {
// TODO create your recycler views etc here
}
但是这一行:
val data = getData()
应该从一个单独的线程调用,因为这确实是导致 UI 冻结的原因(您在 UI 线程上做了很多工作),例如:
fun loadData() = CoroutineScope(Dispatchers.Main).launch {
// TODO show progress bar here
val task = async(Dispatchers.IO) {
getData()
}
data = task.await()
adapter.dataSet = data
adapter.notifyDataSetChanged()
// TODO hide progress bar here
}
现在可以调用 loadData()
来代替所有这些行:
val data = getData()
Handler().postDelayed(Runnable {
progress.visibility =View.GONE
adapter.dataSet = data
adapter.notifyDataSetChanged()
},250) // transition time is 300 so we should call notifyDataSetChanged before ending of transition
David Kroukamp 的回答对我没有帮助,因为 super.onCreateAnimator(transit, enter, nextAnim) returns null.
但我有另一个解决方案:
Handler().postDelayed(Runnable {
progress.visibility =View.GONE
adapter.dataSet = data
adapter.notifyDataSetChanged()
},300) // transition time is 300 so we will wait end of transition.
有一个缺点: 如果数据加载需要200ms,我们会在200+300ms后绘制数据,所以200ms我们无缘无故地等待。但是我们可以测量加载数据的时间。