rubensousa / DpadRecyclerView

A RecyclerView built for Android TV with Compose in mind and as a replacement for Leanback's BaseGridView.
https://rubensousa.github.io/DpadRecyclerView/
Apache License 2.0
136 stars 17 forks source link

java.lang.IllegalStateException: Cannot call removeView(At) within removeView(At) #241

Closed ChinaSunHZ closed 4 months ago

ChinaSunHZ commented 4 months ago

I think it has something to do with updating data when scrolling, but I haven't found out the specific reason.

java.lang.IllegalStateException: Cannot call removeView(At) within removeView(At) at androidx.recyclerview.widget.ChildHelper.removeView(Unknown Source:68) at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeView(Unknown Source:2) at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleView(Unknown Source:0) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycleViewAt(Unknown Source:10) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycle(Unknown Source:21) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycleFromEnd(Unknown Source:70) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycleByLayoutRequest(Unknown Source:29) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.StructureEngineer.fill(Unknown Source:31) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.StructureEngineer.scrollBy(Unknown Source:30) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.PivotLayout.scrollBy(Unknown Source:26) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.PivotLayout.scrollVerticallyBy(Unknown Source:20) at com.rubensousa.dpadrecyclerview.layoutmanager.PivotLayoutManager.scrollVerticallyBy(Unknown Source:12) at androidx.recyclerview.widget.RecyclerView.scrollStep(Unknown Source:39) at androidx.recyclerview.widget.RecyclerView.scrollByInternal(Unknown Source:21) at androidx.recyclerview.widget.RecyclerView.scrollBy(Unknown Source:41) at com.rubensousa.dpadrecyclerview.layoutmanager.scroll.LayoutScroller.scrollBy(Unknown Source:39) at com.rubensousa.dpadrecyclerview.layoutmanager.scroll.LayoutScroller.performScrollToView(Unknown Source:14) at com.rubensousa.dpadrecyclerview.layoutmanager.scroll.LayoutScroller.scrollToView(Unknown Source:80) at com.rubensousa.dpadrecyclerview.layoutmanager.focus.FocusDispatcher.onRequestChildFocus(Unknown Source:63) at com.rubensousa.dpadrecyclerview.layoutmanager.PivotLayoutManager.onRequestChildFocus(Unknown Source:17) at androidx.recyclerview.widget.RecyclerView.requestChildFocus(Unknown Source:4) at android.view.ViewGroup.requestChildFocus(ViewGroup.java:871) at android.view.View.handleFocusGainInternal(View.java:7859) at android.view.View.requestFocusNoSearch(View.java:13988) at android.view.View.requestFocus(View.java:13962) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3435) at android.view.ViewGroup.requestFocus(ViewGroup.java:3384) at android.view.View.requestFocus(View.java:13929) at android.view.View.requestFocus(View.java:13871) at com.rubensousa.dpadrecyclerview.layoutmanager.focus.FocusDispatcher.onFocusChanged(Unknown Source:71) at com.rubensousa.dpadrecyclerview.layoutmanager.PivotLayoutManager.onFocusChanged$dpadrecyclerview_release(Unknown Source:6) at com.rubensousa.dpadrecyclerview.DpadRecyclerView.onFocusChanged(Unknown Source:7) at android.view.View.handleFocusGainInternal(View.java:7867) at android.view.ViewGroup.handleFocusGainInternal(ViewGroup.java:847) at android.view.View.requestFocusNoSearch(View.java:13988) at android.view.View.requestFocus(View.java:13962) at android.view.ViewGroup.requestFocus(ViewGroup.java:3390) at android.view.View.requestFocus(View.java:13929) at android.view.View.requestFocus(View.java:13871) at com.rubensousa.dpadrecyclerview.DpadRecyclerView.removeViewAt(Unknown Source:26) at androidx.recyclerview.widget.RecyclerView$5.removeViewAt(Unknown Source:18) at androidx.recyclerview.widget.ChildHelper.removeView(Unknown Source:43) at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeView(Unknown Source:2) at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleView(Unknown Source:0) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycleViewAt(Unknown Source:10) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycle(Unknown Source:9) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycleFromStart(Unknown Source:58) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.ViewRecycler.recycleByLayoutRequest(Unknown Source:33) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.StructureEngineer.fill(Unknown Source:101) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.StructureEngineer.scrollBy(Unknown Source:30) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.PivotLayout.scrollBy(Unknown Source:26) at com.rubensousa.dpadrecyclerview.layoutmanager.layout.PivotLayout.scrollVerticallyBy(Unknown Source:20) at com.rubensousa.dpadrecyclerview.layoutmanager.PivotLayoutManager.scrollVerticallyBy(Unknown Source:12) at androidx.recyclerview.widget.RecyclerView.scrollStep(Unknown Source:39) at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(Unknown Source:115) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1315) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1323) at android.view.Choreographer.doCallbacks(Choreographer.java:956) at android.view.Choreographer.doFrame(Choreographer.java:863) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1298) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7941) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:569) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1019)

rubensousa commented 4 months ago

@ChinaSunHZ which version are you using? From the stacktrace, it doesn't look like the latest RCs

ChinaSunHZ commented 4 months ago

@ChinaSunHZ which version are you using? From the stacktrace, it doesn't look like the latest RCs

Version: com.rubensousa.dpadrecyclerview:dpadrecyclerview:1.2.0

ChinaSunHZ commented 4 months ago

App is view system, no use Jetpack Compose

ChinaSunHZ commented 4 months ago

DpadRecyclerView attrs in layout xml

<com.rubensousa.dpadrecyclerview.DpadRecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:clipChildren="false"
        android:nextFocusUp="@id/tv_label"
        android:orientation="vertical"
        app:dpadRecyclerViewParentAlignmentFraction="0.3"
        app:dpadRecyclerViewSmoothFocusChangesEnabled="false"
        tools:itemCount="10"
        tools:listitem="@layout/item_layout" />

dynamic set DpadRecyclerView attrs in fragment

rvList.apply {
    setItemAnimator(null)
    setInitialPrefetchItemCount(0)
    setItemPrefetchEnabled(false)
    setFocusSearchEnabledDuringAnimations(false)
    setOnKeyInterceptListener(this)
    addItemDecoration(itemDecoration)
    adapter = appAdapter
}

The app adapter extends from BaseQuickAdapter io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.14

Page Layout

ViewHolder have two view type, the page look like:

other label -> focusable
DpadRecyclerView
  label 1 -> the label item need skip focus
  (dynamic item 1)
  (dynamic item 2)
  (dynamic item 3)
  ...
  label 2 -> same to label 1
  (dynamic item 7)
  (dynamic item 8)
  (dynamic item 9)
  ...

when scroll down, need skip label 1 and label 2, like this: other view -> focus down (skip label 1) (dynamic item 1) -> focus down .... focus down ... focus down ... (skip label 2) (dynamic item 7)

So, i use DpadRecyclerView.OnKeyInterceptListener IMPL the feature

override fun onInterceptKeyEvent(event: KeyEvent): Boolean {
    if (event.action != KeyEvent.ACTION_UP) return false

    val itemCount = binding.rvList.adapter?.itemCount ?: return false

    val currentFocusedPosition = binding.rvList.getSelectedPosition()
    if (currentFocusedPosition == RecyclerView.NO_POSITION) return false
    val currentFocusedViewType =
        binding.rvList.adapter?.getItemViewType(currentFocusedPosition)

    @Suppress("UNCHECKED_CAST")
    (binding.rvList.adapter as? BaseQuickAdapter<Item, BaseHolder>)?.data?.get(currentFocusedPosition)

    // move focus to down or up, skip label item
    if (Constants.KEY_CODE_SLIDE_UP == event.keyCode) {
        if (currentFocusedViewType == AppAdapter.VIEW_TYPE_LABEL_1 && currentFocusedPosition == 0) {
            binding.ohterLabel.post { binding.ohterLabel.requestFocus() }
        } else if (currentFocusedViewType == AppAdapter.VIEW_TYPE_LABEL_2) {
            if (currentFocusedPosition == 0) {
                binding.ohterLabel.post { binding.ohterLabel.requestFocus() }
            } else {
                binding.rvList.post {
                    binding.rvList.requestFocus()
                    binding.rvList.setSelectedPosition(
                        (currentFocusedPosition - 1).coerceAtLeast(0),
                    )
                }
                return true
            }
        }
    } else if (Constants.KEY_CODE_SLIDE_DOWN == event.keyCode) {
        if (currentFocusedViewType == App.VIEW_TYPE_LABEL_1) {
            binding.rvList.post {
                binding.rvList.requestFocus()
                binding.rvList.setSelectedPosition(
                    (currentFocusedPosition + 1).coerceAtMost(itemCount - 1),
                )
            }
            return true
        } else if (currentFocusedViewType == AppAdapter.VIEW_TYPE_LABEL_2) {
            if (currentFocusedPosition == itemCount - 1) {
                binding.rvList.post {
                    binding.rvList.requestFocus()
                    binding.rvList.setSelectedPosition(
                        (currentFocusedPosition - 1).coerceIn(
                            currentFocusedPosition - 1,
                            currentFocusedPosition,
                        ),
                    )
                }
            } else {
                binding.rvList.post {
                    binding.rvList.requestFocus()
                    binding.rvList.setSelectedPosition(
                        (currentFocusedPosition + 1).coerceAtMost(itemCount - 1),
                    )
                }
            }
            return true
        }
    }
    return false
}

The above is all the code related to DpadRecyclerView used in this feature.

rubensousa commented 4 months ago

 @ChinaSunHZ please use the latest 1.3.0-rc02 instead of 1.2.0    To skip the focus of those labels, you don't need to intercept the key events. Just skip the attributes of isFocusable and that should work.    Please let me know if the issue still occurs after that change. I will have a look next week once I'm back from my vacation

ChinaSunHZ commented 4 months ago

@ChinaSunHZ please use the latest 1.3.0-rc02 instead of 1.2.0    To skip the focus of those labels, you don't need to intercept the key events. Just skip the attributes of isFocusable and that should work.    Please let me know if the issue still occurs after that change. I will have a look next week once I'm back from my vacation

Ok, I'll try 1.3.0-rc02. Thanks Bro!

rubensousa commented 4 months ago

@ChinaSunHZ I've added a fix for this crash in 1.3.0-rc03, please try it out

ChinaSunHZ commented 4 months ago

@ChinaSunHZ I've added a fix for this crash in 1.3.0-rc03, please try it out

Ok,Thanks Bro!