即使指定了 "match_parent",使用 ConstraintLayout 的 RecyclerView 项目也不会填满屏幕的整个宽度
RecyclerView items using ConstraintLayout are not filling the entire width of the screen even though "match_parent" is specified
我正在尝试设置回收器视图,并使用 ConstraintLayout 显示 元素。我用了 layout from this example by Google as a guideline.
然而,尽管一直指定 android:layout_width="match_parent"
,但最终显示在屏幕上的结果更类似于 wrap_content
。
到目前为止我尝试了什么
- 将 ConstraintLayout 更改为 LinearLayout
- 此
中建议的解决方法
- 设置
android:layout_width=0
,同时使用ConstraintLayout并将开始、结束和顶部锚定到父级
- 将整个包装在 CardView 中
- 运行 多个设备 上的应用程序, 模拟和物理
相关代码
如果遗漏任何其他感兴趣的内容,请告诉我。
recipe_list_entry.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="@dimen/recipe_entry_height" >
<TextView
android:id="@+id/recipe_title"
style="@style/Widget.RecipeTracker.ListItemTextView"
android:layout_marginHorizontal="@dimen/margin_between_elements"
android:background="@color/design_default_color_on_secondary"
android:layout_width="match_parent"
android:fontFamily="sans-serif"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:maxLines="1"
android:ellipsize="end"
tools:text="amazing stuff"/>
</androidx.constraintlayout.widget.ConstraintLayout>
styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.RecipeTracker.ListItemTextView" parent="Widget.MaterialComponents.TextView">
<item name="android:gravity">center_vertical</item>
<item name="android:layout_height">48dp</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
</resources>
recipe_list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RecipeListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_between_elements"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_entry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/add_entry_fab_margin"
android:layout_marginBottom="@dimen/add_entry_fab_margin"
android:contentDescription="@string/add_new_recipe_contentDescription"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_add" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecipeListFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import me.lindenbauer.recipetracker.databinding.RecipeListFragmentBinding
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class RecipeListFragment : Fragment() {
private val viewModel: RecipeTrackerViewModel by activityViewModels {
RecipeTrackerViewModelFactory(
(activity?.application as RecipeTrackerApplication).database.recipeDao()
)
}
private var _binding: RecipeListFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = RecipeListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set up the automatic data updates for the recyclerView using the RecipeListAdapter
val adapter = RecipeListAdapter {
// TODO: Define onRecipeClicked here
}
// This specifies how the "cards" are displayed by the recyclerView
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.recyclerView.adapter = adapter
// Attach an observer on the allItems list to update the UI automatically when the data
// changes.
viewModel.allRecipes.observe(this.viewLifecycleOwner) { recipes ->
recipes.let{ adapter.submitList(it) }
}
binding.addEntry.setOnClickListener{
findNavController().navigate(R.id.action_RecipeListFragment_to_AddEntryFragment)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
RecipeListAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import me.lindenbauer.recipetracker.data.Recipe
import me.lindenbauer.recipetracker.databinding.RecipeListEntryBinding
class RecipeListAdapter(private val onRecipeClicked: (Recipe) -> Unit):
ListAdapter<Recipe, RecipeListAdapter.RecipeViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
return RecipeViewHolder(
RecipeListEntryBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
// Set up anything that is supposed to happen when interacting with a single item in the recyclerView
val current = getItem(position)
holder.itemView.setOnClickListener {
onRecipeClicked(current)
}
holder.bind(current)
}
class RecipeViewHolder(private var binding: RecipeListEntryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(recipe: Recipe) {
binding.recipeTitle.text = recipe.recipeTitle
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Recipe>() {
override fun areItemsTheSame(oldRecipe: Recipe, newRecipe: Recipe): Boolean {
return oldRecipe === newRecipe
}
override fun areContentsTheSame(oldRecipe: Recipe, newRecipe: Recipe): Boolean {
// TODO: Might need to make this more sophisticated
return oldRecipe.recipeTitle == newRecipe.recipeTitle
}
}
}
}
设计器预览
来自 Android Studio 设计师的预览
实际结果
模拟 Pixel 5 的实际结果 运行 API 30/Android 11
您的代码:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
return RecipeViewHolder(
RecipeListEntryBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
不会在父视图的上下文中膨胀视图,因此某些布局参数不会按您预期的方式处理。在这种情况下,如果它不知道其父对象是谁,它的宽度就不能为 match_parent
。将其更改为:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
return RecipeViewHolder(
RecipeListEntryBinding.inflate(
LayoutInflater.from(
parent.context
),
parent,
false
)
)
}
另外,小费。您不需要创建一个伴随对象来保存您的 DiffCallback。而且您也不需要 属性 来容纳 object
。为了更简洁的代码,而不是
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Recipe>() {
//...
}
}
你可以把
private object DiffCallback: DiffUtil.ItemCallback<Recipe>() {
//...
}
我正在尝试设置回收器视图,并使用 ConstraintLayout 显示 元素。我用了 layout from this example by Google as a guideline.
然而,尽管一直指定 android:layout_width="match_parent"
,但最终显示在屏幕上的结果更类似于 wrap_content
。
到目前为止我尝试了什么
- 将 ConstraintLayout 更改为 LinearLayout
- 此
- 设置
android:layout_width=0
,同时使用ConstraintLayout并将开始、结束和顶部锚定到父级 - 将整个包装在 CardView 中
- 运行 多个设备 上的应用程序, 模拟和物理
相关代码
如果遗漏任何其他感兴趣的内容,请告诉我。
recipe_list_entry.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="@dimen/recipe_entry_height" >
<TextView
android:id="@+id/recipe_title"
style="@style/Widget.RecipeTracker.ListItemTextView"
android:layout_marginHorizontal="@dimen/margin_between_elements"
android:background="@color/design_default_color_on_secondary"
android:layout_width="match_parent"
android:fontFamily="sans-serif"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:maxLines="1"
android:ellipsize="end"
tools:text="amazing stuff"/>
</androidx.constraintlayout.widget.ConstraintLayout>
styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.RecipeTracker.ListItemTextView" parent="Widget.MaterialComponents.TextView">
<item name="android:gravity">center_vertical</item>
<item name="android:layout_height">48dp</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
</resources>
recipe_list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RecipeListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_between_elements"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_entry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/add_entry_fab_margin"
android:layout_marginBottom="@dimen/add_entry_fab_margin"
android:contentDescription="@string/add_new_recipe_contentDescription"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_add" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecipeListFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import me.lindenbauer.recipetracker.databinding.RecipeListFragmentBinding
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class RecipeListFragment : Fragment() {
private val viewModel: RecipeTrackerViewModel by activityViewModels {
RecipeTrackerViewModelFactory(
(activity?.application as RecipeTrackerApplication).database.recipeDao()
)
}
private var _binding: RecipeListFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = RecipeListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set up the automatic data updates for the recyclerView using the RecipeListAdapter
val adapter = RecipeListAdapter {
// TODO: Define onRecipeClicked here
}
// This specifies how the "cards" are displayed by the recyclerView
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.recyclerView.adapter = adapter
// Attach an observer on the allItems list to update the UI automatically when the data
// changes.
viewModel.allRecipes.observe(this.viewLifecycleOwner) { recipes ->
recipes.let{ adapter.submitList(it) }
}
binding.addEntry.setOnClickListener{
findNavController().navigate(R.id.action_RecipeListFragment_to_AddEntryFragment)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
RecipeListAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import me.lindenbauer.recipetracker.data.Recipe
import me.lindenbauer.recipetracker.databinding.RecipeListEntryBinding
class RecipeListAdapter(private val onRecipeClicked: (Recipe) -> Unit):
ListAdapter<Recipe, RecipeListAdapter.RecipeViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
return RecipeViewHolder(
RecipeListEntryBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
// Set up anything that is supposed to happen when interacting with a single item in the recyclerView
val current = getItem(position)
holder.itemView.setOnClickListener {
onRecipeClicked(current)
}
holder.bind(current)
}
class RecipeViewHolder(private var binding: RecipeListEntryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(recipe: Recipe) {
binding.recipeTitle.text = recipe.recipeTitle
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Recipe>() {
override fun areItemsTheSame(oldRecipe: Recipe, newRecipe: Recipe): Boolean {
return oldRecipe === newRecipe
}
override fun areContentsTheSame(oldRecipe: Recipe, newRecipe: Recipe): Boolean {
// TODO: Might need to make this more sophisticated
return oldRecipe.recipeTitle == newRecipe.recipeTitle
}
}
}
}
设计器预览
实际结果
您的代码:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
return RecipeViewHolder(
RecipeListEntryBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
不会在父视图的上下文中膨胀视图,因此某些布局参数不会按您预期的方式处理。在这种情况下,如果它不知道其父对象是谁,它的宽度就不能为 match_parent
。将其更改为:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
return RecipeViewHolder(
RecipeListEntryBinding.inflate(
LayoutInflater.from(
parent.context
),
parent,
false
)
)
}
另外,小费。您不需要创建一个伴随对象来保存您的 DiffCallback。而且您也不需要 属性 来容纳 object
。为了更简洁的代码,而不是
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Recipe>() {
//...
}
}
你可以把
private object DiffCallback: DiffUtil.ItemCallback<Recipe>() {
//...
}