【问题标题】:RecyclerView: call notifyDataSetChanged during transition fragment == freezingRecyclerView:在转换片段期间调用 notifyDataSetChanged == 冻结
【发布时间】:2020-12-07 14:39:46
【问题描述】:

我没有找到类似的问题,所以我问。 我有: 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 上,我模拟在转换期间获取数据并显示数据:

类 SecondFragment : Fragment() {

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>

【问题讨论】:

    标签: android android-fragments android-recyclerview android-animation android-transitions


    【解决方案1】:

    如果您想等待 动画完成,您可以覆盖 FragmentonCreateAnimator#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
    

    【讨论】:

    • 在我的示例中 getData() 只是创建数组,它真的很快,在实际情况下我们必须使用异步代码,你是对的
    • 它的速度并不快,这就是为什么您可以在 UI 中看到暂停的原因。即使只有几毫秒,它仍然是导致 UI 冻结的原因
    • 我测试了它 - 异步加载数据(或者,我们从之前准备好的静态字段或其他东西中获取数据)不能解决问题。但无论如何,非常感谢您的建议。
    【解决方案2】:

    David Kroukamp 的回答对我没有帮助,因为 super.onCreateAnimator(transit, enter, nextAnim) 返回 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 我们无缘无故地等待。但是我们可以测量加载数据的时间。

    【讨论】:

      猜你喜欢
      • 2021-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多