Leanback : How do I load fragment only when header item clicked?

Viewed 336

Problem

I've been trying out Leanback's Page/Row fragment Example. As per the sample, we can use BrowseSupportFragment.FragmentFactory to show fragments when a header item gets focused. It works well, but I need to do some customization.

Question

  • How do I load the fragment only when the header is clicked, but not when focused?
  • How can I insert a custom header item without a row/page, so that I can launch a new activity when it is clicked?
1 Answers

My proposition of solving this problem:

TLDR; It is possible but, with some fragment nesting. You just need to add middle layer for child fragment and open it when middle layer fragment is focused.

Fragment factory -> proxyFocusableFragment -> targetFragment

Nesting visualised

Create layout in fragment_focusable_proxy.xml with fragment container for target fragment:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/proxy_root"
    android:focusable="true"
    android:orientation="vertical">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/proxy_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

It is very important to add flag android:focusable="true" to root view tag, otherwise you will not be able to catch focus in this fragment and side menu would "bounce back".

Create proxy fragment class with focus changer listener:

class FocusableProxyFragment : Fragment(), BrowseSupportFragment.MainFragmentAdapterProvider {

    private val adapter by lazy { BrowseSupportFragment.MainFragmentAdapter(this) }
    override fun getMainFragmentAdapter(): BrowseSupportFragment.MainFragmentAdapter<*> {
        return adapter
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View = inflater.inflate(R.layout.fragment_proxy_focusable, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.setOnFocusChangeListener { _, hasFocus ->
           //todo: here add target fragment and remove focus on middle layer
        }
    }
}

Then, inside focus listener implementation you need to remove this listener and set proxy layer not focusable, otherwise your target fragment will not work properly with leanback:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.setOnFocusChangeListener { _, hasFocus ->
            if (hasFocus) {
                removeProxyFocus(view)
                //todo: add fragment to container
            }
        }
    }

    private fun removeProxyFocus(view: View) {
        val rootView = view.findViewById<ViewGroup>(R.id.proxy_root)
        rootView.focusable = View.NOT_FOCUSABLE
        view.onFocusChangeListener = null
    }

Add target fragment to proxy container using childFragmentManager:

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.setOnFocusChangeListener { _, hasFocus ->
            if (hasFocus) {
                removeProxyFocus(view)
                instantiateTargetFragment()
            }
        }
    }

    private fun instantiateTargetFragment() {
        childFragmentManager
            .beginTransaction()
            .add(R.id.proxy_fragment_container, TargetFragment())
            .commit()
    }

Whole fragment class:

class FocusableProxyFragment : Fragment(), BrowseSupportFragment.MainFragmentAdapterProvider {

    private val adapter by lazy { BrowseSupportFragment.MainFragmentAdapter(this) }
    override fun getMainFragmentAdapter(): BrowseSupportFragment.MainFragmentAdapter<*> {
        return adapter
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View = inflater.inflate(R.layout.fragment_proxy_focusable, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.setOnFocusChangeListener { _, hasFocus ->
            if (hasFocus) {
                removeProxyFocus(view)
                instantiateTargetFragment()
            }
        }
    }

    private fun instantiateTargetFragment() {
        childFragmentManager
            .beginTransaction()
            .add(R.id.proxy_fragment_container, TargetFragment())
            .addToBackStack("cutv")
            .commit()
    }

    private fun removeProxyFocus(view: View) {
        val rootView = view.findViewById<ViewGroup>(R.id.proxy_root)
        rootView.focusable = View.NOT_FOCUSABLE
        view.onFocusChangeListener = null
    }
}

And at the end in your fragment factory replace original target fragment with proxy.

Related