Android横向RecyclerView两行不等宽布局问题求助
横向GridLayoutManager RecyclerView动态设置Item宽度失效问题解决
问题背景
在Android中使用带GridLayoutManager的横向RecyclerView实现布局时,通过设备宽度动态设置Item宽度,仅第一个位置的宽度生效,onBindViewHolder中能正确获取到位置值(0、1、2等),但后续Item宽度不生效。
需求与当前效果
需求设计
当前输出
现有代码
RecyclerView声明代码
val layoutManager: RecyclerView.LayoutManager = GridLayoutManager(requireContext(), 2, GridLayoutManager.HORIZONTAL, false) binding.rvStoreTypesHome.setLayoutManager(layoutManager) binding.rvStoreTypesHome.adapter = StoreTypesAdapter(requireContext())
Adapter代码
class StoreTypesAdapter(val context: Context) : RecyclerView.Adapter<StoreTypesAdapter.ViewHolder>() { var width = 0 init { width = context.getDeviceWidth() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val binding = StoreTypesAdapterBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(binding) } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val layoutParams = holder.binding.conSTAdapter.layoutParams layoutParams.width = if (holder.adapterPosition % 2 == 0) { (width / 2.5).toInt() } else { (width / 3.5).toInt() } layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT holder.binding.conSTAdapter.layoutParams = layoutParams holder.binding.tvStoreNameSTAdapter.text = "Store Name $position" } override fun getItemCount(): Int { return 10 } inner class ViewHolder(var binding: StoreTypesAdapterBinding) : RecyclerView.ViewHolder(binding.root) }
问题原因
GridLayoutManager在横向网格模式下,会基于设置的列数(此处为2)统一计算并约束Item的宽度,onBindViewHolder中修改的LayoutParams会被LayoutManager的测量布局逻辑覆盖,仅第一个Item因初始渲染时机问题暂时生效,后续Item复用或滚动时会被强制重置宽度。
解决方案
方案1:自定义GridLayoutManager重写测量逻辑
通过自定义GridLayoutManager,在测量阶段动态设置每个Item的宽度,避免被默认逻辑覆盖:
class CustomGridLayoutManager(context: Context, spanCount: Int, orientation: Int, reverseLayout: Boolean) : GridLayoutManager(context, spanCount, orientation, reverseLayout) { private val deviceWidth = context.resources.displayMetrics.widthPixels override fun measureChildWithMargins(child: View, widthUsed: Int, heightUsed: Int) { val position = getPosition(child) val layoutParams = child.layoutParams as RecyclerView.LayoutParams // 根据位置设置对应宽度 layoutParams.width = if (position % 2 == 0) { (deviceWidth / 2.5).toInt() } else { (deviceWidth / 3.5).toInt() } child.layoutParams = layoutParams super.measureChildWithMargins(child, widthUsed, heightUsed) } }
替换原有LayoutManager:
val layoutManager = CustomGridLayoutManager(requireContext(), 2, GridLayoutManager.HORIZONTAL, false) binding.rvStoreTypesHome.layoutManager = layoutManager binding.rvStoreTypesHome.adapter = StoreTypesAdapter(requireContext())
方案2:改用LinearLayoutManager(更简单)
由于需求是横向交替宽度的Item,直接使用横向LinearLayoutManager,它不会强制统一Item宽度,原有Adapter逻辑即可生效:
- 修改RecyclerView的LayoutManager:
val layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) binding.rvStoreTypesHome.layoutManager = layoutManager binding.rvStoreTypesHome.adapter = StoreTypesAdapter(requireContext())
- 调整Adapter的
onBindViewHolder,直接使用参数中的position(比adapterPosition更可靠):
@SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val layoutParams = holder.binding.conSTAdapter.layoutParams as ViewGroup.LayoutParams layoutParams.width = if (position % 2 == 0) { (context.resources.displayMetrics.widthPixels / 2.5).toInt() } else { (context.resources.displayMetrics.widthPixels / 3.5).toInt() } holder.binding.conSTAdapter.layoutParams = layoutParams holder.binding.tvStoreNameSTAdapter.text = "Store Name $position" }
注意事项
- 优先使用
onBindViewHolder参数中的position,而非holder.adapterPosition,避免Item复用导致的位置偏差 - 获取设备宽度建议使用
context.resources.displayMetrics.widthPixels,替代自定义的getDeviceWidth()方法,兼容性更好
内容的提问来源于stack exchange,提问作者Kishan Viramgama
相关产品推荐
相关产品推荐

