Generated binding class that only takes a LayoutInflater as its sole argument

随声附和 提交于 2021-02-11 16:45:42

问题


The generated binding class from the Android Data Binding library has different versions of the inflate method: One that looks like the usual LayoutInflater.inflate method that takes a viewGroup and attachToRoot parameter, and one that only takes a LayoutInflater and nothing else.

The documentation doesn't explain the difference between the two and unfortunately Android Studio doesn't let me step through the source code of inflate so that I could figure out which values it is using internally for viewGroup and attachToRoot. When placing fragments on the screen, I notice no difference between the two. When I use it for RecyclerView items, the inflate method that only takes a LayoutInflater doesn't place the items properly.

So my question is: What values for viewGroup and attachToRoot does this method use internally and is it appropriate to use it in a fragment's onCreateView?


回答1:


What values for viewGroup and attachToRoot does this method use internally?

The parameters mirror those on LayoutInflater where root: ViewGroup is described as

Optional view to be the parent of the generated hierarchy (if attachToRoot is true), or else simply an object that provides a set of LayoutParams values for root of the returned hierarchy (if attachToRoot is false.) This value may be null.

Source: LayoutInflater docs, emphasis mine.

The root argument is used to generate LayoutParams from the layout_* XML attributes for the inflated view. Each layout may understand a different set of layout attributes and define a different set of defaults. That's why you should always pass root. The only place where that's not possible is when inflating dialog views.

The one-parameter method on the binding doesn't provide a root layout, so no LayoutParams are generated for the inflated view at this point. In other words, inflate(inflater) == inflate(inflater, null, false).

If a view doesn't have LayoutParams when you attach it, its new parent layout will generate default using generateDefaultLayoutParams.

If you follow what generateDefaultLayoutParams does in RecyclerView, it's delegated to the layout manager. Now look at the default LayoutParams provided by LinearLayoutManager:

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);
}

Typically, if you plan to have a vertical list you would set android:layout_width="match_parent" on the items. But when you attach a view without LayoutParams to a RecyclerView, it gets both dimensions set to wrap_content. That's why your layout looks wrong when inflated with root == null.


Is it appropriate to use [the one-parameter inflate method] in a fragment's onCreateView?

The one-parameter inflater is OK to use for inflating dialog views, where there's no parent/container/root view.

Typically, you would attach a fragment to a FrameLayout or FragmentContainerView (which extends FrameLayout). FrameLayout generates LayoutParams with match_parent width and height. If that's OK for your fragment, you can use the one-parameter inflate method on your binding.

Otherwise always use the provided/known parent to generate proper LaoyutParams. Don't rely on the default LayoutParams provided by the container you're attaching a view to, even if it's the same layout. You defined some layout params in XML, you'll want them to be respected.

Remember, there is some fragment lifecycle ceremony involved if you hold a reference to the binding.

Here's my current setup with fragments + view bindings:

// Nullable reference so we can use it later and clear it later.
private var binding: ExampleFragmentBinding? = null

private fun requireBinding() = checkNotNull(binding)

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
    // Respect the XML layout params.
    val binding = ExampleFragmentBinding.inflate(inflater, container, false)
    this.binding = binding
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val binding = requireBinding()
    // Do stuff with the views.
}

override fun onDestroyView() {
    // Detach view hierarchy reference from fragment, which may prevent memory leaks.
    this.binding = null

    super.onDestroyView()
}



回答2:


If you are using Kotlin, use this version only if layoutId is unknown in advance:

private lateinit var viewModel: MyViewModel

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val binding : MyFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.my_fragment, container, false)
    viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
    binding.myViewModel = viewModel
    binding.lifecycleOwner = this
    return binding.root
}

otherwise use the generated Binding class inflate method:

lateinit var binding: MyFragmentBinding 
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val binding = MyFragmentBinding.inflate(inflater, container, false)
    return binding.root
}

Update:

So my question is: What values for viewGroup and attachToRoot does this method use internally and is it appropriate to use it in a fragment's onCreateView?

When you call inflate with only inflater as paramter, null and false are the default values passed ie. when you do:

val binding: MyFragmentBinding = MyFragmentBinding.inflate(inflater)

it is similar to doing:

 val binding: MyFragmentBinding = MyFragmentBinding.inflate(inflater, null, false)


来源:https://stackoverflow.com/questions/61571381/generated-binding-class-that-only-takes-a-layoutinflater-as-its-sole-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!