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

10

我目前的问题是,我的 LoadStateAdapter 显示加载和错误状态,但它没有居中在我的 recyclerview 中,而 recyclerview 使用的是 gridlayout 作为 layoutmanager。我在官方的 Android 开发者网站上没有找到有关这个问题的信息,所以我在这里问:如何将我的 LoadStateAdapter 居中显示在我的 Recyclerview 中?

当前情况

enter image description here

片段

@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() }
        }
    }
}

Layout.xml

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_shop"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    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有关,您可以查看https://stackoverflow.com/questions/63509661/how-to-prepare-setspansizelookup-in-gridlayoutmanager/63511928#63511928。 - Mohammed Alaa
这对我有什么帮助呢?我知道LoadStateAdapter总是在我的recylerview中的最后一项,所以我必须将我的最后一项的跨度大小设置为与其他项不同,对吧?但问题在于,有时候LoadStateAdapter并没有显示出来,因为没有错误或加载状态等。 - Andrew
1
这应该是分页库的问题。除了网格布局管理器之外,它与每个布局管理器都可以正常工作。我在issuetracker上创建了一个问题。您可以为该问题加星以获得快速关注。 - gowtham6672
@ gowtham6672 我已经做了。 - Andrew
4个回答

10
如果您使用 "withLoadStateFooter",请尝试此代码。来源
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
            }
        }
    }

1
这是由Yigit Bojar本人提供的正确解决方案。感谢您找到它! - Florian Walther

4
首先,您需要在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;
}

动态设置span尺寸

// 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;
        }
    });

您可以查看这个分页 3 示例,其中包括在中心显示加载状态视图。


网格布局管理器的逻辑属于哪个类?是在片段中吗?感谢您的方法,我会测试并稍后回答。 - Andrew
您在图像中展示的RecyclerView每行包含2个项目,这意味着您必须使用span count为2的GridLayoutManager。您需要在该布局管理器上设置setSpanSizeLookup回调函数。 - Amar Yadav
如果您到达结果的末尾,而最后一项不是loadstate页脚而是实际项目怎么办? - Florian Walther

1

我在谷歌上搜索了你的问题,找到了与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
    }
}

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

1

如果您还有加载状态适配器用于刷新状态,请使用此选项。

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
            }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接