My current problem is, that my LoadStateAdapter
which shows the loading and error state, is not centered inside my recyclerview
, which has a gridlayout as a layoutmanager. I didn't find anything about this at the official android developer website, so I am asking here: How can I center my LoadStateAdapter
inside my 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() }
}
}
}
<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"/>
If you use "withLoadStateFooter" try this code. 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
}
}
}
First you have to create multiple view types in your PagingDataAdapter After that override getItemViewType method as shown below
// 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;
}
Then set span size dynamically
// 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;
}
});
You can checkout this paging 3 example which includes displaying loading state view in center
After I google your question, I found this anwser about spanSizeLookup
.
First we create an instance of the GridLayoutManager
and add it to the 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
We have to create two different ViewHolders
because we want to display two different layouts.
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
}
The ShopLoadAdapter
should look like this:
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With