问题
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'sonCreateView
?
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