Android:LoadStateAdapter 不在 recyclerview 网格布局中居中

Posted

技术标签:

【中文标题】Android:LoadStateAdapter 不在 recyclerview 网格布局中居中【英文标题】:Android: LoadStateAdapter not centered inside recyclerview gridlayout 【发布时间】:2021-03-25 05:26:32 【问题描述】:

我当前的问题是,我的LoadStateAdapter 显示加载和错误状态,不在我的recyclerview 内,它有一个网格布局作为布局管理器。我在官方的 android 开发者网站上没有找到任何关于此的信息,所以我在这里问:如何将我的 LoadStateAdapter 居中在我的 Recyclerview 中?

当前

片段

@AndroidEntryPoint
class ShopFragment : Fragment(R.layout.fragment_shop), ShopAdapter.OnItemClickListener 
    private val shopViewModel: ShopViewModel by viewModels()
    private val shopBinding: FragmentShopBinding by viewBinding()
    @Inject lateinit var shopListAdapter: ShopAdapter

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

    private fun bindObjects() 
        shopBinding.adapter = shopListAdapter.withLoadStateFooter(ShopLoadAdapter(shopListAdapter::retry))
        shopListAdapter.clickHandler(this)
    

    override fun onDestroyView() 
        requireView().findViewById<RecyclerView>(R.id.rv_shop).adapter = null
        super.onDestroyView()
    

适配器

@FragmentScoped
class ShopLoadAdapter(private val retry: () -> Unit): LoadStateAdapter<ShopLoadAdapter.ShopLoadStateViewHolder>() 

    inner class ShopLoadStateViewHolder(private val binding: ShopLoadStateFooterBinding) : RecyclerView.ViewHolder(binding.root) 
        fun bind(loadState: LoadState) 
            with(binding) 
                shopLoadPb.isVisible = loadState is LoadState.Loading
                shopLoadMbtnRetry.isVisible = loadState is LoadState.Error
                shopLoadTvError.isVisible = loadState is LoadState.Error
            
        
    

    override fun onBindViewHolder(holder: ShopLoadStateViewHolder, loadState: LoadState) = holder.bind(loadState)

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): ShopLoadStateViewHolder 
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = ShopLoadStateFooterBinding.inflate(layoutInflater, parent, false)
        return ShopLoadStateViewHolder(binding).also 
            binding.shopLoadMbtnRetry.setOnClickListener  retry.invoke() 
        
    

布局.xml

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_shop"
    android:layout_
    android:layout_
    android:layout_marginStart="8dp"
    android:layout_marginEnd="8dp"
    app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
    app:spanCount="2"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/headline"
    app:recyclerview_adapter="@adapter"
    tools:listitem="@layout/shop_list_item"/>

【问题讨论】:

我觉得和setSpanSizeLookup有关你可以查***.com/questions/63509661/… 这对我有什么帮助?我知道LoadStateAdapter 始终是我recylerview 中的最后一项,所以我必须将我的最后一项的跨度大小设置为与其他项不同,对吗?但是这里的问题是有时 LoadStateAdapter 没有显示,因为没有错误或加载状态等。 应该是分页库的问题。它适用于除 gridlayoutmanager 之外的每个布局管理器。我在 issuetracker 上创建了一个issue。您可以为该问题加注星标以获得快速关注。 @gowtham6672 我做到了。 【参考方案1】:

如果您使用“withLoadStateFooter”,请尝试此代码。 source

val footerAdapter = MainLoadStateAdapter(adapter)
recyclerView.adapter = adapter.withLoadStateFooter(footer = footerAdapter)
gridLayoutManager.spanSizeLookup =  object : GridLayoutManager.SpanSizeLookup() 
        override fun getSpanSize(position: Int): Int 
            return if (position == adapter.itemCount  && footerAdapter.itemCount > 0) 
                2
             else 
                1
            
        
    

【讨论】:

这是正确的解决方案,由 Yigit Bojar 本人提供。感谢您找到这个!【参考方案2】:

首先,您必须在 PagingDataAdapter 中创建多种视图类型 之后覆盖 getItemViewType 方法,如下所示

// Define Loading ViewType
public static final int LOADING_ITEM = 0;
// Define Movie ViewType
public static final int MOVIE_ITEM = 1;

@Override
public int getItemViewType(int position) 
    // set ViewType
    return position == getItemCount() ? MOVIE_ITEM : LOADING_ITEM;

然后动态设置跨度大小

// set Grid span
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() 
        @Override
        public int getSpanSize(int position) 
            // If progress will be shown then span size will be 1 otherwise it will be 2
            return moviesAdapter.getItemViewType(position) == MoviesAdapter.LOADING_ITEM ? 1 : 2;
        
    );

您可以查看此paging 3 example,其中包括在中心显示加载状态视图

【讨论】:

glidlayoutmanager 逻辑属于哪个类?在片段中?感谢您的方法,我将对此进行测试并稍后回答 您在图像中显示的 recyclerview 每行包含 2 个项目,这意味着您必须使用 GridLayoutManager,其中 2 作为跨度计数。您必须在该布局管理器上设置 setSpanSizeLookup 回调。 如果您到达结果的末尾并且最后一项不是加载状态页脚而是实际项怎么办?【参考方案3】:

在谷歌搜索您的问题后,我发现this anwser 与spanSizeLookup 有关。 首先我们创建一个GridLayoutManager 的实例并将其添加到RecyclerView

    // Create a grid layout with two columns
    val adapter = ShopLoadAdapter(yourItems)
    val layoutManager = GridLayoutManager(context, 2)

    // Create a custom SpanSizeLookup where the first item spans both columns
    layoutManager.spanSizeLookup = object : SpanSizeLookup() 
        override fun getSpanSize(position: Int): Int 
            return if (adapter.getItemViewType(position) == ShopLoadAdapter.ERROR_RETRY) 2 else 1
        
    

    val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
    recyclerView.layoutManager = layoutManager
    recyclerView.adapter = adapter

我们必须创建两个不同的ViewHolders,因为我们要显示两个不同的布局。

class ViewHolderA(view: View) : RecyclerView.ViewHolder(view) 
    var image: ImageView = view.findViewById(R.id.image)
    //initialize your views for your items


class ViewHolderB(view: View) : RecyclerView.ViewHolder(view) 
    val button: Button = view.findViewById(R.id.retry_button)
    //initalize your views for the retry item

ShopLoadAdapter 应如下所示:

class ShopLoadAdapter(private val items: ArrayList<Item>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() 

    val NORMAL_ITEM = 0
    val ERROR_RETRY = 1

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder 
        val view: View

        if(viewType == ShopLoadAdapter.NORMAL_ITEM)
            view = LayoutInflater.from(viewGroup.context)
                    .inflate(R.layout.normal_item, viewGroup, false)
            return ViewHolderA(view)
        

        view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.error_retry_item, viewGroup, false)
        return ViewHolderB(view)
    

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int)     
        // Bind your items using i as position
    

    override fun getItemViewType(position: Int): Int 
        return when 
            items[position].hasError -> 1
            else -> 0
        
    

    override fun getItemCount(): Int 
        return items.size
    

【讨论】:

我稍后会尝试这两种解决方案。目前,我没有时间这样做,但我很感激你的回答。谢谢【参考方案4】:

如果您还有用于刷新状态的 loadStateAdapter,请使用此选项。

gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() 
        override fun getSpanSize(position: Int): Int 
            return if ((position == adapter.itemCount) && footerAdapter.itemCount > 0) 
                spanCount
             else if (adapter.itemCount == 0 && headerAdapter.itemCount > 0) 
                spanCount
             else 
                1
            
        
    

【讨论】:

以上是关于Android:LoadStateAdapter 不在 recyclerview 网格布局中居中的主要内容,如果未能解决你的问题,请参考以下文章

Android Paging3 Footer踩坑优化

第 3 页初始加载未显示

Android逆向系列文章— Android基础逆向

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本