即使指定了“match_parent”,使用 ConstraintLayout 的 RecyclerView 项目也不会填满屏幕的整个宽度

Posted

技术标签:

【中文标题】即使指定了“match_parent”,使用 ConstraintLayout 的 RecyclerView 项目也不会填满屏幕的整个宽度【英文标题】:RecyclerView items using ConstraintLayout are not filling the entire width of the screen even though "match_parent" is specified 【发布时间】:2021-12-27 00:53:31 【问题描述】:

我正在尝试设置回收站视图使用 ConstraintLayout 显示元素。我用layout from this example by Google as a guideline.

然而,尽管一直指定android:layout_width="match_parent",但最终显示在屏幕上的结果更类似于wrap_content

到目前为止我所尝试的

    ConstraintLayout 更改为 LinearLayout *** question 中建议的解决方法 设置 android:layout_width=0,同时使用 ConstraintLayout 并将 start、end 和 top 锚定到父级 将整个内容封装在 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_
    android:layout_ >

    <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_
        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_
    android:layout_
    tools:context=".RecipeListFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_
        android:layout_
        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_
        android:layout_
        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 的实际结果

【问题讨论】:

【参考方案1】:

你的代码在这里:

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>() 
    //...

【讨论】:

谢谢,这似乎成功了。只是为了我的理解,因为我现在正在学习原生 Android:The problem was this usecase correct? 你能否指出我记录了 Binding 类生成的方法的地方?我在此评论中链接的 Android 文档中甚至没有链接该方法。我找不到任何记录 ViewBinding.inflate() 可用覆盖的内容。它可能会帮助我更多地理解 ViewBinding。 我还没有找到任何方法来查看绑定方法的特定文档,但是 ViewBinding.inflate 的两个重载与 LayoutInflater.inflate 的重载匹配,除了视图 ID 的第一个参数是换成了 LayoutInflater 本身。还有ViewBinding.bind() 为已经膨胀的视图创建绑定实例。这对于您将布局 ID 传递给超级构造函数的片段特别有用。您可以省略整个 onCreateView 样板文件并将您的绑定绑定到 onViewCreated 中。

以上是关于即使指定了“match_parent”,使用 ConstraintLayout 的 RecyclerView 项目也不会填满屏幕的整个宽度的主要内容,如果未能解决你的问题,请参考以下文章

对于Android View绘制的一些思考

GetAsync 使用基本身份验证,即使我指定了 Bearer

即使指定了 JWT 身份验证,也没有指定身份验证方案错误

安卓开发中的4中基本布局

Android 的 onMeasure 函数笔记

即使指定了 samesite=none,浏览器也会使用 samesite=lax